分享 记一次 Redis 实战,实现答题系统

kikyous · 2016年08月03日 · 最后由 nouse 回复于 2016年08月06日 · 3944 次阅读

要做一个微信公众号答题系统,有一个题库,里面的题目分为 3 个难度等级,用户每次答题都是从每个等级随机选择一题,共三道题,答对的题不再出现。

class Topic < ApplicationRecord
  enum level: [ :one, :two, :three ]
end

随机选择题目想到了两种 sql 方案,一种是order by rand(), 一种是取得每个难度的 count,然后limit(1..count).first

而且要去掉已经答过的题目,需要建立新表记录答过的题目,再用复杂的子查询,如果题目规模很大,或者未来增加题目难度等级可能会导致性能非常的差!

这时候就考虑使用 redis 来解决问题。

首先需要在 redis 中为每个等级的题目维护一个 id 列表,可以选用 set。

Key Members
level_one_topic_set [1, 2, 3, ...]
level_two_topic_set [111, 112, 113, ...]
level_three_topic_set [1111, 1112, 1113, ...]

这没什么难度,使用 active_record callback 就行,我把他做成了一个 gem Rediscord

class Topic < ApplicationRecord
  enum level: [ :one, :two, :three ]

  include Rediscord
  redis_set key: ->(m){"level_#{m.level}_topic_set"}, redis: REDIS
end

有兴趣的可以去看看 https://github.com/kikyous/rediscord

接下来需要记录用户答过的题目,依然使用 set

用户每次答对的题目 id 塞进 redis 里面

REDIS.sadd("user_#{current_user.id}_topic_set", topic_ids)

Key Members
user_1_topic_set [x, xx, xxx, ...]
user_2_topic_set [x, xx, xxx, ...]
user_10_topic_set [x, xx, xxx, ...]

按规则为 id 为 1 的用户取一道 level one 的题

left_topics = "user_1_left_topic_set"
REDIS.sdiffstore left_topics, "level_one_topic_set", "user_1_topic_set"
REDIS.srandmember left_topics, 1

就是把 level_one_topic_set 和用户已经答过的题的 id 集合 "user_1_topic_set" 做一个 diff,并保存在一个 set user_1_left_topic_set 里面 然后利用 srandmember 从这个 set 里面随机取一个题目 id

剩下的两个等级同上。

没必要 order by rand(),给每个记录加一列随机数,然后 where rand_column < 生成的随机数

#1 楼 @mizuhashi 嗯,这个方法解决随机取数据好 👌

#1 楼 @mizuhashi
#2 楼 @kikyous

脑子转的慢,没太明白二位交流中所说的方法,所以请教一下。

我所理解的是使用 order by rand() 取随机记录是很方便的。但 @mizuhashi 的方法是要新增一个随机数字段,这意味着在程序和数据库中都要增加额外的处理和空间。这在我看来是变复杂了,所以不太明白好在哪里,烦请指点。

我来做的话,会选择用多一次查询:

Topic.find (Topic.pluck(:id) - current_user.answered_topic_ids).sample

#3 楼 @zzxworld order by rand() 要遍历全表为每一行生成随机数再排序,用 where 不用排序而且可以用到索引,你可以生成一个万行的表试试查询耗时

#3 楼 @zzxworld

我所理解的是使用 order by rand() 取随机记录是很方便的。

实在是太昂贵的操作了。如果只有十几条数据当然可以,如果几千条记录,每秒几十次查询那就是每秒遍历几十万条数据了。

#5 楼 @mizuhashi #6 楼 @msg7086 原来是性能上的考虑,感谢指点。

#4 楼 @quakewang 这样数据量大的话感觉效率不行

#8 楼 @kikyous 有什么不行的,题目又不会经常变,找个地方保存起来就好了。

我想到的就是给题目加上 iid 表明在当前 topic 下的 id,从 1 开始排序,这样只要 random 几个数字然后去数据库查询就好了。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号