← 返回首页
Backend Guide

Redis ZSet 实现在线人数统计

在线人数统计是常见需求,实现方式很多。本文介绍一种基于 Redis 有序集合(ZSet)的轻量方案,利用过期时间做 score,实现自动排序、范围查询与批量清理。

站点:AI Atlas 标签:Redis · ZSet · 后端开发 更新:2026/07/09

前言

在线人数统计功能的核心是判断"谁在有效期内访问"。Redis 的有序集合(Sorted Set,简称 ZSet)非常适合这个场景,因为它天然支持按分数排序、范围查询和批量删除。

整个方案只需要掌握四个命令:

ZADD添加或更新成员及其分数
ZRANGEBYSCORE按分数范围查询成员
ZREMRANGEBYSCORE按分数范围批量删除成员
ZREM删除指定成员

如何认定用户是否在线

认定方式取决于站点类型。需要登录的站点可以直接用 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);
  });
});
将获取到的 visitorId 放到 Cookie 或请求头中传给后端,后端即可用这个 ID 标识匿名用户。

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 会用新的分数覆盖旧值。这意味着集合里始终保留该用户最新的活跃时间,不会重复计数。

分数使用格式化后的时间字符串转 double。实际生产环境建议直接使用 Unix 时间戳(秒或毫秒),避免精度问题。

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);
定时任务的执行频率建议与过期时长相匹配。例如过期时间为 5 分钟,则每 1 到 2 分钟清理一次即可。

zrem 退出时删除成员

用户主动退出登录时,应立即从集合中移除,避免"幽灵在线"。

// 用户退出登录时,直接删除对应成员
redisService.zrem("user.active", userToken);

这个操作是可选的:如果不处理,用户也会在下一次定时清理时被移除。但主动删除能让在线人数更实时、更准确。

方案小结

这套方案的核心思路是:创建一个有序集合作为在线用户池,用用户标识做 member,用过期时间做 score,然后围绕这个集合做增删改查。

操作命令说明
用户活跃ZADD以当前时间 + 过期时长为分数,写入或覆盖成员
查询在线ZRANGEBYSCORE查询分数大于当前时间的成员,返回集合大小
定时清理ZREMRANGEBYSCORE删除分数小于当前时间的成员(已过期)
主动退出ZREM用户退出时立即删除对应成员
相比用 Set + TTL 的方案,ZSet 的优势在于可以一次性查询整个在线列表,并按时间排序。如果需要统计"最近 N 分钟内活跃",只需调整查询的分数下限即可。

文章来源

本文内容整理自以下原始出处: