不能用setbit user:online:uid直接存单个用户状态,因亿级用户会导致位图长度逼近2³²引发redis realloc卡顿,且稀疏uid使sds分配大量零字节浪费内存;必须按时间维度建key、用心跳+bitpos扫描判定在线、bitcount统计实时在线数。

为什么不能用 SETBIT user:online:uid 直接存单个用户状态
看似简单,但会彻底失控:一个 uid 对应一个 offset,亿级用户意味着位图长度逼近 2³²(约 42 亿),Redis 内部 realloc 会卡顿毫秒级;更糟的是,若 uid 稀疏(比如只写 1 和 999999999),SDS 会为中间所有字节分配零内存,实际占用可能达数百 MB —— 不是“能不能存”,而是“一写就崩”。
必须放弃“每个用户一个 key”或“所有用户塞进一个 key”的直觉。核心原则是:位图 key 要按时间维度切分,每位代表一个用户,而非一个用户占多位。
在线判定必须靠心跳 + BITPOS 扫描,不能依赖客户端下线通知
客户端强退、网络中断、进程崩溃时,根本不会发 SETBIT 清零指令,位永远为 1,用户被永久标记“在线”。
正确做法:
- 客户端每 30 秒执行一次
rdb.SetBit(ctx, "online:"+time.Now().UTC().Format("20060102:15"), int64(uid)%16777216, 1)(取模保证 offset ≤ 2²⁴) - 服务端每分钟用
BITPOS online:20060102:15 0 -120从右往左扫最近 120 位(对应 2 小时窗口) - 若返回位置
p存在,说明最后一次心跳距今(now - p) * 60秒;超 180 秒即判定离线(留 1 分钟容错) - 禁止对 key 设置
EXPIRE——SETBIT不刷新 TTL,key 会永久存在;改用BITFIELD online:20060102:15 SET i8 0 0清旧位,或定时DEL过期 key
实时在线数统计必须用 BITCOUNT 按时间 key 计算,别碰单个用户位图
BITCOUNT user:online:1001 查的是该用户历史心跳总次数,不是当前是否在线。
要统计“此刻有多少人在线”,必须反向建模:
- key 格式固定为
online:20260811:02(精确到小时),每位对应一个用户 - offset =
uid % 16777216(即 2²⁴,约 1600 万槽位),写入前必须校验if uid = 16777216 -
BITCOUNT online:20260811:02就是该小时在线总数,O(1),内存仅 ~2MB - Go 客户端务必用
rdb.BitCount(ctx, "online:20260811:02", &redis.BitCount{Start: 0, End: -1}).Val(),start/end 单位是 byte,不是 bit
Go 调用必须封装 SetBit,严禁手拼 Do() 命令
手动 rdb.Do(ctx, "SETBIT", "key", offset, 1) 表面灵活,实则高危:
- 容易传错类型:
intvsint64导致 panic 或静默失败 - 忽略负 offset:
uid为负时,uint64(uid) & 0xFFFFFFFF会转成极大正数,直接越界 - 丢失返回值语义:
SetBit返回旧值(0 或 1),可用于判断重复上线,Do()需手动断言 interface{},极易出错 - 必须用
rdb.SetBit(ctx, key, int64(offset), 1)—— 它内部自动转类型,且对负 offset 直接 panic,逼你提前校验
真正难的不是写几行代码,而是把 offset 控制在安全范围内、把时间窗口和清旧逻辑对齐、让 BITPOS 扫描不漏判也不误判——这些细节一旦松动,线上就是持续飘红的“假在线”数据。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











