应优先选用ZSET实现在线统计,因其支持时间戳排序与范围查询,可自动过滤过期用户;SET适合登出明确的场景但需手动清理;HyperLogLog仅适用于UV估算,不可用于实时在线统计。

直接用 SET 或 ZSET 都能做,但选错数据结构会导致统计不准、内存暴涨或无法清理过期用户——关键不在“能不能”,而在“怎么设过期”和“谁负责删”。
用 SET 统计在线数:简单但需手动维护生命周期
适合登录/登出行为明确、不依赖心跳的场景(比如后台管理系统)。SET 天然去重,scard 查总数快,但没内置过期机制。
- 用户上线:
r.sadd('online_users', user_id) - 用户下线:
r.srem('online_users', user_id) - 查总数:
r.scard('online_users') - 风险点:如果用户关浏览器没触发登出,
user_id会永远留在集合里,导致虚高 - 补救办法:必须配合业务层定时任务(如每5分钟扫一次最近10分钟无操作的用户)或前端主动发登出请求,不能只靠 Redis 自动清理
用 ZSET 实现带时间窗口的实时在线统计
更贴近真实 Web 场景(如社区、直播页),靠时间戳 + 范围查询自动过滤“假在线”用户。
- 上线时存:
r.zadd('online_users', {user_id: timestamp}),其中timestamp是time.time() - 查“最近5分钟在线”:
r.zcount('online_users', now - 300, now) - 清理过期项:
r.zremrangebyscore('online_users', 0, now - 300)(建议在每次查询后或单独定时执行) - 注意:
zcount不删除数据,只是计数;不主动zremrangebyscore,ZSET 会持续膨胀 - 别用
EXPIRE给整个 ZSET 设过期——它只对 key 生效,不影响内部成员的逻辑存活时间
用 HyperLogLog 统计 UV(独立访问用户)而非在线人数
很多人混淆“在线人数”和“今日 UV”。HyperLogLog 是概率型结构,误差率约 0.81%,但内存固定(12KB 支撑上亿用户),只适合去重计数,不能反查具体用户或判断是否“此刻在线”。
创建 Flask + Vue.js 全栈项目,采用单端口架构、uv 依赖管理、结构化日志和生产级前端设计。当用户要求以下操作时触发:创建新的 Flask+Vue 项目、搭建 Web 应用、构建全栈 Python+Vue 应用、初始化 Flask Web 项目、设置单端口 Flask 前后端项目。触发词:新建项目、创建项目、搭建项目、Flask项目、Vue项目。
立即学习“Python免费学习笔记(深入)”;
- 记录访问:
r.pfadd('uv:20260601', user_id) - 查估算值:
r.pfcount('uv:20260601') - 不能用于实时在线统计:它不存原始 ID,也不支持按时间范围剔除,更无法响应用户登出事件
- 典型误用:把
PFADD当成SADD用,结果发现查不到当前谁在线、也删不掉已下线用户
心跳机制是保证准确性的最后一道防线
纯靠登录/登出事件不可靠,浏览器异常关闭、网络中断都会让状态滞留。必须引入客户端定时心跳 + 服务端刷新逻辑。
- 前端每 10 秒调一次
/heartbeat?user_id=xxx - 后端收到后执行:
r.zadd('online_users', {user_id: time.time()}),并设置该 key 的EXPIRE为 15 秒(比心跳间隔略长) - 这样即使某次心跳丢失,最多 15 秒后该用户就会被
zcount自动排除 - 关键细节:不要对每个用户建单独 key(如
online:user:123),否则KEYS online:user:*扫描性能灾难;统一用一个 ZSET + 时间戳 score 是更可扩展的做法
真正难的不是写那几行 r.zadd,而是想清楚“在线”的定义边界:是只要登录就计入?还是必须 30 秒内有交互?这个逻辑一旦定错,后面所有数据都失真,而 Redis 本身不会提醒你。

















