【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 就是下边这个依赖了,用其他的依赖也可,此处只做参考:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

2.2 添加配置

① application.yml

配置类写上 redis 地址:

spring:
  redis:
    database: 0
    host: 172.0.0.1
    port: 6379
    password: xxxx

② FastJsonRedisSerializer

序列化配置类:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

//Redis相关配置,Redis使用FastJson序列化
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {

    public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    private final Class<T> clazz;

    static {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    }

    public FastJsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz);
    }


    protected JavaType getJavaType(Class<?> clazz) {
        return TypeFactory.defaultInstance().constructType(clazz);
    }
}

2.3 工具类实现位图操作

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

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;

    /**
     * 缓存基本的对象,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));
    }
}

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

3 日活周活月活实践

3.1 日活

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

以下是测试类实现:

@SpringBootTest(classes = Application.class)
@RunWith(SpringRunner.class)
public class test {

    @Autowired
    private RedisCache redisCache;

    @Test
    public void testDayActive(){
        // 设置日活数据,模拟用户访问后台
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
        String todayStr = dateFormat.format(new Date());//比如20230923
        redisCache.setBit(todayStr, 1, true);
        redisCache.setBit(todayStr, 5, true);

        // 统计当天日活数据,只会提取 true 的数量
        Long todayCount = redisCache.bitCount(todayStr);
        System.out.println(todayStr + "该天日活数据为:" + todayCount);
    }

}

控制台输出👇

如此,日活就可实现了~

3.2 周活月活

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

    @Test
    public void testDayActive(){
        // 1.假设每天的数据已通过定时任务成功保存至redis

        // 2.获取这周的所有日期,如:[20230918, 20230919, 20230920, 20230921, 20230922, 20230923, 20230924]
        List<String> dateStrs = getWeekDay();

        long weekCount = 0L;
        for (String dateStr : dateStrs) {
            Long todayCount = redisCache.bitCount(dateStr);
            weekCount = weekCount + todayCount;
        }

        System.out.println("当前周日活数据为:" + weekCount);
    }


    public static List<String> getWeekDay() {
        Calendar calendar = Calendar.getInstance();
        while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
            calendar.add(Calendar.DAY_OF_WEEK, -1);
        }
        List<Date> dates = new ArrayList<>(7);
        for (int i = 0; i < 7; i++) {  // i < 7 星期日
            dates.add(i, calendar.getTime());
            calendar.add(Calendar.DATE, 1);
        }

        List<String> dateStrs = new ArrayList<>();
        dates.forEach(date -> {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
            dateStrs.add(dateFormat.format(date));
        });

        return dateStrs;
    }
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
可以使用Java的Jedis库来实现Redis的交互,从而实现考勤功能。 首先,你需要在Java项目中引入Jedis库的依赖。可以在Maven或Gradle项目中的配置文件中添加以下依赖: Maven: ```xml <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.6.0</version> </dependency> ``` Gradle: ```groovy implementation 'redis.clients:jedis:3.6.0' ``` 接下来,你需要在Java代码中连接Redis服务器。假设你的Redis服务器地址是`localhost`,端口是`6379`,可以使用以下代码进行连接: ```java import redis.clients.jedis.Jedis; public class AttendanceSystem { public static void main(String[] args) { // 连接Redis服务器 Jedis jedis = new Jedis("localhost", 6379); // 进行考勤操作 String userId = "your_user_id"; String date = "2022-01-01"; markAttendance(jedis, userId, date); // 关闭与Redis的连接 jedis.close(); } public static void markAttendance(Jedis jedis, String userId, String date) { // 使用Redis的SET数据结构记录考勤信息 jedis.sadd("attendance:" + date, userId); } } ``` 在上述代码中,我们通过`Jedis`类与Redis服务器建立连接,并调用`markAttendance`方法来进行考勤操作。`markAttendance`方法使用Redis的SET数据结构,将用户ID添加到指定日期的考勤集合中,表示该用户已经进行了考勤。 这只是一个简单的示例,你可以根据实际需求进行扩展和优化。例如,你可以使用Redis的Sorted Set数据结构记录考勤时间、使用Hash数据结构存储更多考勤信息等。 请注意,以上代码仅为示例,并未处理异常、错误处理等情况。在实际开发中,你需要根据需求进行完善。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尹煜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值