当前位置:首页 > 问答 > 正文

用Redis搞并发数统计其实没那么难,分享点实操经验和思路

前段时间我在做项目的时候,遇到了一个需求,就是要实时统计我们平台上的在线用户并发数,这个数字不是简单统计总用户数,而是要精确到每一秒内,到底有多少个活跃的连接或者会话,老板想看着这个数字实时跳动,心里有底。

一开始,我脑子里闪过的第一个念头是:用数据库呗,用户一上线,我就在数据库里插一条记录,标记他上线了;他下线了,我就把这条记录删掉或者标记为失效,统计一下状态是“在线”的记录数不就完了?

但转念一想,这肯定不行,为啥?因为并发高的时候,这数据库的插入、删除、查询操作太频繁了,尤其是那个统计总数的SQL,在数据量大的时候就是个性能杀手,很容易就把数据库拖垮了,这方案太笨重,也不够快。

然后我就想到了Redis,Redis这东西,速度快,而且它支持的数据结构特别灵活,正好适合干这种高频读写的活儿,我当时琢磨了几个用Redis来实现的方法,这里跟你分享一下我的实操经验和思路。

第一个思路,也是最直观的:用String类型配合SETNX和DEL。

就是把每个用户会话分配一个唯一的标识符(比如sessionId),当用户上线时,我执行一个命令:SETNX user:session:12345 1,这个命令的意思是,只有当键user:session:12345不存在时,才设置它的值为1,如果设置成功,返回1;如果这个键已经存在(说明可能重复上线了),返回0。

这样一来,成功返回1的时候,我就知道新增了一个并发用户,我可以执行INCR global_online_count命令,让一个叫global_online_count的全局计数器加1。

用Redis搞并发数统计其实没那么难,分享点实操经验和思路

当用户下线时,我先执行DEL user:session:12345删除这个键,如果删除成功(返回1),我再执行DECR global_online_count,让全局计数器减1。

我想看当前并发数的时候,只需要一个简单的GET global_online_count命令就行了,速度飞快。

但是这个方法有个问题: 万一用户不是正常下线,比如网络突然断了,这个用户对应的DEL命令就没法执行,那么这个用户就会一直被统计在在线人数里,成了“僵尸键”,导致计数不准。

这就引出了第二个更完善的思路:利用Redis的过期时间(Expire)和SET的扩展命令。

为了解决“僵尸键”的问题,我得给每个用户的会话键设置一个过期时间,假设我们认为一个会话如果30秒内没有活动就算超时下线。

用Redis搞并发数统计其实没那么难,分享点实操经验和思路

用户上线时,我不再用SETNX,而是用SET user:session:12345 1 EX 30 NX,这个命令更强大:NX表示仅当键不存在时设置,EX 30表示同时设置这个键30秒后自动过期。

我依然需要维护那个全局计数器global_online_count,用户上线成功时,INCR它。

现在关键来了:如何在下线(过期)时,让全局计数器减1呢?这里就需要用到Redis的一个高级功能——过期键空间通知,我需要先在Redis配置里开启这个功能(配置notify-keyspace-events Ex)。

开启后,我可以让应用程序订阅一个特定的频道,当任何一个键过期时(比如user:session:12345在30秒后过期了),Redis会发布一个消息到这个频道,我的应用程序收到这个消息后,就知道有一个会话过期了,此时就可以执行DECR global_online_count来减少计数。

这个方法就精准多了,无论是正常下线(主动DEL)还是异常掉线(键过期),计数器都能得到正确的更新。

用Redis搞并发数统计其实没那么难,分享点实操经验和思路

第三个思路,是一个更取巧的办法:直接用HyperLogLog。

这是Redis提供的一种用于基数统计的数据结构,什么是基数统计?就是统计一个集合中不重复元素的个数,它最大的优点是非常节省内存,只需要12KB左右的内存,就能统计接近2^64个不重复元素,并且误差率很低,大约0.81%。

对于并发统计,我可以这样做:把每一秒(或者每5秒)划分成一个时间段,在当前这一秒内,每当有一个用户活动,我就执行PFADD concurrent:20231027:120500 user_id_12345,这个命令会把用户ID添加到对应这一秒的HyperLogLog结构中。

我想看某一秒的并发数,只需要执行PFCOUNT concurrent:20231027:120500,它返回的就是估算的这一秒内的去重用户数。

这个方法的优缺点都很明显:

  • 优点:极其省内存,适合统计非常大的数据量,比如全站的UV(独立访客)。
  • 缺点:它是估算值,不是精确值;而且它统计的是时间段内的去重用户数,更像是“一分钟内有多少独立用户活跃”,而不是“当前这一瞬间确切的在线人数”,所以它可能不适合我对“实时精确并发数”的要求,但作为一种思路也很有价值。

结合我的实际需求(需要精确的实时并发数),我选择了第二种思路,也就是“SET key value EX timeout NX” + “全局计数器” + “键空间通知”的组合方案,实现起来虽然比第一种复杂一点,但数据的准确性和可靠性最高,完全能满足老板盯着大屏看实时人数变化的需求。

所以你看,用Redis搞并发统计,核心就是利用它快和数据结构丰富的特点,针对不同的精度和场景要求,选择不同的命令组合,关键是理解每个命令的特性和可能遇到的问题,比如上面的“僵尸键”问题,想到了解决办法,事情就没那么难了。