Redis BitMap(位图)
这里是小咸鱼的技术窝(CSDN板块),我又开卷了
之前经手的项目运行了10多年,基于重构,里面有要实现一些诸如签到的需求,以及日历图的展示,可以用将签到信息存到传统的关系型数据库(MySql、Oracle) 然后用 Sql 进行统计、也可以借助 Redis 中的 BitMap来实现签到需求。俩者的区别就在于,一个签到了100万年的项目,你用Mysql无论如何进行分库分表,落地到磁盘的上的数据体量都很大,且查询速度会变慢,但是用 Redis 存储数据占用的内存小,且统计效率更快。当然如果你怕Redis挂,也可以存俩份数据,一份放Mysql一份放Redis,利用 Redis 缓存考勤结果加快查询效率,但是占用的机器磁盘过大的问题还是没解决,下面介绍用 BitMap 如何实现下面这个签到日历。
这个项目的签到用到了海康的人脸识别SDK靠每天进行人脸识别打卡,然后记录签到信息的,就拿手动点击签到来说。整个流程如下。
用户点击签到按钮,发起一个签到请求给我们,先调用 getbit 命令,判断今天有没有进行签到过,没有然后调用 setbit 命令,把签到结果存入 BitMap(期间用户重复点击签到也没事,BitMap多次重复设置值的操作是幂等的)
BitMap 介绍 https://redis.io/commands/bitop/
下面的这段洋文是官网对BitMap的介绍
Bitmaps are not an actual data type, but a set of bit-oriented operations defined on the String type which is treated like a bit vector. Since strings are binary safe blobs and their maximum length is 512 MB, they are suitable to set up to 2^32 different bits.
位图不是实际的数据类型,而是在字符串类型上定义的一组面向位的操作,该操作被视为位向量。由于字符串是二进制安全blob,字符串最大为512 MB,因此它们适合设置为2^32个不同的位。从这段话中我们可以知道,BitMap 其实就是一个二进制的容器,并且他的二进制位最大为 2^32 位(512Mb = 512 * 1024 * 1024 * 8 = 2^32个bit位),当然我们签到只用的到31 bit 位,因此针对于签到需求来说,Bitmap初始结构长这样
0000000000|0000000000|0000000000|0
联想一下我们一个月最大 31 天,1号签到了我们在第一bit位设置为1就好了,同理在第2,3,4,5天签到了,在对应的bit位设置值为1就好了,BitMap是不是很适合签到场景。并且它提供了很多统计API。
SETBIT key offset value:将指定偏移量上的位设置为1或0。
GETBIT key offset:获取指定偏移量上的位的值。
BITCOUNT key start end:计算指定key的Bitmap中1的数量。
BITOP operation destkey key [key...]:对多个key进行位运算,并将结果存储到destkey中。operation可以是AND、OR、XOR、NOT等。
BITPOS key 1 start end:查找指定key的Bitmap中第一个1的偏移量。
BITPOS key 0 start end:查找指定key的Bitmap中第一个0的偏移量。
BITPOS key 0 start:从start的位置开始,查找指定key的Bitmap中第一个0的偏移量。
SET key value:将指定key的Bitmap设置为指定的值。
GET key:获取指定key的Bitmap的值。
//类型u代表无符号十进制,i代表带符号十进制
BITFIELD key get u3 0:从偏移量offset=0开始取3位,获取无符号整数的值,返回的值是一个十进制数
INCRBY u4 4 1:从偏移量offset=4后开始取4位,得到一个对应的二进制数,然后转换成10进制加1
- 利用 bitcount 命令我们可以快速统计出用户,本月签到了多少天
- 利用 setbit 命令我们可以快速记录签到信息
- 利用 getbit 命令我们可以快速判断用户今天是否已经签到
值得注意的是 BITCOUNT key start end 命令中的 start end 指的是 byte 的区间,而 1 byte = 8 bit。那么执行如下命令
bitcount char 0 8的结果是10就不奇怪了,统计范围是 0-8 byte区间,转换成 bit 区间就是 0-64。0-64范围内有 10个1。因此结果是10而不是3!!!!!
签到场景里面一般都不会直接去给Bitmap设置字符串,设置字符串还要去转换成对应的ACSII码然后存入BitMap,而是用 setbit 命令按bit设置值。
注意点二 例如 执行 setbit key 8 1 命令,存储的是 0000000010000000 而不是 000000001,低bit 位会用 0 补齐,因为 byte 是最小的存储单元。这些个个细节要搞清楚哦。
另外BitMap也支持二进制数(与、或运算),平常很少用这个。跟着我大声朗读运算法则,与运算都是1才为1,或运算有1就是1。
mac:0>set a a (01100001)
"OK"
mac:0>set b b (01100010)
"OK"
mac:0>bitop and andres a b (a、b与运算,结果设置到anres)
"1"
mac:0>bitop or orres a b (a、b或运算,结果设置到orres)
BitMap的源码,自行去翻阅redis安装目录下的 src里面的c文件,蹲一个c语言大佬
BitMap签到脚手架
接着用Jedis实现,因为Api命名和原始redis命令很接近。实际开发中合理织入自己的业务逻辑即可。
@Slf4j
@Api(tags = "Bit签到")
@RestController
@RequestMapping("/bit")
public class BitController {@Autowiredprivate Jedis jedis;@ApiOperation("获取签到天数")@PostMapping("/userSignCount")public Result userSignCount(@RequestBody RedisRequest redisRequest) {//相当于执行 bitcount user:sign:uid:yyyyMM 0 31 命令return Result.success(jedis.bitcount(buildSignKey(redisRequest.getUid(),getDate(redisRequest.getDateStr())), 0, 31));}@ApiOperation("签到/补签")@PostMapping("/userSign")public Result sign(@RequestBody RedisRequest redisRequest) {//日期字符串转DateDate date = getDate(redisRequest.getDateStr());//该日期该月的第几天int offset = DateUtil.dayOfMonth(date) - 1;//user:sign:uid:yyyyMMString signKey = buildSignKey(redisRequest.getUid(), date);Assert.isFalse(jedis.getbit(signKey, offset), "今天以及签到过了");//相当于执行 setbit user:sign:uid:yyyyMM offset 1 命令jedis.setbit(signKey, offset, true);return Result.success(getContinuousSignCount(redisRequest.getUid(), date));}/*** 统计截止日之前连续签到的天数* @param userId 用户id* @param date 截止日* @return*/private int getContinuousSignCount(Integer userId, Date date) {int dayOfMonth = DateUtil.dayOfMonth(date);//相当于执行 bitfield user:sign:uid:yyyyMM u10 0 命令List<Long> list = jedis.bitfield(buildSignKey(userId, date), "get", "u" + dayOfMonth, "0");if (list == null || list.isEmpty()) {return 0;}int signCount = 0;long v = list.get(0) == null ? 0 : list.get(0);// i 表示位移操作次数for (int i = dayOfMonth; i > 0; i--) {// 右移再左移,如果等于自己说明最低位是 0,表示未签到if (v >> 1 << 1 == v) {// 低位 0 且非当天说明连续签到中断了if (i != dayOfMonth) {break;}} else {signCount++;}// 右移一位并重新赋值,相当于把最低位丢弃一位v >>= 1;}return signCount;}private String buildSignKey(Integer userId, Date date) {return String.format("user:sign:%d:%s", userId,DateUtil.format(date, "yyyyMM"));}/*** 获取日期*/private Date getDate(String dateStr) {if (StrUtil.isBlank(dateStr)) {return new Date();}try {return DateUtil.parseDate(dateStr);} catch (Exception e) {throw new RuntimeException("请传入yyyy-MM-dd的日期格式");}}}
BitMaq去重
因为字符串最大为512mb,因此可以算出BitMap的最大长度是2的32次方。约42亿个bit位。去重也很简单,就拿我的QQ号去重来说,QQ: 3273448110,在 3273448110 的bit位置上设置为1就好了,同理其他QQ号在自己的Bit位上设置为1,最终统计BitMap有多少个1就得到了,不重复QQ号的个数。
总结
本文涵盖了BitMap从Api使用,到落地到常用场景的上的真实业务体现。以及使用上的注意事项。
这里是小咸鱼的技术窝
相关文章:

Redis BitMap(位图)
这里是小咸鱼的技术窝(CSDN板块),我又开卷了 之前经手的项目运行了10多年,基于重构,里面有要实现一些诸如签到的需求,以及日历图的展示,可以用将签到信息存到传统的关系型数据库(MyS…...
使用eclipse创建一个java文件并运行
启动 Eclipse 并创建一个新的 Java 项目: 打开 Eclipse。 选择 “File” > “New” > “Java Project”(文件 > 新建 > Java 项目)。 在弹出的窗口中,为你的项目命名,比如 MyJavaProject。 点击 “Finish”ÿ…...

C#上位机与欧姆龙PLC的通信05---- HostLink协议
1、介绍 Hostlink协议是欧姆龙PLC与上位机链接的公开协议。上位机通过发送Hostlink命令,可以对PLC进行I/O读写、可以对PLC进行I/O读写、改变操作模式、强制置位/复位等操作。由于是公开协议,即便是非欧姆龙的上位设备(软件)&…...

Uniapp 开发 BLE
BLE 低功耗蓝牙(Bluetooth Low Energy,或称Bluetooth LE、BLE,旧商标Bluetooth Smart),用于医疗保健、运动健身、安防、工业控制、家庭娱乐等领域。在如今的物联网时代下大放异彩,扮演者重要一环ÿ…...
c语言排序算法
C语言代码示例: 冒泡排序(Bubble Sort): void bubbleSort(int arr[], int n) {for (int i 0; i < n-1; i) {for (int j 0; j < n-i-1; j) {if (arr[j] > arr[j1]) {int temp arr[j];arr[j] arr[j1];arr[j1] temp;…...

【机器学习】模式识别
1 概述 模式识别,简单来讲,就是分类问题。 模式识别应用:医学影像分析、人脸识别、车牌识别、遥感图像 2 模式分类器 分类器的分类:线性分类器、非线性分类器、最近邻分类器 2.1 分类器的训练(学习)过…...

【Prometheus|报错】Out of bounds
【背景】进入Prometheus地址的9090端口,pushgateway(0/1)error : out of bounds 【排查分析】 1、out of bounds报错,是由于Prometheus向tsdb存数据出错,与最新存数据的时间序列有问题,有可能当前时间与最…...

【音视频】Mesh、Mcu、SFU三种框架的总结
目录 三种网络场景介绍 【Mesh】 【MCU】(MultiPoint Control Unit) 【SFU】(Selective Forwarding Unit) 三种网络架构的优缺点 Mesh架构 MCU架构(MultiPoint Control Unit) SFU架构(Selective Forwarding Unit) 总结 参考文章 三种网络场景介绍 【Mesh】 Mesh架构…...

高级算法设计与分析(四) -- 贪心算法
系列文章目录 高级算法设计与分析(一) -- 算法引论 高级算法设计与分析(二) -- 递归与分治策略 高级算法设计与分析(三) -- 动态规划 高级算法设计与分析(四) -- 贪心算法 高级…...

MATLAB - 机器人逆运动学设计器(Inverse Kinematics Designer APP)
系列文章目录 前言 一、简介 通过逆运动学设计器,您可以为 URDF 机器人模型设计逆运动学求解器。您可以调整逆运动学求解器并添加约束条件,以实现所需的行为。使用该程序,您可以 从 URDF 文件或 MATLAB 工作区导入 URDF 机器人模型。调整逆…...

使用OpenCV DNN模块进行人脸检测
内容的一部分来源于贾志刚的《opencv4应用开发、入门、进阶与工程化实践》。这本书我大概看了一下,也就后面几章比较感兴趣,但是内容很少,并没有想像的那种充实。不过学习还是要学习的。 在实际工程项目中,并不是说我们将神经网络…...
C#中使用OpenCV的常用函数
以下是一些C#中使用OpenCV的常用函数例子: 1. 加载图像: using OpenCvSharp;Mat image Cv2.ImRead("path_to_your_image.jpg", ImreadModes.Color); 2. 显示图像: Cv2.NamedWindow("Image Window", WindowFlags.Nor…...

使用Swift Package Manager (SPM)实现xcframework分发
Swift Package Manager (SPM) 是苹果官方提供的用于管理 Swift 项目的依赖关系和构建过程的工具。它是一个集成在 Swift 编程语言中的包管理器,用于解决在开发过程中管理和构建包依赖项的需求。 1、上传xcframework.zip到服务端 压缩xcframeworks成一个zip包&…...

非阻塞 IO(NIO)
文章目录 非阻塞 IO(NIO)模型驱动程序应用程序模块使用 非阻塞 IO(NIO) 上一节中 https://blog.csdn.net/tyustli/article/details/135140523,使用等待队列头实现了阻塞 IO 程序使用时,阻塞 IO 和非阻塞 IO 的区别在于文件打开的时候是否使用了 O_NONB…...

Android应用-flutter使用Positioned将控件定位到底部中间
文章目录 场景描述示例解释 场景描述 要将Positioned定位到屏幕底部中间的位置,你可以使用MediaQuery来获取屏幕的高度,然后设置Positioned的bottom属性和left或right属性,一般我们left和right都会设置一个值让控制置于合适的位置࿰…...

Django 简单图书管理系统
一、图书需求 1. 书籍book_index.html中有超链接:查看所有的书籍列表book_list.html页面 2. 书籍book_list.html中显示所有的书名,有超链接:查看本书籍详情book_detail.html(通过书籍ID)页面 3. 书籍book_detail.html中书的作者和出版社&…...

C++内存管理和模板初阶
C/C内存分布 请看代码: int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] { 1, 2, 3, 4 };char char2[] "abcd";const char* pChar3 "abcd";int* ptr1 (int*)mallo…...
QtRO(Qt Remote Objects)分布式对象远程通信
一、什么是QtRO Qt Remote Objects(QRO)是Qt提供的一种用于实现远程对象通信的机制。 QtRO支持两种类型的通信:RPC(远程过程调用)和LPC(本地进程通信)。 RPC(远程过程调用…...

【K8s】1# 使用kuboard-spray安装K8s集群
文章目录 搭建k8s集群1.推荐配置1.1.服务器配置1.2.软件版本 2.使用Kuboard-Spray安装k8s集群2.1.配置要求2.2.操作系统兼容性2.3.安装 Kuboard-Spray2.4.加载离线资源包2.5.规划并安装集群2.6.安装成功2.7.访问集群 3.涉及的命令3.1.linux 4.问题汇总Q1:启动离线集…...
leetCode算法—12. 整数转罗马数字
12. 整数转罗马数字 难度:中等 ** 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如, 罗马数字 2 写做 II ,即…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...

【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...
用鸿蒙HarmonyOS5实现中国象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...

[论文阅读]TrustRAG: Enhancing Robustness and Trustworthiness in RAG
TrustRAG: Enhancing Robustness and Trustworthiness in RAG [2501.00879] TrustRAG: Enhancing Robustness and Trustworthiness in Retrieval-Augmented Generation 代码:HuichiZhou/TrustRAG: Code for "TrustRAG: Enhancing Robustness and Trustworthin…...

车载诊断架构 --- ZEVonUDS(J1979-3)简介第一篇
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…...

【阅读笔记】MemOS: 大语言模型内存增强生成操作系统
核心速览 研究背景 研究问题:这篇文章要解决的问题是当前大型语言模型(LLMs)在处理内存方面的局限性。LLMs虽然在语言感知和生成方面表现出色,但缺乏统一的、结构化的内存架构。现有的方法如检索增强生成(RA…...

论文笔记:Large Language Models for Next Point-of-Interest Recommendation
SIGIR 2024 1 intro 传统的基于数值的POI推荐方法在处理上下文信息时存在两个主要限制 需要将异构的LBSN数据转换为数字,这可能导致上下文信息的固有含义丢失仅依赖于统计和人为设计来理解上下文信息,缺乏对上下文信息提供的语义概念的理解 ——>使用…...