不能用单个key存全年活跃数据,因bitcount是o(n)扫描,百亿用户bitmap达12.5gb会阻塞主线程数十毫秒;超大offset触发realloc卡顿,稀疏写入浪费内存;且offset须≤2³²−1,key必须含时间维度(如active:20260618)并禁用expire。

直接用 BITCOUNT 查单个大 key 会卡住 Redis 主线程,线上服务延迟飙升——这不是能不能做,而是必须分片 + 时间维度隔离 + 客户端聚合。
为什么不能用单个 key 存全年活跃数据
Redis 的 BITCOUNT 是 O(N) 扫描整个字符串,百亿用户映射成 12.5GB bitmap 时,一次调用可能阻塞主线程几十毫秒。更糟的是,SETBIT 写入超大 offset(比如 user_id 直接当 offset)会触发底层 realloc,稀疏写入还导致 SDS 分配大量零字节,内存浪费严重。
- offset 必须 ≤ 2³²−1,否则 Redis 内部 panic 或卡顿
- key 名必须含时间维度(如
active:20260618),否则无法清理过期数据 - 禁止对 bitmap key 设置
EXPIRE:SETBIT不刷新 TTL,key 会永久残留
Go 客户端怎么安全写入和读取
别手拼 rdb.Do(ctx, "SETBIT", ...),go-redis/v9 的封装方法能提前拦截非法值:
- 用
rdb.SetBit(ctx, key, int64(offset), 1),它会对负 offset 直接 panic,逼你校验 -
offset必须是非负整数:用户 ID 是int64时,不能直接uint64(id) & 0xFFFFFFFF,ID 为负会溢出成极大正数;应先if id -
BitCount的Start/End参数是 bit 索引(不是 byte),v9 已自动换算,传进去就是位偏移
如何高效统计 DAU 而不拖慢服务
核心是“分片 + 并行 + 时间窗口”:
- 把用户 ID 对 4096 取模,写入 4096 个 key:
active:20260618:0001、active:20260618:0002…,每个 key 控制在 1MB 以内(≈800 万 bit) - 统计时用
sync.WaitGroup并发调用rdb.BitCount(ctx, key).Result(),再 sum 总和 - 配合
redis.Pipelined批量发请求,压测显示比串行快 3–5 倍 - 若允许误差,改用
PFCOUNT(HyperLogLog),但无法支持“某用户是否活跃”这类精确查询
连续活跃/多日重合用户怎么算
BITOP AND 和 BITOP OR 是服务端原子操作,但有硬约束:
- 目标 key 不能和任一源 key 相同,否则命令失败(Redis 返回 error)
- 例如算「近 3 天都活跃」:用
BITOP AND active:20260616_17_18 active:20260616 active:20260617 active:20260618,目标 key 必须全新 - 注意 key 生命周期:临时结果 key 要设 TTL,避免堆积;建议用
DEL显式清理,别依赖 EXPIRE
真正难的不是写几行 SetBit,而是设计 key 分片规则、控制 offset 范围、协调客户端并发粒度——这些细节漏掉一个,线上就容易出现内存暴涨或统计不准。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











