周一晚上10点,我正靠在椅子上刷短视频放松,手机突然弹出一个视频通话请求——又是小宋。
“哥,有空吗?”
“嗯,你说。”
“最近我抓紧时间投简历,约了个线上面试,结果又被问懵了!”小宋在视频那头一脸苦恼,“对方看我简历写有海量数据处理经验,就问:对于一个上亿用户的项目,如何记录用户连续登录天数?我说在MySQL里建表记录每天登录时间,查询时遍历。对方皱了皱眉,又问有什么补充,我提了分库分表…然后面试就草草结束了。你懂这个吗?给我讲讲吧。”
“记录连续登录天数在签到打卡场景很常见。面对上亿用户,只用MySQL建表记录每个用户的登录时间点,确实不合适。Redis的Bitmap方案会是更优解。这样,你先了解一下Redis记录连续登录天数的实现思路,我们再聊。”我回答道。
不一会儿,小宋回来了:“看完了,开始吧。”
“小宋,我们来算一笔账。”我说道,“如果为上亿用户每天记录一条登录状态,一年就是365亿条记录。每条记录至少包含用户ID和登录日期。如此庞大的数据量,存储成本高昂,查询效率更是堪忧——要统计某个用户的连续登录情况,相当于在汪洋大海里捞针,响应时间可能长达数分钟。这就像用一本巨大的记事本记录全国每个人的每日行程,查找时需要一页页翻,效率极低。”
Redis Bitmap方案的核心优势
对方期待的答案,很可能是Redis的Bitmap(位图)。Bitmap本质上是一个由二进制位(0或1)组成的数组,每个位代表一个二值状态,正好完美匹配“用户是否登录”这种是非判断。其最大优势在于极致的空间效率——一个512MB的Bitmap可以存储超过42亿个二进制位。
在实际设计中,主要有两种思路:
方案一:以日期为维度
- 为每一天创建一个独立的Bitmap,Key命名为例如
login:20251230。
- 将用户ID(需为数字)映射到位图中的偏移位置(offset)。
- 用户当日登录,则将其对应的位设置为1。
这种方案的优势是便于统计“某一天总共有多少用户登录”(使用BITCOUNT命令),但要查询单个用户的连续登录记录,则需要遍历该用户在所有相关日期Bitmap中的位。
方案二:以用户为维度
- 为每一个用户创建一个Bitmap,Key为用户ID。
- 位图的偏移量代表日期序列(例如第0位代表项目的第1天,或一个具体日期)。
- 用户当日登录,则将其对应日期的位设置为1。
这种方式查询单个用户的历史登录记录和连续天数非常直接高效,但当用户量达到亿级时,需要管理海量的Key,这可能带来新的挑战。
“对于上亿用户的场景,我通常推荐第一种方案,并结合合理的过期策略来优化。”我继续说道。
高效的落地实践
一个典型的落地设计如下:
- 限定统计范围:通常只关心用户最近30天(或N天)的连续登录情况,这符合大多数产品的实际需求。
- 设置自动过期:为每个日期维度的Bitmap设置30天的过期时间(TTL),让Redis自动清理历史数据,无需人工干预。
- 利用原生命令:直接使用Redis的
BITCOUNT(统计位数)、GETBIT(获取特定位的值)等命令进行操作,速度极快。
在这样的设计下,统计用户连续登录天数变得非常简单:从当前日期开始,向前逐天检查对应日期Bitmap中该用户位是否为1。连续遇到1则计数加1,一旦遇到0则停止,计数结果即为连续登录天数。
“所以你看,MySQL和Redis在这里扮演的角色完全不同。”我总结道,“MySQL擅长的是结构化数据的持久化存储与复杂关联查询,而Redis Bitmap则是专门为海量、稀疏的二值状态统计而生的利器,在这个特定场景下,它的性能和资源利用率具有压倒性优势。这也是为什么在面试中被问到高性能方案时,仅回答MySQL很难让面试官满意。”
“哦,我明白了!”视频那头的小宋若有所思,“确实,空间和效率差距太大了。谢了哥!”
“不客气,你可以再深入研究一下Redis Bitmap的相关命令和实战案例。多理解不同组件的特性和适用场景,在系统设计时就能做出更优的选择。如果想查看更多类似的架构设计讨论,也可以来我们云栈社区逛逛。没啥事我先挂了。”
于是,我挂断了视频通话。
|