redis实现统计用户连续登陆天数

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

存在的挑战

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

如果使用文件保存

   会有如下问题:

  1. 文件分割变得十分麻烦

  2. 数据检索非常不方便

  3. 用户关联操作复杂

如果使用数据库表

   会有如下问题:

  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层

 

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层

  
 
 
  private static final String SIGN_KEY_PREFIX = "user_sign:";
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);
  }

 

 

 

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

以用户为维度

特点

  1. 用户独立:每个用户都有自己的签到记录,不受其他用户的影响。
  2. 个性化统计:可以精确地统计每个用户的连续登录天数。
  3. 存储结构:使用一个键来存储一个用户的所有签到状态,使用位图(bitmaps)来表示每一天的签到状态。

实现方式

  • 键的设计:使用 sign:<loginId> 这样的格式来构建键,其中 <loginId> 是用户的唯一标识符。
  • 签到状态:使用位图中的一个位来表示每一天的签到状态,例如第1天、第2天等。
  • 连续签到天数的计算:从今天的 dayOfYear 开始,向前检查每一天的签到状态,直到遇到没有签到的一天。

适用场景

  • 当需要针对每个用户进行个性化的签到统计时。
  • 当需要跟踪每个用户的连续签到天数,以便为用户提供个性化的奖励或激励时。
  • 当用户基数较大且需要高效存储和查询时。

以时间为维度

特点

  1. 时间序列:关注的是某个时间段内所有用户的签到情况,而不是特定用户。
  2. 集体统计:可以统计某一天所有用户的签到情况,或者一段时间内所有用户的签到趋势。
  3. 存储结构:使用多个键来存储不同日期的签到状态,使用一个键来存储2024年8月21日所有用户的签到状态。

实现方式

  • 键的设计:使用 sign:<date> 这样的格式来构建键,其中 <date> 是日期,例如 sign:2024-08-21
  • 签到状态:使用一个集合(如哈希表或集合)来存储这一天内所有签到的用户ID。
  • 连续签到天数的计算:需要跟踪每个用户的历史签到记录,可能需要额外的键或数据结构来维护每个用户的历史签到状态。

适用场景

  • 当需要统计整个平台或特定时间段内所有用户的签到趋势时。
  • 当需要分析签到活动的整体表现时,例如了解哪一天的签到率最高。
  • 当需要对签到活动进行营销分析时,例如分析哪些促销活动最能提高签到率。

总结

  • 以用户为维度 更适合个性化需求,例如为用户提供连续签到奖励,跟踪每个用户的签到历史。
  • 以时间为维度 更适合整体统计需求,例如分析整个平台的签到趋势,评估营销活动的效果。

 

posted @   橘子味芬达水  阅读(280)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示