最近在学习it老齐的架构设计,课程里面有这样一个课题,如何实现3ms内从1000w级别的用户里面随机抽取出100m用户。
首先,我们先从常规的常规的sql出发,如果要从数据表里面随机的选出几名员工的话,可能会使用mysql的相关函数
select username from user order by rand() limit 10
但是我们知道在mysql里面使用order by rand会让数据库性能呈指数级下降。因为MySQL会不得不去执行RAND()函数(很消耗CPU的时间),而且这是为每一条记录及取值,然后再对其排序,所以这种方法不推荐。
那么,我们来考虑使用第二种改进的方案。
方案二、
offset = select FLOOR(RAND()*COUNT()) AS offset FROM 关注用户表;
select * from 活动用户表 limit offset 1;
性能尚可,但是因为要执行两条语句,所以可能存在原子性的问题,且不能保证不会存在重复中奖的可能性。
接着,我们再深入思考,可不可以通过Redis这种方案来进行优化能
方案三、基于redis set集合做随机弹出
通过redis的set类型来做入栈和出栈的操作来随机抽出100名用户
具体实现方法:在用户关注直播间的时候,写入MySQL的同时额外在Redis增加userlist Set集合,存储用户编号。
sadd userlist userid1 userid2 userid3...
添加完成之后,预计1000w用户预计占用500M内存空间。
然后,从里面随机弹出100位用户即可。
spop userlist 100
10000009
10000034
10000453
10000053
10000034
10000434
.....
select username from 关注用户表 where id in (10000009,10000034,10000453,10000053,10000034,10000434);
这种做法的好处是,使用redis来取代了mysql获取中奖员工编号,提升了效率, 属于用空间换时间的做法,大大的提升了效率,但是仍然没有完全摆脱MySQL,最后一步,仍然是从MySQL中取值,没有把效率发挥到极致。
最后,我来放大招了。
方案四、完全使用Redis实现
sadd userlist "10011515:gaos" "1004554454:ccf" "121432452:zhangxr"....
然后从里面随机弹出100个元素
spop userlist 100
最后,对于弹出的结果进行相应的处理,只显示对应的用户名称即可。
import redis
from random import randint
if __name__ == '__main__':
redis_conn = redis.Redis(host="127.0.0.1", port=6379, password="")
print(redis_conn)
for i in range(100000):
num_int = str(i).zfill(6)
redis_conn.sadd("userlist", f"{randint(10000000,99999999)}:ccf{num_int}")
s2 = redis_conn.spop("userlist", 10)
print("获奖的员工为:")
for s in s2:
if len(s.decode("UTF-8").split(":")) > 1:
print(s.decode("UTF-8").split(":")[1])
执行完成之后,可以发现pop操作的时间控制在了3ms内,该方案有效!