要做一个微信公众号答题系统,有一个题库,里面的题目分为 3 个难度等级,用户每次答题都是从每个等级随机选择一题,共三道题,答对的题不再出现。
class Topic < ApplicationRecord
enum level: [ :one, :two, :three ]
end
随机选择题目想到了两种 sql 方案,一种是order by rand()
, 一种是取得每个难度的 count,然后limit(1..count).first
而且要去掉已经答过的题目,需要建立新表记录答过的题目,再用复杂的子查询,如果题目规模很大,或者未来增加题目难度等级可能会导致性能非常的差!
这时候就考虑使用 redis 来解决问题。
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
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, ...] |
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
剩下的两个等级同上。