业务背景
最近自己打算做一个 做题网站 既然是做题网站,就应该有随机出题的功能。本文的宗旨是给出几种业务实现方案,并会大致介绍每种策略的优缺点。
正文
mysql实现
思路: 我们观察到,如果我们对所有表中的所有数据进行随机排序,那么如果我们想随机抽取3道题,limit 3 即可。
对表中的数据进行随机排序:SELECT * FROM questions ORDER BY RAND()
那么,随机取3条数据就是:SELECT * FROM questions ORDER BY RAND() LIMIT 3
结果: 可以告诉大家的是,这样实现确实可以满足我们的需求,并且随机性非常高,这方面堪称完美,但是其所带来的时间损耗是非常高的。 order by rand()使用了内存临时表,内存临时表排序的时候使 用了rowid排序方法。 所以我们要解决这种问题,因为如果体量大的时候,有时候首页都不一定会打得开。
问题分析: 我们发现语句执行慢的原因是因为 进行了 order By 排序,提示sql语句最常用的方法就是走索引,我们考虑一下这种方法怎么样SELECT * FROM questions where id in (1,2,3) 很容易分析出,这条sql的语义是取 id为1,2,3 的这三道题。
我们发现这条sql语句确实可以走索引。可是它也带来了很多问题。
第一个问题:我们要如何确定 (1,2,3) 呢?
答案:利用mybatis 在java层面生成n个随机数传进来。
第二个问题:比如说数据库里面有100条数据,我们要在java层面确定随机数生成的范围呢?
答案:先 调用 SELECT count(*) FROM questions 确定有多少题目。
第三个问题:考虑一种特殊的情况: 数据库中此时有10道题,他们对应的 id 分别为(1,2,3,7,8,9,10,11,12,15)。也就是说 由于对题目进行删除的操作,导致他们的Id 没有严格连续上升 。那么如果此时 执行 SELECT count(*) FROM questions 获取到的 count = 10 按照我们之前的理论来看,我们会在 [0~10]之间生成随机数,那么永远取不到id为11,12,15 的题。
答案:将 SELECT count(*) FROM questions 改为 SELECT id FROM questions , 我们把这些Id用一个List集合进行接收,就可以之间在集合中进行Id的挑选了。
java实现
思路: 仿照第三个问题的解决方案即可。
代码实现
public List<Integer> getElementsRandom(List<Integer> list ,int number) {
List<Integer> res = new ArrayList<>();
int range = list.size();
Random random = new Random();
//选取的次数
while (number-- > 0) {
int rand = random.nextInt(range);
//抽取出选中的元素
res.add(list.get(rand));
swag(list,rand,range-1);
range--;
}
return res;
}
private void swag(List<Integer> list, int rand,int range) {
list.set(rand, list.get(range));
}
代码图解说明
选取第一个数:
选取第二个数:
依此类推,这样的一个好处是我们不用删除数据,因为list删除元素效率非常低,故我们只需要逐渐缩短访问区间即可。
我们发现如果将 所有ID 全部提取到java中,我们可以实现很多扩展的功能。
我们可以将所有Id保存到redis中,再通过一致性监听 来尽量做到同步。因为我们发现,题目是不会经常需要添加的,因此 select > insert 缓存到 redis 非常合适,其次,我们对该业务的实时性也没有较高的要求。
当我们需要连续出5轮题目,每轮3道题的时候,也可以进行非常简单的代码实现。
综上所述。