• 【Java开发】Redis位图实现统计日活周活月活


    最近研究了使用 Redis 的位图功能统计日活周活等数据,特来和大家分享下,Redis 位图还可用于记录用户签到情况、判断某个元素是否存在于集合中等。

    1 Redis 位图介绍

    Redis 位图是一种特殊的数据结构,它由一系列位组成,每个位只能是0或1。在 Redis 中,位图可以用来存储和操作二进制数据。位图提供了一些特殊的命令,使得我们可以对位进行操作,如设置、清除、计数和查询等。

    Redis位图的底层实现采用了稀疏数据结构,这意味着当位图中大部分位都是0时,Redis 只会占用很少的内存空间。这使得位图在处理大规模数据时非常高效。

    简单来说,Redis 位图使用二进制减少了统计数据存储的内存,使用大用户规模的情况,理论层面就不多说了,接下来直接讲应用层面吧~

    2 RedisTemplate 位图技术实现

    Redis 位图操作有多种实现方式,比如 JedisRedisTemplateStringRedisTemplate 等,本文主要介绍 RedisTemplate 方式,特别简单易实现,StringRedisTemplate 其实和 RedisTemplate 技术实现一致,而 Jedis 比较麻烦了。

    简单介绍一下 RedisTemplate,作为 Spring Data Redis 提供的 Redis 客户端工具。它封装了 Redis 的操作流程和异常处理流程,使得 Redis 操作更加简单方便,同时也提供了 Redis 常用数据结构的操作方式。RedisTemplate 主要提供了对 String、List、Set、ZSet、Hash 数据结构的操作,支持序列化和反序列化的方式存储数据,同时支持事务操作,具有高并发性能,是开发人员使用 Redis 必不可少的组件之一。

    2.1 redis 依赖

    除了 spring-boot 就是下边这个依赖了,用其他的依赖也可,此处只做参考:

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-data-redis-reactiveartifactId>
    4. dependency>

    2.2 添加配置

    ① application.yml

    配置类写上 redis 地址:

    1. spring:
    2. redis:
    3. database: 0
    4. host: 172.0.0.1
    5. port: 6379
    6. password: xxxx

    ② FastJsonRedisSerializer

    序列化配置类:

    1. import com.alibaba.fastjson.JSON;
    2. import com.alibaba.fastjson.parser.ParserConfig;
    3. import com.alibaba.fastjson.serializer.SerializerFeature;
    4. import com.fasterxml.jackson.databind.JavaType;
    5. import com.fasterxml.jackson.databind.type.TypeFactory;
    6. import org.springframework.data.redis.serializer.RedisSerializer;
    7. import org.springframework.data.redis.serializer.SerializationException;
    8. import java.nio.charset.Charset;
    9. import java.nio.charset.StandardCharsets;
    10. //Redis相关配置,Redis使用FastJson序列化
    11. public class FastJsonRedisSerializer implements RedisSerializer {
    12. public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    13. private final Class clazz;
    14. static {
    15. ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    16. }
    17. public FastJsonRedisSerializer(Class clazz) {
    18. super();
    19. this.clazz = clazz;
    20. }
    21. @Override
    22. public byte[] serialize(T t) throws SerializationException {
    23. if (t == null) {
    24. return new byte[0];
    25. }
    26. return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    27. }
    28. @Override
    29. public T deserialize(byte[] bytes) throws SerializationException {
    30. if (bytes == null || bytes.length <= 0) {
    31. return null;
    32. }
    33. String str = new String(bytes, DEFAULT_CHARSET);
    34. return JSON.parseObject(str, clazz);
    35. }
    36. protected JavaType getJavaType(Class clazz) {
    37. return TypeFactory.defaultInstance().constructType(clazz);
    38. }
    39. }

    2.3 工具类实现位图操作

    如下代码,省略了和位图没什么关系的方法,大家可任意使用以下方法:

    1. import org.springframework.beans.factory.annotation.Autowired;
    2. import org.springframework.data.redis.core.RedisCallback;
    3. import org.springframework.data.redis.core.RedisTemplate;
    4. import org.springframework.data.redis.core.ValueOperations;
    5. import org.springframework.stereotype.Component;
    6. @Component
    7. @SuppressWarnings(value = { "unchecked", "rawtypes" })
    8. public class RedisCache {
    9. @Autowired
    10. public RedisTemplate redisTemplate;
    11. /**
    12. * 缓存基本的对象,Integer、String、实体类等
    13. *
    14. * @param key 缓存的键值
    15. * @param value 缓存的值
    16. */
    17. public void setCacheObject(final String key, final T value) {
    18. redisTemplate.opsForValue().set(key, value);
    19. }
    20. /**
    21. * 获得缓存的基本对象。
    22. *
    23. * @param key 缓存键值
    24. * @return 缓存键值对应的数据
    25. */
    26. public T getCacheObject(final String key) {
    27. ValueOperations operation = redisTemplate.opsForValue();
    28. return operation.get(key);
    29. }
    30. /**
    31. * 设置位图数据
    32. * @param key 键
    33. * @param id
    34. * @param bool
    35. */
    36. public Boolean setBit(String key, long id, boolean bool){
    37. return redisTemplate.opsForValue().setBit(key, id, bool);
    38. }
    39. /**
    40. * 返回位图数据
    41. * @param key 键
    42. * @param id
    43. */
    44. public Boolean getBit(String key, long id){
    45. return redisTemplate.opsForValue().getBit(key, id);
    46. }
    47. /**
    48. * bitCount 统计值对应位为1的数量
    49. * @param key redis key
    50. */
    51. public Long bitCount(String key) {
    52. return (Long) redisTemplate.execute((RedisCallback) con -> con.bitCount(key.getBytes()));
    53. }
    54. /**
    55. * bitCount 统计值指定范围(范围为字节范围)对应位为1的数量
    56. * @param key redis key
    57. * @param start 开始字节位置(包含)
    58. * @param end 结束字节位置(包含)
    59. */
    60. public Long bitCount(String key, long start, long end) {
    61. return (Long) redisTemplate.execute((RedisCallback) con -> con.bitCount(key.getBytes(), start, end));
    62. }
    63. }

     关于 redis 更多操作可参考:Docker 环境下安装 Redis 并连接 Spring 项目实现简单 CRUD

    3 日活周活月活实践

    3.1 日活

    实现思路也是蛮简单的,第一点是确定 key,比如 20230923,那这就是该天的 key,第二点是确定 id,该 id 可以取自用户表的主键,也可用可唯一指定用户的数据替代。

    以下是测试类实现:

    1. @SpringBootTest(classes = Application.class)
    2. @RunWith(SpringRunner.class)
    3. public class test {
    4. @Autowired
    5. private RedisCache redisCache;
    6. @Test
    7. public void testDayActive(){
    8. // 设置日活数据,模拟用户访问后台
    9. SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
    10. String todayStr = dateFormat.format(new Date());//比如20230923
    11. redisCache.setBit(todayStr, 1, true);
    12. redisCache.setBit(todayStr, 5, true);
    13. // 统计当天日活数据,只会提取 true 的数量
    14. Long todayCount = redisCache.bitCount(todayStr);
    15. System.out.println(todayStr + "该天日活数据为:" + todayCount);
    16. }
    17. }

    控制台输出👇

    如此,日活就可实现了~

    3.2 周活月活

    思路就是日活的 for 循环累加,月活也可如此~

    1. @Test
    2. public void testDayActive(){
    3. // 1.假设每天的数据已通过定时任务成功保存至redis
    4. // 2.获取这周的所有日期,如:[20230918, 20230919, 20230920, 20230921, 20230922, 20230923, 20230924]
    5. List dateStrs = getWeekDay();
    6. long weekCount = 0L;
    7. for (String dateStr : dateStrs) {
    8. Long todayCount = redisCache.bitCount(dateStr);
    9. weekCount = weekCount + todayCount;
    10. }
    11. System.out.println("当前周日活数据为:" + weekCount);
    12. }
    13. public static List getWeekDay() {
    14. Calendar calendar = Calendar.getInstance();
    15. while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
    16. calendar.add(Calendar.DAY_OF_WEEK, -1);
    17. }
    18. List dates = new ArrayList<>(7);
    19. for (int i = 0; i < 7; i++) { // i < 7 星期日
    20. dates.add(i, calendar.getTime());
    21. calendar.add(Calendar.DATE, 1);
    22. }
    23. List dateStrs = new ArrayList<>();
    24. dates.forEach(date -> {
    25. SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
    26. dateStrs.add(dateFormat.format(date));
    27. });
    28. return dateStrs;
    29. }
  • 相关阅读:
    ES Query DSL-复合查询和关联查询
    每个程序员必须掌握的常用英语词汇
    Linux用户组操作
    MySQL_生产环境中concat用法及功能实现
    Games104现代游戏引擎入门-lecture20 现代游戏引擎架构:面向数据编程与任务系统
    Vue3+typescript项目使用script-setup写法时,模版中的变量和方法编辑器检测都为Unresolved variable或者Element is not exported,如何解决
    虹科示波器 | 汽车免拆检修 | 2010款奥迪A5车怠速时发动机偶尔自动熄火
    ADSP-21489的开发详解:VDSP+自己编程写代码开发(8-延时算法)
    NgRx 中如何进行状态管理?(含示例)
    王道p18 第12题假设 A中的 n个元素保存在一个一维数组中,请设计一个尽可能高效的算法,找出A的主元素。若存在主元素,则输出该元素:否则输出-1
  • 原文地址:https://blog.csdn.net/weixin_51407397/article/details/133195486