Redis高级特性(一)-Bitmaps与布隆过滤器

1.Bitmaps

Bitmaps在Redis本质上是字符串,但它可以对字符串的位进行操作。我们可以把Bitmaps看出成以

位为单位的数组,为了区别字符串的操作,Redis给Bitmaps提供了一套单独的命令对其进行操

作。

1.1 常用命令

1.1.1 设置值

setbit key offset value setbit

命令接收两个参数, 第一个参数表示你要操作的是第几个bit位,第二个参数表示你要将这个位设

为何值,可选值只有0,1两个。 如果所操作的bit位超过了当前字串的长度,reids会自动增大字串长

度。

1.1.2 获取值

getbit key offset getbit

只是返回特定bit位的值。如果试图获取的bit位在当前字串长度范围外,该命令返回0。

1.1.3 获取Bitmaps指定范围值为1的个数

bitcount key [start] [end]

如果start和end顺序有错误结果则不对

1.2 Bitmaps间运算

BITOP AND destkey key [key ...]

对一个或多个 key 求逻辑并,并将结果保存到 destkey 。

BITOP OR destkey key [key ...]

对一个或多个 key 求逻辑或,并将结果保存到 destkey 。

BITOP XOR destkey key [key ...]

对一个或多个 key 求逻辑异或,并将结果保存到 destkey 。

BITOP NOT destkey key

对给定 key 求逻辑非,并将结果保存到 destkey 。

除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入。

1.3 Bitmaps的优势

假设要对一个1亿日活的网站用户id进行记录,从下图我们可知,Bitmaps的优势在于在存储海量数

字的情况下,它可以以极小的容量进行存储,节省大量的内存空间。

数据类型每个用户id占用空间需要存储用户量1天占用空间1个月占用空间1年占用空间
set64位10000w800MB24G288G
Bitmaps1位10000w12.5MB375.5MB4.5G

2.布隆过滤器

布隆过滤器( Bloom Filter) 是在 1970 年由布隆( Burton Howard Bloom) 提出,是一种紧凑型

的、比较巧妙的概率型数据结构,它由一个很长的二进制向量( 位向量)和一系列随机均匀分

布的散列( 哈希)函数组成 此种方式不仅可以提升查询效率,也可以节省大量的内存空间。

当一个元素被加入元素集合时,通过 k 个散列函数将这个元素映射成一个位数组中的 k 个点(

特征) ,把它们置为 1,检索时这些点如果是 1,则被检元素可能存在,如果这些点当中有任

何一个 0,则被检元素一定不在。

准备阶段

1.创建向量位图,并设置其值为0

2.准备hash函数数组

3.准备已有元素集合 构建阶段

4.每个元素循环执行hash函数,并且把结果写入向量位图上

查找阶段

5.接收判断元素循环执行hash函数,获得对应值

确认阶段

6.判断接收元素所算出的结果,如果对应位图全为1,那么这个元素有可能存在于集合中,如果结果存在1个或以上为0,那么这个元素比不存在于数组中

3.基于Redis Bitmaps实现的简单布隆过滤器

1.通过http://en.wikipedia.org/wiki/Bloom_filter#Probability_of_false_positives阅读我们可以进行布隆过滤器的使用,我们需要获取下面的参数:

k是函数个数

m是位图位数

n是插入元素个数

ε是误报率

其中,元素个数和误报率将由使用者提供,通过下面的代码可知

    private RedisBloomFilter(int forecastNumber,float bearError,RedisTemplate redisTemplate)
    {
        this.redisTemplate=redisTemplate;
        this.forecastNumber=forecastNumber;
        this.bearError=bearError;
        this.bitslength = (long) (-forecastNumber * Math.log(bearError) / (Math.log(2) * Math.log(2)));
        this.hashFunctionsNum=(int) Math.round((double) this.bitslength / this.forecastNumber * Math.log(2));
        this.setScript = new DefaultRedisScript<>(setString, Long.class);
        this.getScript = new DefaultRedisScript<>(getString, Long.class);
    }

 由 {\displaystyle m=-{\frac {n\ln \varepsilon }{(\ln 2)^{2}}}}{\displaystyle m=-{\frac {n\ln \varepsilon }{(\ln 2)^{2}}}}方程式我们可计算出位图位数,转换成代码

(long) (-forecastNumber * Math.log(bearError) / (Math.log(2) * Math.log(2)))

又由{\displaystyle k={\frac {m}{n}}\ln 2}方程式我们计算出函数个数,转换成代码

Math.max(1, (int) Math.round((double) this.bitslength / this.forecastNumber * Math.log(2)))

2.在实际应用中Hash算法并不是无限的,因此由Adam Kirsch与Michael Mitzenmacher做出了研究,并证明了其效果:使用两个Hash函数的计算依然能保证较低的假阳性概率(两个不同的数据得到相同的结果)。

g_{i}(x)=h_{1}(x)+ih_{2}(x)+i^{2}modm

由原来的

g0(x) = hash1(object)              第0个hash函数求出的hash值

g1(x) = hash2(object)              第1个hash函数求出的hash值

g2(x) = hash3(object)              第2个hash函数求出的hash值

gk(x) = hashn(object)              第k-1个hash函数求出的hash值

转变为

//计算哈希结果

h1=hash1(object)  h2=hash2(object)

g0(x) = h1                    

g1(x) = h1+h2+1              

g2(x) = h1+2*h2+4            

gk-1(x) = h1+(k-1)*h2+(k-1)^2 

与guava不同我在这里还原了论文中的方程式,我使用了zero-allocation-hashing,他提供了多种高

效的哈希算法,这里我选用了guava使用的murmur_3实现和google自家的cityhash实现,由于

cityhash只能对string进行hash,因此该功能暂时只限制做string的处理。

        Long hash1=LongHashFunction.murmur_3().hashChars(str);
        Long hash2=LongHashFunction.city_1_1().hashChars(str);
        for(int i=0;i<hashFunctionsNum;i++)
        {
            long bloomresult=hash1+i*hash2+i*i;
            //确保结果在申请的位置中
            bloomresultlist[i]=(bloomresult & Long.MAX_VALUE) % bitslength;
        }

3.数据获取部分利用redis lua在执行存储操作的原子性,编写lua对bitmap进行操作

    //setbit lua 语句
    private final String setString="local actorlist = ARGV "+
            "for i,v in pairs(actorlist) do" +
            "    redis.call('setbit',KEYS[1],tonumber(v,10),1) "+
            "end";
    //getbit lua 语句
    private final String getString="local actorlist = ARGV "+
            "for i,v in pairs(actorlist) do" +
            "    if(redis.call('getbit',KEYS[1],tonumber(v,10))==0)  " +
            "    then" +
            "       return 0" +
            "    end "+
            "end"+
            " return 1";
    private Boolean setbits(String[] bloomresultlist,String redisKey)
    {
        redisTemplate.execute(setScript, new StringRedisSerializer(), new StringRedisSerializer(), Arrays.asList(redisKey),bloomresultlist);
        return true;
    }
    private Boolean getbits(String[] bloomresultlist,String redisKey)
    {
        if((Long)redisTemplate.execute(getScript, new StringRedisSerializer(), new StringRedisSerializer(), Arrays.asList(redisKey), bloomresultlist)!=1)
            return false;
        else
            return true;
    }

参考:

http://redisdoc.com/bitmap/bitop.html

http://en.wikipedia.org/wiki/Bloom_filter#Probability_of_false_positives

书籍:

《Redis开发与运维》

论文:

Less Hashing, Same Performance: Building a Better Bloom Filter

github:https://github.com/tale2009/Blog-Demonstration/tree/main/RedisBloomFilter

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值