用setbit而非incr或hset,因incr无法判重、hset内存开销大,而bitmap以1bit/用户实现高效去重与低内存(100万用户仅约125kb),但需将用户id映射为非负整数offset以避免报错。

为什么用 SETBIT 而不是 INCR 或 HSET
在线用户统计的核心是「去重 + 实时性 + 低内存」,INCR 只能计数不能判重,HSET 存用户 ID 会吃内存(每个 key 至少几十字节),而 Bitmap 用 1 bit 表示一个用户是否在线——100 万用户仅占约 125 KB。关键在于:必须把用户 ID 映射为非负整数索引,否则 SETBIT 会报 ERR bit offset is not an integer or out of range。
- 用户 ID 不能直接当 offset:字符串
"u_12345"或 UUID 不合法,必须转成 uint64(如哈希后取模,或用自增 ID) - offset 最大支持 2^32−1(Redis 6.2+ 支持 2^64−1),但实际建议控制在 10^8 以内,避免单个 Bitmap 过大影响网络传输和操作延迟
- 别用
GETBIT遍历判断在线数——复杂度 O(N),改用BITCOUNT
Go 里怎么安全调用 SETBIT 和 BITCOUNT
官方 github.com/go-redis/redis/v9 客户端对 Bitmap 操作支持良好,但要注意命令参数顺序和返回值类型。例如 SETBIT 第三个参数是 int64(0 或 1),不是 bool;BITCOUNT 默认统计整个 key,加 start/end 参数才能按字节范围查(比如只查今天的数据分区)。
- 上线:
rdb.SetBit(ctx, "online:20240520", int64(uid), 1).Err()—— key 建议带日期前缀,方便 TTL 清理 - 下线:用
SETBIT设为 0,不要DEL整个 key,否则会丢失其他用户的位状态 - 统计:
rdb.BitCount(ctx, "online:20240520", &redis.BitCount{Start: 0, End: -1}).Val()返回int64,不是 string - 注意 ctx 超时:Bitmap 操作虽快,但大 key 的
BITCOUNT可能卡住,生产环境务必设context.WithTimeout(ctx, 500*time.Millisecond)
如何避免用户 ID 冲突和 Bitmap 索引越界
直接用数据库自增 ID 当 offset 最省事,但微服务多实例写入时可能重复;用 hash(userId) % capacity 有哈希碰撞风险。更稳的做法是维护一个全局 user_id → bitmap_offset 映射,存在 Redis 的 Hash 里,首次上线时用 HSETNX 争抢分配。
- 初始化映射:
rdb.HSetNX(ctx, "uid2offset", userID, nextOffset).Val() == true成功才继续SETBIT - nextOffset 用
INCR维护:key 设为"bitmap:next_offset",每次成功分配后rdb.Incr(ctx, "bitmap:next_offset").Val() - 容量预估:如果预计日活 500 万,Bitmap 分配 800 万 slot(留 60% 余量),超过就切新 key + 迁移逻辑(实际中建议按周分片,避免单 key 膨胀)
- 别忽略时区:key 名如
"online:20240520"应基于 UTC 或统一业务时区,否则凌晨时段统计错乱
BITOP 合并多日数据时的坑
要算「近 7 日活跃用户」,不能简单 BITCOUNT 七次再相加——那是去重前的总和。得用 BITOP OR 把 7 个 key 合成一个临时 Bitmap,再 BITCOUNT。但 BITOP 输出 key 如果已存在,会被覆盖且不报错;更大的问题是:如果某天 key 不存在,Redis 会当成全 0 的空 Bitmap 处理,不影响结果,但容易让人误以为数据丢了。
- 临时 key 必须带随机后缀:
"tmp:active7:" + uuid.NewString(),用完立刻DEL - 检查源 key 是否存在:先
EXISTS7 个 key,跳过缺失的,避免把空 Bitmap 当作 0 位参与运算 -
BITOP是阻塞命令,7 个 10MB 的 Bitmap 合并可能耗时 200ms+,线上慎用;更稳妥的是用 HyperLogLog 做近似去重,Bitmap 仅用于精确的「当前在线」场景
Bitmap 真正难的不是命令调用,而是 offset 分配策略和 key 生命周期管理——这两块没设计好,后期扩容和数据修复成本极高。











