redis实现统计用户连续登陆天数
在很多app以及游戏当中,经常会统计用户连续登陆天数,使用mysql统计这份数据也可以解决大部分问题,但是像我们选择临时开放一个活动,7天连续签到,仅需要判断是否连续登陆七天,如果是,赠送礼包或者vip,该数据是不需要持久化的,这样我们采用redis来做便十分的方便了。
存在的挑战
- 数据如何尽可能用小的空间存储
- 如何能快速获取指定的数据
如果使用文件保存
会有如下问题:
-
文件分割变得十分麻烦
-
数据检索非常不方便
-
用户关联操作复杂
如果使用数据库表
会有如下问题:
- 占用空间增长速度快,表急剧增大
- 使用索引,易产生碎片,每次插入数据还要维护索引,影响性能
- 要用group ,sum等运算,计算较慢
- 无需持久化的数据进入了数据库
使用redis位图进行存储(setbit/getbit)
用户的签到状态无非两种,我们按月来统计用户签到信息,签到记录为1,未签到则记录为0.
把每一个bit位对应当月的每一天,形成了映射关系。用0和1标识业务状态,这种思路就称为位图(BitMap)。我们用31个bit位(4字节)即可表示我们一个月的签到记录,这样就用极小的空间,实现了大量数据的表示。
Redis中是利用string类型数据结构实现BitMap,最大上限是512M,转换为bit则是 2^32个bit位。BitMap的操作命令有:
SETBIT:向指定位置(offset)存入一个0或1
GETBIT :获取指定位置(offset)的bit值
BITCOUNT :统计BitMap中值为1的bit位的数量
BITFIELD :操作(查询、修改、自增)BitMap中bit数组中的指定位置(offset)的值
BITFIELD_RO :获取BitMap中bit数组,并以十进制形式返回
BITOP :将多个BitMap的结果做位运算(与 、或、异或)
BITPOS :查找bit数组中指定范围内第一个0或1出现的位置
优点:
- 由于我的业务中只需要根据某个用户id查询是否是活跃用户判断连续登陆天数,不存在复杂的查询条件,所以用redis很合适。
- redis中所有数据都是二进制形式存储的。redis支持一个setbit和getbit操作,它支持在某个key的value上直接对某个二进制位操作,每个二进制位都只有0和1两种状态,正好可以表示用户是否活跃两种状态。
- 存取速度非常快
思路
- 记录用户登陆:每天按日期生成一个位图, 用户登陆后,把user_id位上的bit值置为1
- 把1周的位图用 and 计算, 是否连续登陆用and计算,得到1即为连续登陆的用户,简单来说,能快速的拿到用户是否登陆的0/1状态,就能快速的计算出某段日期内登陆了几天
- 如果每次执行redis比较繁琐,可以简单的生成追加文件的方式,追加redis命令,例setbit到文件中,隔一段时间统一利用pipe mode通过管道的方式直接快速存入redis
代码展示
因为要统计连续登录的天数,所有这里我选择了一个Integer类型,如果只需要签到判断是否重复登陆使用Boolean类型即可。
controller层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /** * 用户簽到 */ @RequestMapping ( "sign" ) public Result<Integer> sign() { try { Integer result = subjectLikedDomainService.sign(); if (result == 0 ) { return Result.fail( "今日已签到,请明天再来~" ); // 今日已签到 } else { return Result.ok( "恭喜完成签到!已连续签到" +result+ "天" ); // 签到成功 } } catch (Exception e) { log.error( "UserController.sign.error:{}" , e.getMessage(), e); return Result.fail( false ); // 处理异常情况 } } |
Service层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | /** * 签到 * * @return */ @Override public Integer sign() { String loginId = LoginUtil.getLoginId(); LocalDate today = LocalDate.now(); long dayOfYear = today.getDayOfYear(); String key = SIGN_KEY_PREFIX + loginId; // 检查今天是否已经签到 boolean hasSignedToday = redisUtil.getbit(key, ( int ) dayOfYear); if (hasSignedToday) { // 如果今天已经签到,返回提示信息 log.info( "用户 {} 今日已签到" , loginId); return 0 ; } redisUtil.setbit(key, ( int ) dayOfYear, true ); // 计算连续签到天数 int consecutiveDays = calculateConsecutiveDays(redisUtil, key, ( int ) dayOfYear); log.info( "用户 {} 于 {} 完成签到.连续登陆 {}天" , loginId, today,consecutiveDays); return consecutiveDays; } private int calculateConsecutiveDays(RedisUtil redisUtil, String key, int dayOfYear) { int consecutiveDays = 0 ; while (dayOfYear >= 1 && redisUtil.getbit(key, dayOfYear)) { consecutiveDays++; dayOfYear--; } return consecutiveDays; } |
redisutil
1 2 3 4 5 6 7 8 | public void setbit(String key, int offset, boolean value) { redisTemplate.opsForValue().setBit(key, offset, value); } public boolean getbit(String key, int offset) { return redisTemplate.opsForValue().getBit(key, offset); } |
上述是以用户为维度的统计登陆天数,也是较为常用的方法。下面介绍一下时间维度与用户维度的区别。
以用户为维度
特点
- 用户独立:每个用户都有自己的签到记录,不受其他用户的影响。
- 个性化统计:可以精确地统计每个用户的连续登录天数。
- 存储结构:使用一个键来存储一个用户的所有签到状态,使用位图(bitmaps)来表示每一天的签到状态。
实现方式
- 键的设计:使用
sign:<loginId>
这样的格式来构建键,其中<loginId>
是用户的唯一标识符。 - 签到状态:使用位图中的一个位来表示每一天的签到状态,例如第1天、第2天等。
- 连续签到天数的计算:从今天的
dayOfYear
开始,向前检查每一天的签到状态,直到遇到没有签到的一天。
适用场景
- 当需要针对每个用户进行个性化的签到统计时。
- 当需要跟踪每个用户的连续签到天数,以便为用户提供个性化的奖励或激励时。
- 当用户基数较大且需要高效存储和查询时。
以时间为维度
特点
- 时间序列:关注的是某个时间段内所有用户的签到情况,而不是特定用户。
- 集体统计:可以统计某一天所有用户的签到情况,或者一段时间内所有用户的签到趋势。
- 存储结构:使用多个键来存储不同日期的签到状态,使用一个键来存储2024年8月21日所有用户的签到状态。
实现方式
- 键的设计:使用
sign:<date>
这样的格式来构建键,其中<date>
是日期,例如sign:2024-08-21
。 - 签到状态:使用一个集合(如哈希表或集合)来存储这一天内所有签到的用户ID。
- 连续签到天数的计算:需要跟踪每个用户的历史签到记录,可能需要额外的键或数据结构来维护每个用户的历史签到状态。
适用场景
- 当需要统计整个平台或特定时间段内所有用户的签到趋势时。
- 当需要分析签到活动的整体表现时,例如了解哪一天的签到率最高。
- 当需要对签到活动进行营销分析时,例如分析哪些促销活动最能提高签到率。
总结
- 以用户为维度 更适合个性化需求,例如为用户提供连续签到奖励,跟踪每个用户的签到历史。
- 以时间为维度 更适合整体统计需求,例如分析整个平台的签到趋势,评估营销活动的效果。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南