目录

1 实现思路

2 统计用户三日内留存数据

2.1  工具类

2.2  实践


1 实现思路

前提:已将用户访问后台的数据保存到 redis 中,流程如下图 👇

【Java开发】Redis位图轻松实现统计用户三日内留存数据-LMLPHP

思路Redis 位图实际上是由二进制位组成的数据结构,那么我们讲位图原始数据拿到后可以进行解析,从而获取到所有偏移量为 1 的用户 id 的集合,依次类推,前天、昨天和今天的访问后台的用户 id 的集合都能拿到手,最后取交集即可~

如此,不仅能够得到连续三天登录用户的数量,还能拿到这些用户的 id

2 统计用户三日内留存数据

依旧还是使用 RedisTemplate 来作为客户端工具!!

一些前置操作请点击该链接:Redis位图实现统计日活周活月活

2.1  工具类

相对于前一篇文章,新增了 listBoolTrue 静态方法,该方法就是用来返回某天登录用户 id 的集合。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
 
@Component
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public class RedisCache {
 
    @Autowired
    public RedisTemplate redisTemplate;


    /**
     * bitCount 获取所有值为1的偏移量--本文章用到的方法
     * @param key redis key
     */
    public List<Integer> listBoolTrue(String key) {
        List<Integer> result = new ArrayList<>();
        redisTemplate.execute((RedisCallback<Long>) con -> {
            byte[] bitmap = con.get(key.getBytes());
            if (bitmap != null){
                for (int offset = 0; offset < bitmap.length * 8; offset++) {
                    if ((bitmap[offset / 8] & (0x80 >> (offset % 8))) != 0) {
                        result.add(offset);
                    }
                }
            }
            return null;
        });
        return result;
    }

 
    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value) {
        redisTemplate.opsForValue().set(key, value);
    }
 
 
    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key) {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }
 
 
    /**
     * 设置位图数据
     * @param key 键
     * @param id
     * @param bool
     */
    public Boolean setBit(String key, long id, boolean bool){
        return redisTemplate.opsForValue().setBit(key, id, bool);
    }
 
 
    /**
     * 返回位图数据
     * @param key 键
     * @param id
     */
    public Boolean getBit(String key, long id){
        return redisTemplate.opsForValue().getBit(key, id);
    }
 
 
    /**
     * bitCount 统计值对应位为1的数量
     * @param key redis key
     */
    public Long bitCount(String key) {
        return (Long) redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes()));
    }
 
 
    /**
     * bitCount 统计值指定范围(范围为字节范围)对应位为1的数量
     * @param key redis key
     * @param start 开始字节位置(包含)
     * @param end 结束字节位置(包含)
     */
    public Long bitCount(String key, long start, long end) {
        return (Long) redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes(), start, end));
    }
}

2.2  实践

思路还是蛮简单的,key 就是之前设置的,我这儿举个例子,最终返回的是取交集后的用户 id 的集合,如果只是统计数量,那么加个 size 就行了~

    @Test
    public void testRetainedUser(){
        // 获取三天登录用户id的集合
        List<Integer> todayUserIds = redisCache.listBoolTrue("20230930");
        List<Integer> yesterdayUserIds = redisCache.listBoolTrue("20231001");
        List<Integer> beforeYesterdayUserIds = redisCache.listBoolTrue("20231002");

        // 取交集
        todayUserIds.retainAll(yesterdayUserIds);
        todayUserIds.retainAll(beforeYesterdayUserIds);

        // 返回取交集后的结果
        System.out.println(todayUserIds);
    }
10-02 17:03