redis位图的简单使用

2020-10-23  本文已影响0人  guessguess

如果要简单做一个签到功能,可能最简单粗暴的方式就是,插入数据库,签到一次插入一条数据。那么问题来了,一个人一年会有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 符合预期
上一篇 下一篇

猜你喜欢

热点阅读