Redis ZSet 实现在线人数统计
在线人数统计是常见需求,实现方式很多。本文介绍一种基于 Redis 有序集合(ZSet)的轻量方案,利用过期时间做 score,实现自动排序、范围查询与批量清理。
前言
在线人数统计功能的核心是判断"谁在有效期内访问"。Redis 的有序集合(Sorted Set,简称 ZSet)非常适合这个场景,因为它天然支持按分数排序、范围查询和批量删除。
整个方案只需要掌握四个命令:
如何认定用户是否在线
认定方式取决于站点类型。需要登录的站点可以直接用 token 作为用户标识;公开站点则需要生成访客指纹。
登录站点:使用 Token
用户登录后,后端颁发一个带过期时间的 token。每次请求时验证 token 有效性,有效即视为在线。
公开站点:使用浏览器指纹
不需要登录的站点可以通过浏览器指纹技术生成唯一访客 ID。指纹信息通常包括 User-Agent、屏幕分辨率、时区、语言、插件列表等。
现成的库可以简化这个过程,例如 FingerprintJS:
// 安装:npm install @fingerprintjs/fingerprintjs
import FingerprintJS from '@fingerprintjs/fingerprintjs';
// 初始化指纹库
FingerprintJS.load().then(fp => {
// 获取访客唯一 ID
fp.get().then(result => {
const visitorId = result.visitorId;
console.log(visitorId);
});
});
zadd 添加在线用户
ZADD 命令需要三个参数:key(集合名)、score(分数)、member(成员)。在线统计场景中,分数是过期时间戳,成员是用户标识。
// 计算当前时间 + 过期时长,作为分数
LocalDateTime expireTime = LocalDateTime.now().plusSeconds(expireTimeout);
String expireTimeStr = DateUtil.formatFullTime(expireTime);
// 将用户 token 加入有序集合,分数为过期时间
redisService.zadd("user.active", Double.parseDouble(expireTimeStr), userToken);
同一个用户多次访问时,ZADD 会用新的分数覆盖旧值。这意味着集合里始终保留该用户最新的活跃时间,不会重复计数。
zrangeByScore 查询在线人数
ZRANGEBYSCORE 可以查询分数落在某个区间内的所有成员。把当前时间作为下限,正无穷作为上限,就能查出所有"尚未过期"的用户。
// 获取当前时间字符串
String now = DateUtil.formatFullTime(LocalDateTime.now());
// 查询分数在 [now, +inf] 区间的所有成员
Set<String> onlineUsers = redisService.zrangeByScore("user.active", now, "+inf");
// 在线人数
int onlineCount = onlineUsers.size();
返回的集合大小就是当前在线人数。查询的时间复杂度为 O(logN + M),其中 N 是集合总大小,M 是返回成员数,性能很好。
zremrangeByScore 定时清理
有序集合不会自动清理过期成员,需要写一个定时任务,定期删除分数低于当前时间的记录(即已经过期的用户)。
// 获取当前时间
String now = DateUtil.formatFullTime(LocalDateTime.now());
// 删除分数在 [-inf, now] 区间的所有成员(已过期)
redisService.zremrangeByScore("user.active", "-inf", now);
zrem 退出时删除成员
用户主动退出登录时,应立即从集合中移除,避免"幽灵在线"。
// 用户退出登录时,直接删除对应成员
redisService.zrem("user.active", userToken);
这个操作是可选的:如果不处理,用户也会在下一次定时清理时被移除。但主动删除能让在线人数更实时、更准确。
方案小结
这套方案的核心思路是:创建一个有序集合作为在线用户池,用用户标识做 member,用过期时间做 score,然后围绕这个集合做增删改查。
| 操作 | 命令 | 说明 |
|---|---|---|
| 用户活跃 | ZADD | 以当前时间 + 过期时长为分数,写入或覆盖成员 |
| 查询在线 | ZRANGEBYSCORE | 查询分数大于当前时间的成员,返回集合大小 |
| 定时清理 | ZREMRANGEBYSCORE | 删除分数小于当前时间的成员(已过期) |
| 主动退出 | ZREM | 用户退出时立即删除对应成员 |
文章来源
本文内容整理自以下原始出处: