问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Redis实现用户连续登录天数统计

创作时间:
作者:
@小白创作中心

Redis实现用户连续登录天数统计

引用
1
来源
1.
https://www.cnblogs.com/azwz/p/18371398

在许多应用程序和游戏中,统计用户连续登录天数是一个常见的功能需求。本文将介绍如何使用Redis的位图(Bitmap)数据结构来高效实现这一功能,包括具体的代码实现和两种不同的维度统计方式(用户维度和时间维度)。

在很多app以及游戏当中,经常会统计用户连续登陆天数,使用mysql统计这份数据也可以解决大部分问题,但是像我们选择临时开放一个活动,7天连续签到,仅需要判断是否连续登陆七天,如果是,赠送礼包或者vip,该数据是不需要持久化的,这样我们采用redis来做便十分的方便了。

存在的挑战

  1. 数据如何尽可能用小的空间存储
  2. 如何能快速获取指定的数据

如果使用文件保存

会有如下问题:

文件分割变得十分麻烦

数据检索非常不方便

用户关联操作复杂

如果使用数据库表

会有如下问题:

  1. 占用空间增长速度快,表急剧增大
  2. 使用索引,易产生碎片,每次插入数据还要维护索引,影响性能
  3. 要用group ,sum等运算,计算较慢
  4. 无需持久化的数据进入了数据库

使用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出现的位置

优点:

  1. 由于我的业务中只需要根据某个用户id查询是否是活跃用户判断连续登陆天数,不存在复杂的查询条件,所以用redis很合适。

  2. redis中所有数据都是二进制形式存储的。redis支持一个setbit和getbit操作,它支持在某个key的value上直接对某个二进制位操作,每个二进制位都只有0和1两种状态,正好可以表示用户是否活跃两种状态。

  3. 存取速度非常快

思路

  1. 记录用户登陆:每天按日期生成一个位图, 用户登陆后,把user_id位上的bit值置为1

  2. 把1周的位图用 and 计算, 是否连续登陆用and计算,得到1即为连续登陆的用户,简单来说,能快速的拿到用户是否登陆的0/1状态,就能快速的计算出某段日期内登陆了几天

  3. 如果每次执行redis比较繁琐,可以简单的生成追加文件的方式,追加redis命令,例setbit到文件中,隔一段时间统一利用pipe mode通过管道的方式直接快速存入redis

代码展示

  因为要统计连续登录的天数,所有这里我选择了一个Integer类型,如果只需要签到判断是否重复登陆使用Boolean类型即可。

controller层

 /**
 * 用户簽到
 */
 @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层

  

  private static final String SIGN_KEY_PREFIX = "user_sign:";

/**
 * 签到
 *
 * @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

 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);
 }

 

上述是以用户为维度的统计登陆天数,也是较为常用的方法。下面介绍一下时间维度与用户维度的区别。

以用户为维度

特点

  1. 用户独立:每个用户都有自己的签到记录,不受其他用户的影响。

  2. 个性化统计:可以精确地统计每个用户的连续登录天数。

  3. 存储结构:使用一个键来存储一个用户的所有签到状态,使用位图(bitmaps)来表示每一天的签到状态。

实现方式

  • 键的设计:使用

sign:

这样的格式来构建键,其中

是用户的唯一标识符。

  • 签到状态:使用位图中的一个位来表示每一天的签到状态,例如第1天、第2天等。

  • 连续签到天数的计算:从今天的

dayOfYear

开始,向前检查每一天的签到状态,直到遇到没有签到的一天。

适用场景

  • 当需要针对每个用户进行个性化的签到统计时。

  • 当需要跟踪每个用户的连续签到天数,以便为用户提供个性化的奖励或激励时。

  • 当用户基数较大且需要高效存储和查询时。

以时间为维度

特点

  1. 时间序列:关注的是某个时间段内所有用户的签到情况,而不是特定用户。

  2. 集体统计:可以统计某一天所有用户的签到情况,或者一段时间内所有用户的签到趋势。

  3. 存储结构:使用多个键来存储不同日期的签到状态,使用一个键来存储2024年8月21日所有用户的签到状态。

实现方式

  • 键的设计:使用

sign:

这样的格式来构建键,其中

是日期,例如

sign:2024-08-21

  • 签到状态:使用一个集合(如哈希表或集合)来存储这一天内所有签到的用户ID。

  • 连续签到天数的计算:需要跟踪每个用户的历史签到记录,可能需要额外的键或数据结构来维护每个用户的历史签到状态。

适用场景

  • 当需要统计整个平台或特定时间段内所有用户的签到趋势时。

  • 当需要分析签到活动的整体表现时,例如了解哪一天的签到率最高。

  • 当需要对签到活动进行营销分析时,例如分析哪些促销活动最能提高签到率。

总结

  • 以用户为维度 更适合个性化需求,例如为用户提供连续签到奖励,跟踪每个用户的签到历史。

  • 以时间为维度 更适合整体统计需求,例如分析整个平台的签到趋势,评估营销活动的效果。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号