作者:微信小助手
发布时间:2024-12-28T22:57:06
记录20亿的用户连续 7 天的打卡数据,如何统计出这连续 7 天连续打卡用户总数呢?
我们把每天的日期作为 Bitmap 的 key,userId 作为 offset,若是打卡则将 offset 位置的 bit 设置成 1。
key 对应的集合的每个 bit 位的数据则是一个用户在该日期的打卡记录。
一共有 7 个这样的 Bitmap,如果我们能对这 7 个 Bitmap 的对应的 bit 位做 『与』运算。
同样的 UserID offset 都是一样的,当一个 userID 在 7 个 Bitmap 对应对应的 offset 位置的 bit = 1 就说明该用户 7 天连续打卡。
结果保存到一个新 Bitmap 中,我们再通过BITCOUNT
统计 bit = 1 的个数便得到了连续打卡 7 天的用户总数了。
Redis 提供了BITOP operation destkey key [key ...]
这个指令用于对一个或者多个 键 = key 的 Bitmap 进行位元操作。
opration
可以是and
、OR
、NOT
、XOR
。当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作0
。空的key
也被看作是包含0
的字符串序列。
便于理解,如下图所示:
3 个 Bitmap,对应的 bit 位做「与」操作,结果保存到新的 Bitmap 中。
操作指令表示将 三个 bitmap 进行 AND 操作,并将结果保存到 destmap 中。接着对 destmap 执行 BITCOUNT 统计。
// 与操作
BITOP AND destmap bitmap:01 bitmap:02 bitmap:03
// 统计 bit 位 = 1 的个数
BITCOUNT destmap
简单计算下 一个一亿个位的 Bitmap占用的内存开销,大约占 12 MB 的内存(10^8/8/1024/1024),7 天的 Bitmap 的内存开销约为 84 MB。同时我们最好给 Bitmap 设置过期时间,让 Redis 删除过期的打卡数据,节省内存。
示例
# 假设有3个用户(ID: 1001,1002,1003)在3天内的打卡记录
# 第一天打卡记录 bitmap:20241213
SETBIT bitmap:20241213 1001 1 # 用户1001打卡
SETBIT bitmap:20241213 1002 1 # 用户1002打卡
SETBIT bitmap:20241213 1003 0 # 用户1003未打卡
# 第二天打卡记录 bitmap:20241214
SETBIT bitmap:20241214 1001 1 # 用户1001打卡
SETBIT bitmap:20241214 1002 0 # 用户1002未打卡
SETBIT bitmap:20241214 1003 1 # 用户1003打卡
# 第三天打卡记录 bitmap:20241215
SETBIT bitmap:20241215 1001 1 # 用户1001打卡
SETBIT bitmap:20241215 1002 1 # 用户1002打卡
SETBIT bitmap:20241215 1003 1 # 用户1003打卡
现在分析每个用户的情况:
用户1001的打卡记录:1 1 1 (连续打卡)
用户1002的打卡记录:1 0 1 (没有连续打卡)
用户1003的打卡记录:0 1 1 (没有连续打卡)
执行BITOP AND操作:
# 对三天的bitmap进行与运算,结果存入destmap
BITOP AND destmap bitmap:20241213 bitmap:20241214 bitmap:20241215
# 结果分析:
用户1001: 1 AND 1 AND 1 = 1 (在destmap中是1)
用户1002: 1 AND 0 AND 1 = 0 (在destmap中是0)
用户1003: 0 AND 1 AND 1 = 0 (在destmap中是0)
统计连续打卡用户数:
# 统计destmap中1的个数
BITCOUNT destmap # 结果为1,表示只有用户1001连续打卡
分析
三天的打卡记录可以表示为:
offset(用户ID): 1001 1002 1003
第一天: 1 1 0 (bitmap:20241213)
第二天: 1 0 1 (bitmap:20241214)
第三天: 1 1 1 (bitmap:20241215)
执行BITOP AND操作:
BITOP AND destmap bitmap:20241213 bitmap:20241214 bitmap:20241215
计算过程:
用户1001的位置: 1 AND 1 AND 1 = 1 (三天都打卡)
用户1002的位置: 1 AND 0 AND 1 = 0 (第二天没打卡)
用户1003的位置: 0 AND 1 AND 1 = 0 (第一天没打卡)
最终destmap的结果:
offset(用户ID): 1001 1002 1003
destmap: 1 0 0
当执行 BITCOUNT destmap 时:
- 统计destmap中值为1的位数
- 只有1001这个位置是1
- 其他位置都是0
- 所以结果为1,表示只有一个用户完成连续打卡
当我们遇到的统计场景只需要统计数据的二值状态,比如用户是否存在、 ip 是否是黑名单、以及签到打卡统计等场景就可以考虑使用 Bitmap。
只需要一个 bit 位就能表示 0 和 1。在统计海量数据的时候将大大减少内存占用。