Redis Bitmap怎样统计每日签到的连续天数怎样在批处理代码中减少额外分配
来源:17golang原创
时间:2026-11-10 01:00:00 330浏览 收藏
我把签到统计做成批处理后,最稳妥的做法是:用 Redis Bitmap 的一个 bit 表示一天,按“用户 + 月份”拆 key;计算连续天数时从截止日向前扫描,遇到第一个 0 就停止。批量处理多个用户时,日期缓冲区、月份 key 和读取字节都在循环外复用,避免每个用户都新建临时对象。
官方地址:https://redis.io/docs/latest/develop/data-types/bitmaps/
这个方案适合“某天签没签到”和“连续多少天”这类布尔状态,不适合直接保存签到时间、设备或备注。
先把日期和位偏移定死
不要把全年日期直接塞进一个无限增长的 key。这里按月建立 key,例如 signin:{userID}:2026-11,1 日对应偏移 0,2 日对应偏移 1。月初、月末和时区必须由业务统一,否则凌晨切日时会出现“多一天”或“少一天”。

| 对象 | 示例 | 作用 |
|---|---|---|
| key | signin:42:2026-11 | 隔离用户和月份 |
| offset | day-1 | 定位某一天的 bit |
| value | 0 或 1 | 未签到或已签到 |
用 SETBIT 写入,用 GETBIT 验证
写入签到是幂等的,同一天重复执行仍然只是把同一位设为 1。下面的命令用于理解数据形状,行首注释说明每一步的目的。
# 第 1 天签到,偏移量是 0 redis-cli SETBIT signin:42:2026-11 0 1 # 第 3 天签到,偏移量是 2 redis-cli SETBIT signin:42:2026-11 2 1 # 验证第 2 天是否签到,返回 0 redis-cli GETBIT signin:42:2026-11 1 # 统计整个月已经签到的天数,不等于连续天数 redis-cli BITCOUNT signin:42:2026-11
BITCOUNT只能回答“总共签了几天”,连续天数仍要围绕截止日判断相邻 bit。若要一次处理很多天,可以读取位图字节后在内存中扫描,避免每天单独发一个命令。
从截止日向前扫描连续天数
下面的小函数把一个月的 Bitmap 字节作为输入。它不访问 Redis,因此便于在批处理中复用读取结果;跨月时由调用方再读取上一个月,不能把不存在的位误当成已签到。
package main
import "fmt"
// streakFromBytes 从截止日向前数连续的 1;截止日按 1-based 传入。
func streakFromBytes(bitmap []byte, cutoffDay int) int {
if cutoffDay = 1; day-- {
offset := day - 1
byteIndex, bitIndex := offset/8, uint(offset%8)
if byteIndex >= len(bitmap) {
break // 位图没有覆盖到这一天
}
if bitmap[byteIndex]&(1
批处理时复用缓冲区
性能问题通常不在一次 GET,而在几万名用户循环里反复拼 key、格式化日期、复制结果。可以把月份字符串和 key 缓冲放在循环外;每个用户只替换 ID 区段,并让 Redis 客户端返回的字节直接交给扫描函数。不要为了“零分配”牺牲可读性,先用基准测试确认热点。

// 复用固定容量的 key 缓冲,避免每个用户都重新分配完整字符串。
keyBuf := make([]byte, 0, 64)
month := "2026-11"
for _, userID := range userIDs {
keyBuf = keyBuf[:0] // 保留容量,只清空本轮长度
keyBuf = append(keyBuf, "signin:"...)
keyBuf = append(keyBuf, userID...)
keyBuf = append(keyBuf, ':')
keyBuf = append(keyBuf, month...)
key := string(keyBuf) // Redis 客户端需要稳定的字符串参数
// bytesFromRedis 是当前用户 Bitmap 的读取结果;实际项目中按客户端 API 获取。
streak := streakFromBytes(bytesFromRedis(key), cutoffDay)
_ = streak // 将结果写入批量更新或报告
}
如果客户端会异步持有参数,不能把仍会变化的字节切片直接交给它;此时转换成字符串或使用客户端提供的安全批量接口更稳妥。减少分配的前提是参数生命周期清楚。
验收四个边界
第一,截止日未签到时结果必须是 0;第二,连续签到从月初开始时,向前只能扫描当前月;第三,未来日期不能被当作已签到;第四,空 key 的读取结果要按全 0 处理。用 BITCOUNT 做总数核对,用几个已知日期做连续区间核对,二者不能混为一个指标。
我会把这个方案用于“签到是否连续”而不是奖励账本。需要记录补签原因、时间戳或多种状态时,应增加明细表或 Hash;Bitmap 的优势是位密度高、读写简单,代价是表达能力有限。
相关问题
Bitmap 能直接统计连续天数吗?不能。它能存位和统计 1 的总数,连续天数需要从截止日扫描相邻位。
为什么不每天调用一次 GETBIT?用户量大时网络往返会成为主要成本,按月读位图后在内存扫描更适合批处理。
复用切片一定更快吗?不一定。它只减少可观的临时分配,仍应配合基准测试和客户端生命周期约束验证。
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习