Redis_08_Bloom

1.再看Redis的布隆过滤器之前,先看看其他几位大佬的博客。

《转载—张维鹏》什么是散列码,什么是hashCode()
简单的总结一下:
hashCode就是对象的散列码。
散列的作用在于速度。
线性查询是最慢的查询方法。所以散列将键保存在某处,以便能够很快找到。存储一组元素最快的数据结构是数组,所以使用它来表示键的信息。
数组并不保存键本身,而是通过键对象生成一个数字,将其作为数组的下标,这个数字就是散列码。
听起来就像HashMap一样,其实就是HashMap实现原理。
String字符串的hashCode()相同而StringBuilder 对象的hashCode()不同。取决于是否重写了hashCode()方法,因为如果没有重写hashCode(),那么会使用Obkect()类的hashCode(),而Obhect.hashCode()永远计算的是对象的存储地址,而非内容。

2.使用场景,布隆过滤器判可以准确断该数据不存在,或者存在但有可能不存在的场景。

例如: 某个网站有1000万的用户,防止有人恶意刷登陆接口,导致数据库无法承受大量的并发,而被击穿.。
解决: 使用Redis做缓存将1000王的用户信息放入Redis的set中。。。好像有点大
布隆过滤器: Bloom filter是由Howard Bloom在1970年提出的二进制向量数据结构,它具有空间和时间效率,被用来检测一个元素是不是集合中的一个成员。

3.以精度换空间

优势
1.全量存储但是不存储元素本身
2.空间高效率
3.插入/查询时间都是常数O(k),效率高
用户a的唯一id存储入布隆过滤器。经过计算a存储到下标0,5,9998的存储位置
在这里插入图片描述
用户b的唯一id存储入布隆过滤器。经过计算b存储到下标是1,3,9999的存储位置
在这里插入图片描述
劣势:
用户c的唯一id存储入布隆过滤器。经过计算c存储到下标是0,5,9999的存储位置.。
1.存在误算率
但是0,5,9999的位置已经被a用户和b用户存储过了,不管c用户是合法用户还是非法用户都会通过我们布隆过滤器的判断,而且随着存入的元素数量增加,误算率随之增加。
我们能做的,就是尽可能将容器扩大和增加散列码的散列程度,减少散列码的碰撞几率。
在这里插入图片描述
2.无法从中删除莫一个用户。因为我们无法判断当前存储位置是否有多个用户使用,只有全部删除重新写入。

4.如何计算可以减少存储位置的碰撞率

《demo转载— 负债程序猿》
该博主使用的是Hash 算法(扰动函数)减少碰撞的概率。

5.我们以hashMap的put操作为例子

先上源码:

//创建一个hashMap
       HashMap<Object, Object> hashMap = new HashMap<>();
       hashMap.put(1,1);
       //put方法
       public V put(K key, V value) {
           return putVal(hash(key), key, value, false, true);
       }
       //get方法
       public V get(Object key) {
           HashMap.Node<K,V> e;
           return (e = getNode(hash(key), key)) == null ? null : e.value;
       }
       //获取hash方法
       static final int hash(Object key) {
           int h;
           return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
       }


//向桶中设置一个值
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                  boolean evict) {
       Node<K,V>[] tab; Node<K,V> p; int n, i;
       if ((tab = table) == null || (n = tab.length) == 0)
           n = (tab = resize()).length;
       if ((p = tab[i = (n - 1) & hash]) == null)
           tab[i] = newNode(hash, key, value, null);
       else {
           Node<K,V> e; K k;
           if (p.hash == hash &&
               ((k = p.key) == key || (key != null && key.equals(k))))
               e = p;
           else if (p instanceof TreeNode)
               e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
           else {
               for (int binCount = 0; ; ++binCount) {
                   if ((e = p.next) == null) {
                       p.next = newNode(hash, key, value, null);
                       if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                           treeifyBin(tab, hash);
                       break;
                   }
                   if (e.hash == hash &&
                       ((k = e.key) == key || (key != null && key.equals(k))))
                       break;
                   p = e;
               }
           }
           if (e != null) { // existing mapping for key
               V oldValue = e.value;
               if (!onlyIfAbsent || oldValue == null)
                   e.value = value;
               afterNodeAccess(e);
               return oldValue;
           }
       }
       ++modCount;
       if (++size > threshold)
           resize();
       afterNodeInsertion(evict);
       return null;
   }

       //put 和 get的时候 获取节点的方法
       final HashMap.Node<K,V> getNode(int hash, Object key) {
           HashMap.Node<K,V>[] tab; HashMap.Node<K,V> first, e; int n; K k;
           if ((tab = table) != null && (n = tab.length) > 0 &&
                   (first = tab[(n - 1) & hash]) != null) {
               if (first.hash == hash && // always check first node
                       ((k = first.key) == key || (key != null && key.equals(k))))
                   return first;
               if ((e = first.next) != null) {
                   if (first instanceof HashMap.TreeNode)
                       return ((HashMap.TreeNode<K,V>)first).getTreeNode(hash, key);
                   do {
                       if (e.hash == hash &&
                               ((k = e.key) == key || (key != null && key.equals(k))))
                           return e;
                   } while ((e = e.next) != null);
               }
           }
           return null;
       }

1.我们 hashMap.put(1,1);时如何保证这个key可以最理想化的分布在我们的桶中,(如何最大散列程度)
看下put()方法中的hash(key)方法:

 static final int hash(Object key) {
       int h;
       return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
   }

2.h = key.hashCode()) ^ (h >>> 16) 这是hash()方法拿到key的hashCode然后^ (h >>> 16);
但是在putVal()中,在hash()方法上有了 p = tab[i = (n - 1) & hash]
最终就是p为该key的最终位置:
为什么p需要这么计算 :p = tab[i = (n - 1) & (key.hashCode()) ^ (h >>> 16)])

n = tab.length 就是hashMap桶的大小 默认16 为什么需要减一
 log.info("桶数为2^n: hashde的值为{} ", 16 & "hash".hashCode());
        log.info("桶数为2^n: PUBG的值为{} ", 16 & "PUBG".hashCode());
        log.info("桶数为2^n: 零度之下的值为{} ", 16 & "零度之下".hashCode());
        log.info("桶数为2^n-1: hashde的值为{} ", 15 & "hash".hashCode());
        log.info("桶数为2^n-1: PUBG的值为{} ", 15 & "PUBG".hashCode());
        log.info("桶数为2^n-1: 零度之下的值为{} ", 15 & "零度之下".hashCode());

Console

14:30:51.053 [main] INFO com.hdh.test.test - 桶数为2^n: hashde的值为0 
14:30:51.057 [main] INFO com.hdh.test.test - 桶数为2^n: PUBG的值为0 
14:30:51.057 [main] INFO com.hdh.test.test - 桶数为2^n: 零度之下的值为16 
14:30:51.057 [main] INFO com.hdh.test.test - 桶数为2^n-1: hashde的值为14 
14:30:51.057 [main] INFO com.hdh.test.test - 桶数为2^n-1: PUBG的值为10 
14:30:51.057 [main] INFO com.hdh.test.test - 桶数为2^n-1: 零度之下的值为0 

不管是 (n - 1) & hash 还是 (key.hashCode()) ^ (h >>> 16)]) 都是为了散列,让我们的key可以更加均匀的散列在桶中
减少碰撞因为
2^ 4=16=10000B 因为 2^n 10000& 任意一个数要么0要么是2^n 而且大小16的 ,而 2^ 4-1=15=1111B 就有更多种可能。
数组没有下标16的位置。

6.Linux安装redis和Bloom
//或者自己去官网下tar
wget http://download.redis.io/releases/redis-4.0.9.tar.gz
tar xzvf redis-4.0.8.tar.gz
make
make install PREFIX=/usr/local/redis 
手动cp redis.config到你自己的安装目录下

修改配置文件自后台启动,注释掉#bind 127.0.0.1  
protected-mode no
daemonize yes

启动服务端./redis.server redis.config
启动客户端 ./redis.cli -p 6379 [-a (pwd) 可不写]

详细springBoot整合redis看这
RedisBloom安装

//或者自己去官网下tar
wget https://github.com/RedisBloom/RedisBloom/archive/v2.0.3.tar.gz
make
修改redis.conf
loadmodule /opt/apps/RedisBloom-2.0.3/redisbloom.so

自己测试一下
127.0.0.1:6379> bf.add key1  val1
安装完成
7.springBoot整合redie和RedisBloom(自己整理的将就着看)

Redis.config 序列化RedisTemplate和注册布隆过滤器

@Configuration
public class RedisConfig {


    @Bean
    public RedisTemplate<String, Object> myRedisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);

        // 使用Jackson2JsonRedisSerialize 替换默认序列化(默认采用的是JDK序列化)
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        //string的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        //key采用string的序列化
        redisTemplate.setKeySerializer(stringRedisSerializer);
        //hash的key也采用string的序列化
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        //value序列化的方式也采用jackson
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        //hash的value序列化方式采用jackson
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    //初始化布隆过滤器,放入到spring容器里面
    @Bean
    public BloomFilterHelper<String> initBloomFilterHelper() {
        return new BloomFilterHelper<>((Funnel<String>) (from, into) -> into.putString(from, Charsets.UTF_8).putString(from, Charsets.UTF_8), 1000000, 0.01);
    }

}
/**
 * @description: 在redis中设置 判断是否存在
 * @author:hdh
 * @date:2021-07-05 15:09
 **/
@Component
public class RedisBloomFilterUtils {
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 根据给定的布隆过滤器添加值
     */
    public <T> void addByBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
        Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能为空");
        int[] offset = bloomFilterHelper.murmurHashOffset(value);
        for (int i : offset) {
            System.out.println("key : " + key + " " + "value : " + i);
            redisTemplate.opsForValue().setBit(key, i, true);
        }
    }

    /**
     * 根据给定的布隆过滤器判断值是否存在
     */
    public <T> boolean includeByBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
        Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能为空");
        int[] offset = bloomFilterHelper.murmurHashOffset(value);
        for (int i : offset) {
            System.out.println("." + " : " + key + " " + "value : " + i);
            if (!redisTemplate.opsForValue().getBit(key, i)) {
                return false;
            }
        }
        return true;
    }
}

Bloom过滤器设置

public class BloomFilterHelper<T> {

    private int numHashFunctions;

    private int bitSize;

    private Funnel<T> funnel;

    public BloomFilterHelper(Funnel<T> funnel, int expectedInsertions, double fpp) {
//        Preconditions里面的方法:
//
//        1 .checkArgument(boolean) :
//        功能描述:检查boolean是否为真。 用作方法中检查参数
//        失败时抛出的异常类型: IllegalArgumentException
//
//        2.checkNotNull(T):
//        功能描述:检查value不为null, 直接返回value;
//        失败时抛出的异常类型:NullPointerException
//
//        3.checkState(boolean):
//        功能描述:检查对象的一些状态,不依赖方法参数。 例如, Iterator可以用来next是否在remove之前被调用。
//        失败时抛出的异常类型:IllegalStateException
//
//        4.checkElementIndex(int index, int size):
//        功能描述:检查index是否为在一个长度为size的list, string或array合法的范围。 index的范围区间是[0, size)(包含0不包含size)。无需直接传入list, string或array, 只需传入大小。返回index。
//        失败时抛出的异常类型:IndexOutOfBoundsException
//
//
//        5.checkPositionIndex(int index, int size):
//        功能描述:检查位置index是否为在一个长度为size的list, string或array合法的范围。 index的范围区间是[0, size)(包含0不包含size)。无需直接传入list, string或array, 只需传入大小。返回index。
//        失败时抛出的异常类型:IndexOutOfBoundsException
//
//        6.checkPositionIndexes(int start, int end, int size):
//        功能描述:检查[start, end)是一个长度为size的list, string或array合法的范围子集。伴随着错误信息。
//        失败时抛出的异常类型:IndexOutOfBoundsException
        Preconditions.checkArgument(funnel != null, "funnel不能为空");
        this.funnel = funnel;
        bitSize = optimalNumOfBits(expectedInsertions, fpp);
        numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, bitSize);
    }

    int[] murmurHashOffset(T value) {
        int[] offset = new int[numHashFunctions];

        long hash64 = Hashing.murmur3_128().hashObject(value, funnel).asLong();
        int hash1 = (int) hash64;
        int hash2 = (int) (hash64 >>> 32);
        for (int i = 1; i <= numHashFunctions; i++) {
            int nextHash = hash1 + i * hash2;
            if (nextHash < 0) {
                nextHash = ~nextHash;
            }
            offset[i - 1] = nextHash % bitSize;
        }
        return offset;
    }

    /**
     * 计算bit数组长度
     */
    private int optimalNumOfBits(long n, double p) {
        if (p == 0) {
            p = Double.MIN_VALUE;
        }
        return (int) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
    }

    /**
     * 计算hash方法执行次数
     */
    private int optimalNumOfHashFunctions(long n, long m) {
        return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值