SpringBoot + Redis的Bitmap实现活跃用户统计

前言

Redis的Bitmap数据结构是一种紧凑的位图,它可以用于实现各种场景,其中统计活跃用户是一种经典的业务场景。

实现原理是,通过将每个用户表示为一个位,从而跟踪用户的活跃状态,使用位图记录用户每天是否登录,并计算月度或年度活跃用户数。

案例代码

以下是一个小例子,可以看到,使用SpringDataRedis,可以很轻松的实现BitMap的操作。

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.BitSet;

@Service
public class UserLoginService {

    // 用户登录记录的键前缀
    private static final String LOGIN_KEY_PREFIX = "login:"; 
    // 月份格式化器
    private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyyMM"); 
    // 年份格式化器
    private static final DateTimeFormatter YEAR_FORMATTER = DateTimeFormatter.ofPattern("yyyy"); 

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 记录用户登录
     *
     * @param userId 用户ID
     */
    public void recordLogin(String userId) {

        // 获取存储当天用户登录信息的键
        String loginKey = getLoginKey(); 
        // 计算位图偏移量,对应用户ID的哈希码
        int bitOffset = getUserIdHashCode(userId); 
        // 将用户ID对应的位设置为1,表示用户登录
        redisTemplate.opsForValue().setBit(loginKey, bitOffset, true); 
    }

    /**
     * 获取月度活跃用户统计数据
     *
     * @return 月度活跃用户统计数据
     */
    public BitSet getMonthlyActiveUsers() {

        // 获取存储月度活跃用户信息的键
        String monthlyKey = getMonthlyKey(); 
        // 从Redis中获取位图数据
        return retrieveBitSet(monthlyKey); 
    }

    /**
     * 获取年度活跃用户统计数据
     *
     * @return 年度活跃用户统计数据
     */
    public BitSet getYearlyActiveUsers() {

        // 获取存储年度活跃用户信息的键
        String yearlyKey = getYearlyKey(); 
        // 从Redis中获取位图数据
        return retrieveBitSet(yearlyKey); 
    }

    /**
     * 获取存储当天用户登录信息的键
     */
    private String getLoginKey() {

        LocalDate today = LocalDate.now();
        String dateKey = today.format(DateTimeFormatter.ISO_DATE);
        return LOGIN_KEY_PREFIX + dateKey;
    }

    /**
     * 计算用户ID的哈希码,保证非负数并适应位图长度
     */
    private int getUserIdHashCode(String userId) {

        int hashCode = userId.hashCode();
        // 1073741823是Redis位图最大支持长度(2^30-1),可根据实际需求调整
        return Math.abs(hashCode % 1073741823); 
    }

    /**
     * 获取存储月度活跃用户信息的键
     */
    private String getMonthlyKey() {

        LocalDate today = LocalDate.now();
        String monthKey = today.format(MONTH_FORMATTER);
        return LOGIN_KEY_PREFIX + "monthly:" + monthKey;
    }

    /**
     * 获取存储年度活跃用户信息的键
     */
    private String getYearlyKey() {

        LocalDate today = LocalDate.now();
        String yearKey = today.format(YEAR_FORMATTER);
        return LOGIN_KEY_PREFIX + "yearly:" + yearKey;
    }

    /**
     * 从Redis中获取位图数据
     */
    private BitSet retrieveBitSet(String key) {

        // 获取存储在Redis中的位图数据
        byte[] bytes = (byte[]) redisTemplate.opsForValue().get(key); 
        if (bytes != null) {
            // 将字节数组转换为BitSet
            return BitSet.valueOf(bytes); 
        } else {
            // 若不存在,则返回空的BitSet
            return new BitSet(); 
        }
    }
}

其中有几个地方解释一下:

1、recordLogin方法用于记录用户登录情况。每天的登录情况被保存在以"login:日期"为键的位图中,用户的登录状态由位图中对应的位表示;

2、countMonthlyActiveUsers方法用于计算月度活跃用户数量。每个月的活跃用户数保存在以"login:monthly:年月"为键的位图中,通过Redis的bitCount方法统计位图中置为1的位数,即月度活跃用户数;

3、ountYearlyActiveUsers方法用于计算年度活跃用户数量,原理同上,只是统计的键变为"login:yearly:年份";

4、getLoginKey、getUserIdHashCode、getMonthlyKey和getYearlyKey是辅助方法,负责生成对应的Redis键或计算用户ID的哈希码。

转换

上面的例子最终返回的是BitSet对象,通过这个对象我们经过转换后可以获取到许多经典的统计数据,这里列举一些经典常用的统计结果示例。

大家可以根据这里面列举的统计数据,针对上面的代码进行替换,得到自己想要的结果。

java 复制代码
import java.util.BitSet;

// 获取月度活跃用户统计数据
BitSet monthlyActiveUsers = userLoginService.getMonthlyActiveUsers();

// 获取年度活跃用户统计数据
BitSet yearlyActiveUsers = userLoginService.getYearlyActiveUsers();

// 统计月度活跃用户数量
int monthlyActiveUserCount = monthlyActiveUsers.cardinality();

// 统计年度活跃用户数量
int yearlyActiveUserCount = yearlyActiveUsers.cardinality();

// 判断某个用户是否为月度活跃用户
String userId = "user123";
boolean isMonthlyActiveUser = monthlyActiveUsers.get(userLoginService.getUserIdHashCode(userId));

// 判断某个用户是否为年度活跃用户
boolean isYearlyActiveUser = yearlyActiveUsers.get(userLoginService.getUserIdHashCode(userId));

// 输出统计结果
System.out.println("月度活跃用户数量: " + monthlyActiveUserCount);
System.out.println("年度活跃用户数量: " + yearlyActiveUserCount);
System.out.println("用户user123是否为月度活跃用户: " + isMonthlyActiveUser);
System.out.println("用户user123是否为年度活跃用户: " + isYearlyActiveUser);

总结

Redis的Bitmap数据结构非常灵活,可以根据具体需求实现各种位操作,但平时在项目中很多人没有机会使用到,这个案例非常简单,希望能让大家对此有个认识,未来用到了不会感到陌生。

相关推荐
一只叫煤球的猫1 小时前
ThreadForge v1.1.0 发布:让 Java 并发更接近 Go 的开发体验
java·后端·性能优化
014.2 小时前
2025最新jenkins保姆级教程!!!
java·运维·spring boot·spring·jenkins
亓才孓4 小时前
【MyBatis Exception】Public Key Retrieval is not allowed
java·数据库·spring boot·mybatis
万象.4 小时前
redis缓存和分布式锁
redis·分布式·缓存
万象.4 小时前
redis集群算法,搭建,故障处理及扩容
redis·算法·哈希算法
白太岁5 小时前
Redis:(2) hiredis 使用、C++ 封装与连接池
c语言·c++·redis·缓存
w***71105 小时前
常见的 Spring 项目目录结构
java·后端·spring
野犬寒鸦6 小时前
深入解析HashMap核心机制(底层数据结构及扩容机制详解剖析)
java·服务器·开发语言·数据库·后端·面试
阿在在9 小时前
Spring 系列(三):Spring PostProcessor 顶级扩展接口全解析
java·后端·spring
专注VB编程开发20年9 小时前
c# vb.net Redis 左侧添加,右侧添加(添加到头部,添加到尾部)
redis·c#·.net