记录了20亿用户连续 7 天的打卡数据,如何统计出这连续 7 天连续打卡用户总数呢?

作者:微信小助手

发布时间: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可以是andORNOTXOR。当 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。在统计海量数据的时候将大大减少内存占用。