使用redis( RedisTemplate )中的BitMap 记录用户签到情况

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 使用redis( RedisTemplate )中的BitMap 记录用户签到情况
  • Redis的Bitmaps这个“数据结构”可以实现对位的操作。Bitmaps本身不是一种数据结构,实际上就是字符串,但是它可以对字符串的位进行操作
  • 可把Bitmaps想象成一个以位为单位数组,数组中的每个单元只能存0或者1,数组的下标在bitmaps中叫做偏移量
  • 单个bitmaps的最大长度是512MB,即2^32个比特位

bitmaps的最大优势是节省存储空间。比如在一个以自增id代表不同用户的系统中,我们只需要512MB空间就可以记录40亿用户的某个单一信息,相比mysql节省了大量的空间

  • 有两种类型的位操作:一类是对特定bit位的操作,比如设置/获取某个特定比特位的值。另一类是批量bit位操作,例如在给定范围内统计为1的比特位个数
  • 代码Demo

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.util.ArrayList;
import java.util.List;

/**
 * 使用redis( RedisTemplate )中的bitMap 记录用户签到情况
 */
@SpringBootTest
public class RedisTemplateSignDemo {


    @Resource
    RedisTemplate redisTemplate;


    @Test
    public void mainTest() {
        Long initId = 10001234L;
        LocalDate today = LocalDate.now();
        DateTimeFormatter yyyyMMDft = DateTimeFormatter.ofPattern("yyyyMMdd");
        String dateMonthIndexStr = today.format(yyyyMMDft);
        String memberSignCacheKey = "member_sign:" + dateMonthIndexStr.substring(0, dateMonthIndexStr.length() - 2) + ":" + initId;
//        List<String> signInfoList = getSignInfo(memberSignCacheKey, today);
//        System.out.println("本月该ID签到情况:"+ signInfoList.toString());
//        System.out.println("是否签到成功" + !doSign(memberSignCacheKey, today));
        System.out.println("用户:" + initId + "今天" + (checkSign(memberSignCacheKey, today) ? "已经" : "还未") + "签到");
        System.out.println("用户:" + initId + "当前签到了" + getSignCount(memberSignCacheKey) + "次");
        System.out.println("用户:" + initId + "本月连续签到了" + getContinuousSignCount( memberSignCacheKey,  today) + "天");
        System.out.println("用户:" + initId + "本月第一次签到日期为 " + getFirstSignDate( memberSignCacheKey,  today));
    }


    /**
     * 查询当天是否有签到
     *
     * @param cacheKey
     * @param localDate
     * @return
     */
    public boolean checkSign(String cacheKey, LocalDate localDate) {
        return redisTemplate.opsForValue().getBit(cacheKey, localDate.getDayOfMonth() - 1);
    }

    /**
     * 获取用户签到次数
     */
    public long getSignCount(String cacheKey) {
        Long bitCount = (Long) redisTemplate.execute((RedisCallback) cbk -> cbk.bitCount(cacheKey.getBytes()));
        return bitCount;
    }

    /**
     * 获取本月签到信息
     * @param cacheKey
     * @param localDate
     * @return
     */
    @Test
    public List<String> getSignInfo(String cacheKey, LocalDate localDate) {
        List<String> resultList = new ArrayList<>();
        int lengthOfMonth = localDate.lengthOfMonth();
        List<Long> bitFieldList = (List<Long>) redisTemplate.execute((RedisCallback<List<Long>>) cbk
                -> cbk.bitField(cacheKey.getBytes(), BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(lengthOfMonth)).valueAt(0)));
        if (bitFieldList != null && bitFieldList.size() > 0) {
            long valueDec = bitFieldList.get(0) != null ? bitFieldList.get(0) : 0;
//            System.out.println("valueDec" + valueDec);
            for (int i = lengthOfMonth; i > 0; i--) {
                LocalDate tempDayOfMonth = LocalDate.now().withDayOfMonth(i);
                if (valueDec >> 1 << 1 != valueDec) {
                    resultList.add(tempDayOfMonth.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
                }
                valueDec >>= 1;
            }
        }
        return resultList;
    }


    /**
     * 签到操作
     */
    @Test
    public boolean doSign(String cacheKey, LocalDate localDate) {
        if (localDate == null) {
            localDate = LocalDate.now();
        }
        //localDate 是 本月的第 dayOfMonth 天
        int currOffset = localDate.get(ChronoField.DAY_OF_MONTH) - 1;
        return redisTemplate.opsForValue().setBit(cacheKey, currOffset, true);
    }


    /**
     * 获取当月连续签到次数
     * @param cacheKey
     * @param localDate
     * @return
     */
    public long getContinuousSignCount(String cacheKey, LocalDate localDate) {
        long signCount = 0;
        List<Long> list = redisTemplate.opsForValue().bitField(cacheKey, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType
                .unsigned(localDate.getDayOfMonth())).valueAt(0));
        if(list != null && list.size() > 0){
            long valueDec = list.get(0) != null ? list.get(0) : 0 ;
//            System.out.println("valueDec..." + valueDec);
            for(int i = 0; i< localDate.getDayOfMonth() ; i++){
                if(valueDec >> 1 << 1 == valueDec){
                    if(i > 0){ break; }
                }else{
                    signCount += 1;
                }
                valueDec >>= 1;
            }
        }
        return signCount;
    }

    /**
     * 获取当月首次签到日期
     */
    public LocalDate getFirstSignDate(String cacheKey, LocalDate localDate) {
            long bitPosition = (Long) redisTemplate.execute((RedisCallback) cbk -> cbk.bitPos(cacheKey.getBytes(), true));
            return bitPosition < 0 ? null : localDate.withDayOfMonth((int) bitPosition + 1);
        }
}

参考资料 & 致谢

[1] 基于Redis位图实现用户签到功能

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
1月前
|
存储 NoSQL PHP
如何用Redis高效实现点赞功能?用Set?还是Bitmap?
在众多软件应用中,点赞功能几乎成为标配。本文从实际需求出发,探讨如何利用 Redis 的 `Set` 和 `Bitmap` 数据结构设计高效点赞系统,分析其优缺点,并提供 PHP 实现示例。通过对比两种方案,帮助开发者选择最适合的存储方式。
28 3
|
3月前
|
JSON NoSQL Java
redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)
这篇文章介绍了在Java中使用Redis客户端的几种方法,包括Jedis、SpringDataRedis和SpringBoot整合Redis的操作。文章详细解释了Jedis的基本使用步骤,Jedis连接池的创建和使用,以及在SpringBoot项目中如何配置和使用RedisTemplate和StringRedisTemplate。此外,还探讨了RedisTemplate序列化的两种实践方案,包括默认的JDK序列化和自定义的JSON序列化,以及StringRedisTemplate的使用,它要求键和值都必须是String类型。
redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)
|
6月前
|
存储 NoSQL 算法
Redis系列学习文章分享---第十篇(Redis快速入门之附近商铺+用户签到+UV统计)
Redis系列学习文章分享---第十篇(Redis快速入门之附近商铺+用户签到+UV统计)
44 0
|
2月前
|
消息中间件 分布式计算 NoSQL
大数据-41 Redis 类型集合(2) bitmap位操作 geohash空间计算 stream持久化消息队列 Z阶曲线 Base32编码
大数据-41 Redis 类型集合(2) bitmap位操作 geohash空间计算 stream持久化消息队列 Z阶曲线 Base32编码
27 2
|
5月前
|
存储 NoSQL Redis
Redis 中bitMap使用及实现访问量
Redis 中bitMap使用及实现访问量
156 3
|
5月前
|
存储 NoSQL Java
Java中使用redis的bitMap实现签到功能
这个实现示例提供了一种灵活、高效的方式,展示了如何使用Redis来解决现实中的问题。
309 2
|
6月前
|
监控 NoSQL Java
在 Spring Boot 中实现 Redis 的发布/订阅功能可以通过 RedisTemplate 和消息监听器来完成
在 Spring Boot 中实现 Redis 的发布/订阅功能可以通过 RedisTemplate 和消息监听器来完成
310 1
|
5月前
|
存储 NoSQL 数据管理
如何借助Redis巧妙的管理用户签到?——Bitmap篇
Redis位操作用于高效存储分析,如用户签到。通过位操作,每个用户签到只需1位,节省空间。使用`setbit`设置签到状态,`getbit`查询,`bitcount`统计签到天数。适用于用户特征标记、系统功能开关和在线状态追踪。高效率、低空间占用,适合大数据场景。
80 0
|
6月前
|
存储 NoSQL Redis
蓝易云 - Redis之bitmap类型解读
需要注意的是,虽然bitmap可以高效地存储和计算大量的位,但是它也有一些局限性,例如,它不能直接获取或设置某一范围内的所有位,也不能直接获取或设置多个不连续的位。
28 2
|
6月前
|
存储 监控 NoSQL
Redis系列学习文章分享---第十二篇(搭建哨兵集群+RedisTemplate连接哨兵+搭建分片集群+-散列插槽+集群伸缩 +故障转移+RedisTemplate访问分片集群)
Redis系列学习文章分享---第十二篇(搭建哨兵集群+RedisTemplate连接哨兵+搭建分片集群+-散列插槽+集群伸缩 +故障转移+RedisTemplate访问分片集群)
262 0