redis位图的简单使用
如果要简单做一个签到功能,可能最简单粗暴的方式就是,插入数据库,签到一次插入一条数据。那么问题来了,一个人一年会有365条 或者366条数据,而一个小部门一年就会有成千上万条数据。同时数据插入的时候,需要用upsert操作,也是需要消耗资源的。因此这种做法是不可取的,如果先从数据库里面查询今天签到没有,再进行签到呢?那这个操作不是原子性的,可能会导致线程安全问题,一个人可能最后可以签到多次,另外需要查询,也需要插入,分为俩次操作。
此外还有一个问题,查询一个一年签到多少次,怎么办?数据库里面做一个聚合查询,也可以做到这个,但是数据量本身很大的情况下,会导致并不快。每个人的某一年的数据,从字面上来看,需要根据人以及年份做聚合,数据量小的时候确实无关紧要,但是数据量一大,数据库的索引本身很大的情况下,就显得效率不怎么高了。
那么怎么样做这个操作比较好呢?
其实说到就是查询是否签到,跟签到需要是一个完整的过程,那么利用redis原子性,其实可以很好的实现上面的功能。其实是从书上看到的,但是想做一个demo模拟一下使用。就做一个捡漏的签到功能吧。那么如何减少数据量呢,利用redis的位图。那么下面讲讲如何使用redis的位图。
首先需要准备的东西
redis
docker
为什么要使用位图
计算机里面数据结构的最小单位就是bit了,如果一个人一天的签到,占一个位的话,那么一年的签到,会占365或者366个位,一条记录大概是46个字节,就可以满足了。当然这里是以年为单位的,若以月为单位会更小。
另外签到其实就将该位的数据改为1,不存在线程安全问题,哪怕重复签到。
同时,当统计一个人一年签到多少次的时候,直接使用redis的bitCount指令即可。不用像数据库一样通过聚合数据来查询。
那么先来进行基本的使用
设置key=hello value=“hello”的一个键值对
set hello "hello" //设置键值对
bitcount hello //计算出有多少个1
结果如下图所示
127.0.0.1:6379> BITCOUNT hello 0 99
(integer) 21
127.0.0.1:6379> BITCOUNT hello
(integer) 21
h - > 1101000
e - > 1100101
l - > 1101100
l - > 1101100
o - > 1101111
3 + 4 +4 + 4 + 6 = 21刚刚好21个位是的值为1
另外bitcount这个指令还支持按范围搜索(粒度为字节,这里的话就使用上有点限制了,比如说要统计某个月多少次的话,就得每个月使用一个位图,而不是每年一个位图)
bitcount hello 0 0 //查询出第一个字节里面有多少个1 第一个字节就是h 很明显结果当然是3
127.0.0.1:6379> bitcount hello 0 0
(integer) 3
设置位图的数据 h这个字符的字节为01101000(高位到低位) 1,2,4位都为0
127.0.0.1:6379> SETBIT h 1 1
(integer) 0
127.0.0.1:6379> SETBIT h 2 1
(integer) 0
127.0.0.1:6379> SETBIT h 4 1
(integer) 0
127.0.0.1:6379> get h
"h"
127.0.0.1:6379> BITCOUNT h 0 0
(integer) 3
概括一下用法
setbit key index value 可以改变Key对应的value的某个位的值
bitcount key 统计出该key的value对应的1的总数
bitcount key start index 统计出该key的value对应的1的总数(依据字节范围)
下面贴代码
使用的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--单元测试相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- redis相关组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis依赖commons-pool 这个依赖一定要添加 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
操作位的一个demo
@Component
public class BitsOpUtils {
@Autowired
private RedisTemplate<String, String> jedisTemplate;
//返回的值是与传入的tag值相反,便是成功,设置为1 返回false 设置为0返回true
public boolean setBits(String key, int index, int value) {
//tag为true就是设置1 否则就是设置0
boolean tag = value == Constants.ONE;
return jedisTemplate.execute((RedisCallback<Boolean>) con -> con.setBit(key.getBytes(), index, tag));
}
public Long countBits(String key) {
return jedisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes()));
}
}
测试方法---模拟2020的签到,这里还有一个东西就是要知道当天位于一年的第几天,决定要更新哪个位,这里代码就不写这个了
public class TestBitsOpUtils extends TestApplication{
@Autowired
private BitsOpUtils bitsOpUtils;
@Test
public void setBits() {
//模拟2020年第一天签到
String key = "2020";
int index = 0;
int value = 1;
System.out.println(bitsOpUtils.setBits(key, index, value));
}
@Test
public void countBits() {
//模拟2020年总共签到多少次
String key = "2020";
System.out.println(bitsOpUtils.countBits(key));
}
}
测试结果
第一个方法返回的是 false
第二个方法则是 1 符合预期