使用 Redis BitMap 实现签到与查询历史签到以及签到统计功能(SpringBoot环境)

目录

    • 一、前言
    • 二、Redis BitMap 位图原理
      • 2.1、BitMap 能解决什么
      • 2.2、BitMap 存储空间计算
      • 2.3、BitMap 存在问题
    • 三、Redis BitMap 操作基本语法和原生实现签到
      • 3.1、基本语法
      • 3.2、Redis BitMap 实现签到操作指令
    • 四、SpringBoot 使用 Redis BitMap 实现签到与统计功能
      • 4.1、代码实现
      • 4.2、功能测试

一、前言

      签到是一个很常见的功能,如果使用数据库实现,那么用户一次签到,就是一条记录,假如有100万用户,平均每个用户每年签到次数为30次,则这张表一年的数据量为 3000 万条,一般签到记录字段不会太多一条数据按照30字节算,一年就是858.3MB左右,但是对于签到信息查询是比较频繁的,如查询当天是否签到、查询用户近7天签到记录、查询用户近30天签到记录、统计用户签到次数,如果这些查询都要去签到表查询那么数据库压力是非常大的,而且考虑到数据量会不断增长,这里使用Redis BitMap 实现高效的签到与统计。

二、Redis BitMap 位图原理

      BitMap 在 Redis 中并不是一个新的数据类型,其底层是 Redis 实现,Redis 的位图(BitMap)是由多个二进制位组成的数组,只有两种状态,0和1, 数组中的每个二进制位都有与之对应的偏移量(从 0 开始),通过这些偏移量可以对位图中指定的一个或多个二进制位进行操作,由于采用一个bit 来存储一个数据,因此可以大大的节省空间。

在这里插入图片描述

2.1、BitMap 能解决什么

  • BitMap 能解决很多问题,核心就是使用位数组节省存储空间,常见业务有用户签到、打卡、统计活跃用户、统计用户在线状态、实现布隆过滤器、数据去重、快速查找等。

  • BitMap是如何使用位数组节省存储空间的
    在20亿个随机整数中找出某个数m是否存在其中,并假设32位操作系统,4G内存。
    计算机分配给内存的最小单元是bit,在Java中,int占4字节,1字节=8位(1 byte = 8 bit)。
    如果每个数字用int存储,那就是20亿个int,因而占用的空间约为 (2000000000*4/1024/1024/1024)≈7.45G
    如果按位存储就不一样了,20亿个数就是20亿位,占用空间约为 (2000000000/8/1024/1024/1024)≈0.23G

2.2、BitMap 存储空间计算

  • 在 Redis 中是使用字符串类型存储的,Redis 中字符串的最大长度是 512M,所以 BitMap 的 offset (偏移量)最大值为:512 * 1024 * 1024 * 8 = 2^32,也就是说一个BitMap只能存储2^32个位,差不多4.29亿。
  • 还注意一个问题,如果我们只在一个 BitMap 偏移量为99的位置存放了一个数据,那么这个 BitMap 也是会占用100个位的内存的,0-98这些位都会被隐式地初始化为 0。

2.3、BitMap 存在问题

  • 数据碰撞。比如将字符串映射到 BitMap 的时候会有碰撞的问题,那就可以考虑用 Bloom Filter 来解决,Bloom Filter 使用多个 Hash 函数来减少冲突的概率。

  • 数据稀疏。又比如要存入(10,100000,10000000)这三个数据,我们需要建立一个 9999999 长度的 BitMap ,但是实际上只存了3个数据,这时候就有很大的空间浪费,碰到这种问题的话,可以通过引入 Roaring BitMap 来解决。

三、Redis BitMap 操作基本语法和原生实现签到

3.1、基本语法

# 设置指定偏移量上的位的值(0 或 1),语法:SETBIT key offset value
## 示例:给mykey 偏移量为9的位置设置值为1
SETBIT mykey 9 1# 获取指定偏移量上的位的值,语法:GETBIT key offset
## 示例:获取mykey 偏移量为9上的值
GETBIT mykey 9# 统计指定范围内所有位为1的数量 如果不指定范围则统计整个key,这个范围是以字节为单位的比如start设置成1其实代表8bit,对应偏移量是8开始,语法:BITCOUNT key [start end]
## 示例:获取mykey 所有所有位为1的数量
BITCOUNT mykey# 在指定范围内查找第一个被设置为 1 或 0 的位,语法:BITPOS key bit [start] [end]
## 示例:查找mykey中第一个被设置为 1 的位置
BITPOS mykey 1# 对位图的指定偏移量进行位级别的读写操作:语法:BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment]
## GET type offset 用于获取指定偏移量上的位,type 可以是 u<n>(无符号整数)或 i<n>(有符号整数),offset 是位图的偏移量。
## SET type offset value 用于设置指定偏移量上的位,type 是位的类型,offset 是位图的偏移量,value 是要设置的值。
## INCRBY type offset increment 用于递增或递减指定偏移量上的位,type 是位的类型,offset 是位图的偏移量,increment 是递增或递减的值。
## 示例:获取mykey 偏移量从 0 开始的4位无符号整数(u4 表示 4 位的无符号整数)
BITFIELD mykey GET u4 0# 对一个或多个位图执行指定的位运算操作(AND、OR、XOR、NOT),语法:BITOP operation destkey key [key ...]
## 示例:将key1和key1进行AND运算(对应位都为 1 时结果位为 1,否则为 0),将运算后的结果保存到新的key:destkey 
BITOP AND destkey key1 key2

3.2、Redis BitMap 实现签到操作指令

      这里模拟一个月签到,日期上选择2023年11月的1 3 5号这三天签到,因为偏移量是从0开始,所以对应偏移量就是0、2、4,其余日期不签到。

  • 1、添加用户签到位 key = USER_SIGN_IN:U0001:202311,其中U0001代表用户编号,202311代表对应年和月

    127.0.0.1:6379> SETBIT USER_SIGN_IN:U0001:202311 0 1
    (integer) 0
    127.0.0.1:6379> SETBIT USER_SIGN_IN:U0001:202311 2 1
    (integer) 0
    127.0.0.1:6379> SETBIT USER_SIGN_IN:U0001:202311 4 1
    (integer) 0
    
  • 2、查看用户指定日期是否有签到(查看当天是否有签到同理),这里查看5号是否有签到偏移量为4,返回1则代表有签到

    127.0.0.1:6379> GETBIT USER_SIGN_IN:U0001:202311 4
    (integer) 1
    
  • 3、查看用户2023年11月一共签到了几天

    127.0.0.1:6379> BITCOUNT USER_SIGN_IN:U0001:202311
    (integer) 3
    
  • 4、查看用户2023年11月那些日期签到了,11月一共有30天

    • 通过BITFIELD获取30位的无符号十进制整数,从偏移量0开始
    127.0.0.1:6379> BITFIELD USER_SIGN_IN:U0001:202311 GET u30 0
    1) (integer) 704643072
    
    • 将获取到的无符号十进制整数转换成二进制,这里可以看到从左到右二进制的第1 3 5位置值都是1,对应偏移量0 2 4,这里不是从右到左的,然后通过业务代码判断判断这个二进制对应位为1则代表有签到,具体代码会在下面做实现。
    # 十进制
    704643072
    # 二进制
    101010000000000000000000000000
    

四、SpringBoot 使用 Redis BitMap 实现签到与统计功能

      这里会使用SpringBoot环境RedisTemplate来操作Redis,需要集成文章可以查看,SpringBoot集成Lettuce客户端操作Redis:https://blog.csdn.net/weixin_44606481/article/details/133907103

我这里还会使用到hutool工具包操作时间解析,有需要可以引入。

<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.17</version>
</dependency>

4.1、代码实现

代码里注释比较完整这里就不做额外介绍了

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.time.YearMonth;
import java.util.*;/*** 签到业务*/
@Service
public class SignInService {@Autowiredprivate RedisTemplate<String, String> redisTemplate;private static String yyyy_MM_dd = "yyyy-MM-dd";private static String yyyy_MM = "yyyy-MM";/*** 用户签到* @param userNo 用户编号* @param date   日期 格式yyyy-MM-dd*/public boolean signIn(String userNo, String date) {// 获取缓存keyString cacheKey = getCacheKey(userNo, date);// 获取日期DateTime dateTime = DateUtil.parse(date, yyyy_MM_dd);int day = dateTime.dayOfMonth();// 设置给BitMap对应位标记 其中offset为0表示第一天所以要day-1Boolean result = redisTemplate.opsForValue().setBit(cacheKey, day - 1, true);// 如果响应true则代表之前已经签到,在Redis指令操作setbit 设置对应位为1的时候,如果之前是0或者不存在会响应0,如果为1则响应1if (result) {System.out.println("用户userNo=" + userNo + " date=" + date + "  已签到");}return result;}/*** 查看用户指定日期是否签到(查看当天是否有签到同理)* @param userNo* @param date   日期 格式yyyy-MM-dd*/public boolean isSignIn(String userNo, String date) {// 获取缓存keyString cacheKey = getCacheKey(userNo, date);// 获取日期DateTime dateTime = DateUtil.parse(date, yyyy_MM_dd);int day = dateTime.dayOfMonth();return redisTemplate.opsForValue().getBit(cacheKey, day - 1);}/*** 统计用户指定年月签到次数* @param userNo* @param date   格式yyyy-MM*/public Long getSignInCount(String userNo, String date) {// 获取缓存keyString cacheKey = getCacheKey(userNo, date);// 不知道是那个版本才有的下面这个方法,我的现在使用的spring-data-redis是2.3.9.RELEASE 是没有这个方法的,改用connection直接调用bitCount
//        Long count = redisTemplate.opsForValue().bitCount(key, start, end);Long count = redisTemplate.execute(connection -> connection.bitCount(cacheKey.getBytes()), true);return count;}/*** 获取用户指定年月签到列表,也可以通过这种方式获取用户月签到次数** @param userNo* @param date   格式yyyy-MM*/public List<Map> getSignInList(String userNo, String date) {// 获取缓存keyString cacheKey = getCacheKey(userNo, date);// 获取传入月份有多少天DateTime dateTime = DateUtil.parse(date, yyyy_MM);YearMonth yearMonth = YearMonth.of(dateTime.year(), dateTime.monthBaseOne());int days = yearMonth.lengthOfMonth();BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(days)).valueAt(0);// 获取位图的无符号十进制整数List<Long> list = redisTemplate.opsForValue().bitField(cacheKey, bitFieldSubCommands);if (list == null || list.isEmpty()) {return null;}// 获取位图的无符号十进制整数值long bitMapNum = list.get(0);// 进行位运算判断组装那些日期有签到List<Map> result = new ArrayList<>();for (int i = days; i > 0; i--) {Map<String, Object> map = new HashMap<>();map.put("day", i);//先 右移,然后在 左移,如果得到的结果仍然与本身相等,则 最低位是0 所以是未签到if (bitMapNum >> 1 << 1 == bitMapNum) {map.put("active", false);} else {//与本身不等,则最低位是1 表示已签到map.put("active", true);}result.add(map);// 将位图的无符号十进制整数右移一位,准备下一轮判断bitMapNum >>= 1;}Collections.reverse(result);return result;}/*** 获取缓存key*/private static String getCacheKey(String userNo, String date) {DateTime dateTime = DateUtil.parse(date, yyyy_MM);return String.format("USER_SIGN_IN:%s:%s", userNo, dateTime.year() + "" + dateTime.monthBaseOne());}
}

4.2、功能测试

import com.redisscene.service.SignInService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
import java.util.Map;/*** 签到功能测试*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class SignInTest {@Autowiredprivate SignInService signInService;/*** 测试用户签到*/@Testpublic void t1() {boolean b1 = signInService.signIn("U0001", "2023-11-01");boolean b2 = signInService.signIn("U0001", "2023-11-03");boolean b3 = signInService.signIn("U0001", "2023-11-05");boolean b4 = signInService.signIn("U0001", "2023-11-01");System.out.println("b1=" + b1 + " b2=" + b2 + " b3=" + b3 + " b4=" + b4);}/*** 测试查看用户指定日期是否签到(查看当天是否有签到同理)*/@Testpublic void t2() {boolean b1 = signInService.isSignIn("U0001", "2023-11-01");System.out.println(b1 ? "b1已签到" : "b1未签到");boolean b2 = signInService.isSignIn("U0001", "2023-11-06");System.out.println(b2 ? "b2已签到" : "b2未签到");}/*** 测试统计用户指定年月签到次数*/@Testpublic void t3() {Long count = signInService.getSignInCount("U0001", "2023-11");System.out.println("签到次数count=" + count);}/*** 测试获取用户指定年月签到列表,也可以通过这种方式获取用户月签到次数*/@Testpublic void t4() {List<Map> list = signInService.getSignInList("U0001", "2023-11");if (list != null && !list.isEmpty()) {list.forEach(item -> System.out.println(item));}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/201461.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端CSS实现响应式TimeLine效果(附源码)

文章目录 纯CSS搭建&#xff0c;先上效果图&#xff08;附有源码&#xff09;视图层 index.htmlindex.css 公用样式文件Main.css 主要的样式文件 纯CSS搭建&#xff0c;先上效果图&#xff08;附有源码&#xff09; 本效果为纯CSS搭建&#xff0c;适配移动端和PC端&#xff01…

MySQL 事务的底层原理和 MVCC(二)

7.2. undo 日志 7.2.1. 事务回滚的需求 我们说过事务需要保证原子性&#xff0c;也就是事务中的操作要么全部完成&#xff0c;要么什么也不做。但是偏偏有时候事务执行到一半会出现一些情况&#xff0c;比如&#xff1a; 情况一&#xff1a;事务执行过程中可能遇到各种错误&a…

力扣C++学习笔记——C++ 给vector去重

要使用std::set对std::vector进行去重操作&#xff0c;您可以将向量中的元素插入到集合中&#xff0c;因为std::set会自动去除重复元素。然后&#xff0c;您可以将集合中的元素重新存回向量中。以下是一个示例代码&#xff0c;演示如何使用std::set对std::vector进行去重&#…

数据结构学习笔记——多维数组、矩阵与广义表

目录 一、多维数组&#xff08;一&#xff09;数组的定义&#xff08;二&#xff09;二维数组&#xff08;三&#xff09;多维数组的存储&#xff08;四&#xff09;多维数组的下标的相关计算 二、矩阵&#xff08;一&#xff09;特殊矩阵和稀疏矩阵&#xff08;二&#xff09;…

opengl制作天空盒

首先创建顶点数组 unsigned int m_uiVaoBufferID; glGenVertexArrays(1, &m_uiVaoBufferID); 然后创建顶点缓冲区 float skyboxVertices[] {// positions-1.0f, 1.0f, -1.0f,-1.0f, -1.0f, -1.0f,1.0f, -1.0f, -1.0f,1.0f, -1.0f, -1.0f,1.0f, 1.0f, -1.0f,-1.0f, 1.…

基于晶体结构算法优化概率神经网络PNN的分类预测 - 附代码

基于晶体结构算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于晶体结构算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于晶体结构优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…

Android Studio 安装及使用

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

基于一致性算法的微电网分布式控制MATLAB仿真模型

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 本模型主要是基于一致性理论的自适应虚拟阻抗、二次电压补偿以及二次频率补偿&#xff0c;实现功率均分&#xff0c;保证电压以及频率稳定性。 一致性算法 分布式一致性控制主要分为两类&#xff1a;协调同…

深度学习之生成唐诗案例(Pytorch版)

主要思路&#xff1a; 对于唐诗生成来说&#xff0c;我们定义一个"S" 和 "E"作为开始和结束。 示例的唐诗大概有40000多首&#xff0c; 首先数据预处理&#xff0c;将唐诗加载到内存&#xff0c;生成对应的word2idx、idx2word、以及唐诗按顺序的字序列。…

基于材料生成算法优化概率神经网络PNN的分类预测 - 附代码

基于材料生成算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于材料生成算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于材料生成优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…

矩阵知识补充

正交矩阵 定义&#xff1a; 正交矩阵是一种满足 A T A E A^{T}AE ATAE的方阵 正交矩阵具有以下几个重要性质&#xff1a; A的逆等于A的转置&#xff0c;即 A − 1 A T A^{-1}A^{T} A−1AT**A的行列式的绝对值等于1&#xff0c;即 ∣ d e t ( A ) ∣ 1 |det(A)|1 ∣det(A)∣…

【开源】基于Vue.js的教学过程管理系统

项目编号&#xff1a; S 054 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S054&#xff0c;文末获取源码。} 项目编号&#xff1a;S054&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 教师端2.2 学生端2.3 微信小程序端2…

D. Absolute Beauty - 思维

题面 分析 补题。配上题解的图&#xff0c;理解了很长时间&#xff0c;思维还需要提高。 对于每一对 a i a_i ai​和 b i b_i bi​&#xff0c;可以看作一个线段的左右端点&#xff0c;这是关键的一步&#xff0c;那么他们的绝对值就是线段的长度&#xff0c;对于线段相对位…

Microsoft Visual Studio 2019下载及安装流程记录

第一周任务&#xff1a; 1.笔记本上安装vc2019的环境 2.再把OpenCV安装上 3.根据网上的教程&#xff0c;试着写几个opencv的程序 一、安装Visual Studio 2019社区版 首先先完成安装vc2019的环境&#xff0c; 因为&#xff1a; Microsoft Visual C是用于C编程的工具集合&am…

计算机毕业论文内容参考|基于深度学习的交通标识智能识别系统的设计与维护

文章目录 导文摘要前言绪论1课题背景2国内外现状与趋势3课题内容相关技术与方法介绍系统分析总结与展望导文 基于深度学习的交通标识智能识别系统是一种利用深度学习模型对交通标识进行识别和解析的系统。它可以帮助驾驶员更好地理解交通规则和安全提示,同时也可以提高道路交通…

【tomcat】java.lang.Exception: Socket bind failed: [730048

项目中一些旧工程运行情况处理 问题 1、启动端口占用 2、打印编码乱码 ʮһ&#xfffd;&#xfffd; 13, 2023 9:33:26 &#xfffd;&#xfffd;&#xfffd;&#xfffd; org.apache.coyote.AbstractProtocol init &#xfffd;&#xfffd;&#xfffd;&#xfffd;: Fa…

Active Directory 和域名系统(DNS)的相互关系

什么是域名系统&#xff08;DNS&#xff09; 域名系统&#xff08;DNS&#xff09;&#xff0c;从一般意义上讲是一种将主机名或域名解析为相应IP地址的手段。 在 AD 的中&#xff0c;DNS 服务维护 DNS 域和子域的工作命名空间&#xff0c;这些域和子域主要有助于查找过程&am…

echarts 几千条分钟级别在小时级别图标上展示

需求背景解决效果ISQQW代码地址strategyChart.vue 需求背景 需要实现 秒级数据几千条在图表上显示&#xff0c;(以下是 设计图表上是按小时界别显示数据&#xff0c;后端接口为分钟级别数据) 解决效果 ISQQW代码地址 链接 strategyChart.vue <!--/** * author: liuk *…

2023 年 亚太赛 APMCM ABC题 国际大学生数学建模挑战赛 |数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题。 以五一杯 A题为例子&#xff0c;以下是咱们做的一些想法呀&am…

电子眼与无人机在城市安防中的协同应用研究

随着城市化进程的快速推进&#xff0c;城市安全问题成为了人们关注的焦点。传统的安防手段已经无法满足现代城市复杂多变的安全需求。因此&#xff0c;结合电子眼与无人机技术&#xff0c;实现二者之间的协同应用&#xff0c;成为提升城市安防能力的重要途径。 一、电子眼与无人…