登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  Redis

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 日期偏移与 Redis Bitmap 位状态的关系说明图
图1:Redis Bitmap 的日期偏移与签到位关系说明图,不是运行截图。
对象示例作用
keysignin:42:2026-11隔离用户和月份
offsetday-1定位某一天的 bit
value0 或 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 客户端返回的字节直接交给扫描函数。不要为了“零分配”牺牲可读性,先用基准测试确认热点。

批处理循环复用日期缓冲区和 Bitmap 字节缓冲区的路径说明图
图2:批量计算连续签到天数时复用缓冲区的处理路径说明图,不是运行截图。
// 复用固定容量的 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?用户量大时网络往返会成为主要成本,按月读位图后在内存扫描更适合批处理。

复用切片一定更快吗?不一定。它只减少可观的临时分配,仍应配合基准测试和客户端生命周期约束验证。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>