做题网站-随机出n道题的方案

171 阅读3分钟

业务背景


最近自己打算做一个 做题网站 既然是做题网站,就应该有随机出题的功能。本文的宗旨是给出几种业务实现方案,并会大致介绍每种策略的优缺点。

正文

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));
}

代码图解说明

选取第一个数:

image.png

image.png

选取第二个数:

image.png

依此类推,这样的一个好处是我们不用删除数据,因为list删除元素效率非常低,故我们只需要逐渐缩短访问区间即可。

我们发现如果将 所有ID 全部提取到java中,我们可以实现很多扩展的功能。

我们可以将所有Id保存到redis中,再通过一致性监听 来尽量做到同步。因为我们发现,题目是不会经常需要添加的,因此 select > insert 缓存到 redis 非常合适,其次,我们对该业务的实时性也没有较高的要求。

当我们需要连续出5轮题目,每轮3道题的时候,也可以进行非常简单的代码实现。

综上所述。