问题背景
在我们的系统中,系统管理员关注所有终端的在线状态。因此,管理系统需要列出每个终端当前是否在线,最后心跳(在线)时间,以及当前系统中所有在线、离线终端数量。
这是一个常见的需求。常见的方案是,终端定时给服务端发送心跳信息(例如每隔1分钟发一次心跳信息),服务端在数据库中维护一张终端在线状态表(t_client_status),该表包含终端(mid)、最后心跳时间(last_time)等信息。当管理员需要查看终端在线状态时,后台进程查询t_client_status表给出结果。
在终端数量较少(低于10K)时,以上提到的方案是可以满足功能需求和性能需求的。然而,当终端数量较大时,由于服务端需要频繁刷新t_client_status的last_time字段,产生大量的磁盘IO操作,导致系统性能下降,影响系统的并发数,甚至可能导致数据库崩溃。
因此,我们需要考虑更加有效的方案处理终端在线状态。
解决思路
传统的解决方案中,导致磁盘IO过多的原因,在于采用了全量更新的方式。对于每次终端上报的心跳信息,都去刷新数据库。因此,我们希望能增量地去完成刷新。我们的解决方案中,主要考虑如何完成增量刷新。
最后,我们发现,Redis的有序集合可以实现增量刷新的效果。
有序集合时Redis提供的一种集合类型的数据结构。和普通集合不同的是,有序集合中不仅保存元素,同时还保存元素的分数,并按照从顺序排列。有序集合可以用在积分榜等场景中。
Redis的有序集合提供了如下命令:
- zadd
- zcount
- zrangebyscore
- zmembers
- …
实现细节
1.添加心跳信息
zadd("heartbeats", nowtime+heartbeat_interval, mid)
2.查询在线总量
zcount("heartbeats", nowtime, "+inf")
3.增量刷新在线状态
//1. get offline clients
offline_list = zrangebyscore("heartbeats", "-inf", nowtime)
//2. get online clients
ret = zadd("heartbeats", nowtime+heartbeat_interval, mid)
if ret == 1 then
online_list += mid
//3. update offline_list && online_list to db