redis布隆过滤器与四个缓存问题

布隆过滤器

定义

它实际上是一个很长的二进制数组+一系列随机hash算法映射函数,主要用于快速的判断一个元素是否在集合中.
布隆过滤器的初始状态

他的判断结果并不是百分之百准确的

特性
  • 高效的插入和查询,占用空间少,返回的结果是不确定性的
  • 一个元素如果判断结果为存在的时候元素不一定存在,但是判断结果为不存在的时候则一定不存在
  • 布隆过滤器可以添加元素,但是不能删除元素.因为删除元素会导致误判率增加
  • 误判只会发生在过滤器没有添加过的元素,对于已经添加过的元素不会发生误判.

因为可能发生hash冲突

使用场景
解决缓存穿透的问题

把已经存在数据的key存放到布隆过滤器中,相当于在redis前面再加一层拦截过滤.
当有新的请求时,先到布隆过滤器中查询是否存在,如果布隆过滤器不存在该条数据则直接返回,如果布隆过滤器中已存在,才去查询redis缓存,如果redis没有再去查询mysql

黑白名单校验

直接判断值在不在布隆过滤器里面

底层原理
哈希冲突案例
public static void main(String[] args) {
        System.out.println("Aa".hashCode());
        System.out.println("BB".hashCode());
    }
添加key

使用多个hash函数对key进行hash运算得到一个整数索引值,对位数组长度进行取模运算得到一个位置,每个hash函数都会得到一个不同的位置,将这几个位置都置为1就完成了add操作
添加key的图示
添加n个key
添加一个key

查询key

只要有其中一位是0就表示这个key不存在,如果都是1(都是布隆过滤器就返回存在),但是不一定就代表存在这对应的key.
因为只要是hash函数就存在hash冲突,哪怕是采用了多个hash函数也有可能会跟其他的多个key值hash出来的值冲突,所以不能确定一定存在
查询误判示意图:
查询误判示意图

总结
  • 有是有可能有,无就是一定无
  • 使用时最好不要让实际元素数量远大于初始化数量
  • 当实际元素数量超过初始化数量时,应该对布隆过滤器进行重建,重新分配一个size更大的过滤器,再将所有的历史元素批量add进去
  • 优点
  • 高效的插入和查询
  • 占用空间很少
  • 缺点
  • 不能删除元素(因为删除元素会导致误判率的增加,因为hash冲突同一个槽位对应多个对象的信息,你删除一个元素很可能把其他的也删除了)
  • 存在误判率(hash冲突,不同的数据对象可能会出来相同的hash值)

四个缓存问题

缓存雪崩

定义

redis主机挂了,redis全盘崩溃(或者缓存中有大量数据同时过期)

解决方案
  • redis缓存集群实现高可用
    • 主从+哨兵
    • Redis Cluster
  • ehcache本地缓存 + Hystrix或者阿里sentinel限流降级
  • 开启redis持久化机制aof/rdb,尽快恢复缓存集群

缓存穿透

定义

一般情况下,先查询缓存redis是否有该条数据,缓存未命中时,在查询数据库.当数据库也不存在该条数据时,每次查询都要访问数据库,这就是缓存穿透.它带来的问题就是当有大量的请求查询数据库本身就不存在的数据时,就会给数据库带来压力,甚至是直接拖垮数据库

解决方案
  • 方案1:空对象缓存或者缺省值
  • 方案2:google布隆过滤器guava解决缓存穿透
  • 方案3:redis布隆过滤器解决缓存穿透
方案一

就是缓存一个空值或者业务上定义的缺省值在redis里面缓存返回(每次换id就不好用了 而且会导致大量的无用key堆积)

方案二(guava实现)
代码案例
public static final int _1w = 10000;

    public static final int size = 100 * _1w;

    public static double fpp = 0.03;

    /**
     * 入门demo
     */
    public void bloomFilter() {
        BloomFilter<Integer> filter = BloomFilter.create(Funnels.integerFunnel(), 100);
        System.out.println(filter.mightContain(1));
        System.out.println(filter.mightContain(2));
        filter.put(1);
        filter.put(2);
        System.out.println(filter.mightContain(1));
        System.out.println(filter.mightContain(2));
    }

    /**
     * 误判率演示和源码分析
     */
    public void bloomFilter2() {
        BloomFilter<Integer> filter = BloomFilter.create(Funnels.integerFunnel(), size);
        for (int i = 0; i < size; i++) {
            filter.put(i);
        }
        ArrayList<Object> list = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            if (filter.mightContain(i)) {
                list.add(i);
            }
        }
        System.out.println("存在的数量" + list.size());
        ArrayList<Object> arrayList = new ArrayList<>(10 * _1w);
        for (int i = size + 1; i < size + 100000; i++) {
            if (filter.mightContain(i)) {
                System.out.println(i + "被误判了");
                arrayList.add(i);
            }
        }
        System.out.println("误判的数量" + arrayList.size());
    }


    public static void main(String[] args) {
        new GuavaBBloomFilterDemo().bloomFilter2();
    }
源码分析

guava布隆过滤器新建

当你的误判率fpp越低,需要占用的bit数组就越长,对值进行hash计算的hash函数就越多,这样的话能更加避免hash冲突的情况发生,用空间和时间来换取准确率,guava默认设置是0.03,个人用的话最低也就建议到0.01,再低的话导致程序变慢就得不偿失了.

方案三(RedisSon实现)
代码实现
public static final int _1w = 10000;

    public static final int size = 100 * _1w;

    public static double fpp = 0.01;

    static RedissonClient redissonClient;

    static RBloomFilter rBloomFilter;

    static {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://8.131.64.231:16678").setDatabase(0)
                .setPassword("liang#0601");
        redissonClient = Redisson.create(config);
        rBloomFilter = redissonClient.getBloomFilter("phoneList", new StringCodec());
        rBloomFilter.tryInit(size, fpp);
        rBloomFilter.add("10086");
        redissonClient.getBucket("10086", new StringCodec()).set("chinamobile10086");
    }

    public static String getPhone(String id) {
        String result;
        if (rBloomFilter.contains(id)) {
            RBucket<String> rBucket = redissonClient.getBucket(id, new StringCodec());
            result = rBucket.get();
            if (result != null) {
                return "result form redis:" + result;
            } else {
                result = getByMysql(id);
                if (result == null) {
                    return null;
                }
                redissonClient.getBucket(id, new StringCodec()).set(result);
            }
            return "result form mysql:" + result;
        }
        return null;
    }

    public static String getByMysql(String id) {
        return "10086mysql";
    }


    public static void main(String[] args) {
        String phone = getPhone("10086");
        System.out.println(phone);
        redissonClient.shutdown();
    }
方案四(直接安装redis插件,应用层解决方案)
编译安装Rebloom插件

编译安装
编译安装

docker安装

常用操作命令

默认的误判率是0.01 默认的bit数组是100

缓存击穿

定义

热点key突然失效了,导致大量的请求直接打到了MySQL上面

解决方案
  • 互斥更新(在第一个线程进来拿数据的时候,如果发现redis里面没有就用互斥锁锁住,更新进入redis之后在放开锁)
  • 随机退避
  • 双缓存结构解决
  • 开启两块缓存,主A从B,先更新B再去更新A,严格按照这个顺序
  • 先查询主缓存A,A没有再去查询从缓存B
  • 注意更新和查询的顺序要倒过来,这样就能避免有时间差

两个主从缓存差异化失效时间,在你删除缓存的时候就不会缓存击穿

缓存问题总结图

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Redis过滤器Redis Bloom Filter)是一种基于概率数据结构的空间效率高、查询效率快的数据过滤器。它主要用于判断一个元素是否存在于一个大型集合中,具有低内存消耗和快速查询的特点。 过滤器的原理是利用多个哈希函数和一个位数组来表示集合中的元素。当一个元素被加入到过滤器中时,会通过多个哈希函数计算出多个哈希值,并将对应的位数组位置设为1。当需要判断一个元素是否存在时,同样通过多个哈希函数计算出多个哈希值,并检查对应的位数组位置是否都为1。如果有任何一个位置为0,则可以确定该元素不存在于集合中;如果所有位置都为1,则可能存在于集合中,但并不确定。 Redis过滤器通过提供以下几个命令来实现: 1. BF.ADD:将一个元素添加到过滤器中。 2. BF.EXISTS:判断一个元素是否存在于过滤器中。 3. BF.MADD:批量添加多个元素到过滤器中。 4. BF.MEXISTS:批量判断多个元素是否存在于过滤器中。 需要注意的是,过滤器在判断元素存在时可能会出现误判,即判断元素存在但实际上不存在。这是因为过滤器的位数组中可能存在碰撞,多个元素计算得到的位数组位置可能相同。因此,在使用过滤器时需要权衡误判率和内存消耗之间的关系,并根据具体场景进行调整。 Redis过滤器可以应用于一些需要快速判断元素是否存在的场景,例如缓存穿透的防护、URL去重、爬虫过滤等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

life or die

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值