使用Redis的Set集合和有序集合结合心跳机制实现在线用户统计。高并发场景下,采用滑动窗口+分布式锁确保数据准确性和实时性。核心代码如下:
1. 用户上线:ZADD online_users <timestamp> user_id; SADD active_users user_id;
2. 心跳续期:用户每30s发送心跳,EXPIRE user_heartbeat:user_id 60;
3. 清理离线:定时任务每分钟执行ZREMRANGEBYSCORE online_users 0 <current-120>; DEL user_heartbeat:user_id if expired;
4. 在线数:ZCARD online_users 或 SCARD active_users;
5. 高并发防重:Lua脚本原子执行incr/decr操作,避免竞态。脚本示例:redis.call('ZADD', KEYS[1], ARGV[1], ARGV[2]); redis.call('EXPIRE', KEYS[1], 120);
方案一:基于Redis Set + 定时清理
当用户登录时,将用户ID加入到一个Redis Set集合中,命名为online_users。同时设置一个超时时间,比如5分钟。用户主动退出时,从集合中移除。服务器定时任务每分钟扫描集合中超时的用户ID并清理。这样既保证了实时性,也能处理异常掉线情况。高并发下使用pipeline批量操作提升性能。
方案二:使用Sorted Set + 心跳机制
采用ZSET,score为当前时间戳。用户每30秒发送心跳更新score。后台任务每分钟执行ZREMRANGEBYSCORE online_users 0 <now-120> 移除2分钟无心跳用户。查询在线数直接ZCARD,非常高效。在高并发场景,结合Redis Cluster分片存储,按用户ID哈希到不同节点。
方案三:Bitmap + 分布式锁
对于百万级用户,用Bitmap节省内存,每位代表一个用户在线状态。用户上线SETBIT online_bitmap user_id 1,下线SETBIT 0。准确性问题用Redlock分布式锁包装操作。高并发下分多个Bitmap,按用户ID取模分片存储,实时性通过BITCOUNT快速统计。
方案四:HyperLogLog近似统计
如果不需要精确数字,HyperLogLog误差<1%。用户上线PFADD online_hll user_id,下线不处理或定时清理。适合亿级UV统计,高并发无压力,内存占用固定8KB左右。但不适合精确在线用户数。
方案五:Stream + Consumer Group
用户事件发到Redis Stream,消费者组处理上线/下线事件更新计数器。结合Lua脚本原子更新incr在线数。实时性强,支持回溯,适合复杂业务逻辑。高并发用多个消费者并行处理。
优化:Lua脚本防竞态
高并发下多进程同时操作同一key会导致数据不准。用Lua脚本原子执行:local users = redis.call('SCARD', KEYS[1]); if users < 10000 then redis.call('SADD', KEYS[1], ARGV[1]); end; return users;
实际生产代码片段
function addOnlineUser(userId)
redis:eval([[
if redis.call('sismember', KEYS[1], ARGV[1]) == 0 then
redis.call('sadd', KEYS[1], ARGV[1])
redis.call('setex', KEYS[2]..ARGV[1], 300, ARGV[2])
end
]], 2, 'online_users', 'heartbeat:', userId, timestamp)
end
FAQ
Q: 高并发下为什么不用数据库统计在线用户?
A: 数据库单表QPS有限,Redis内存操作快几千倍。
Q: 用户异常掉线怎么处理?
A: 心跳超时+定时清理任务自动移除。
Q: 怎么保证多机部署数据一致?
A: Redis Cluster或Sentinel,主从同步。
Q: 内存占用怎么控制?
A: Set每用户64字节,结合LRU策略和分片。