并发系列-1-高并发下Redis缓存穿透解决方案与原理分析

高并发下Redis缓存穿透解决方案与原理分析

1.为什么用redis 做缓存

目的:降低数据库的压力

redis 的特性:

存储大小

数据类型k-v

持久化,过期

分布式服务间 共享数据

 

2.缓存穿透

A 缓存在 数据库里存在

application --> 缓存存在 直接查缓存

B 缓存在 数据库里不存在 数据不一致(没更新缓存、key过期)

Application —> 缓存不存在,数据库存在 —>读取 database(加锁,高并发场景下只允许一个线程查询 )—> 写入缓存

C 缓存不存在 数据库里不存在

应用 查redis 没查到 去 database 没查到 返回空  一直查询此条件 导致 缓存穿透
要避免持续的查询在缓存和数据库都不存在的数据 (把空也更新到缓存 并加key过期时间,避免数据不同步)

攻击:一直查询不同的 缓存和数据库不存在的数据 怎么解决呢 ?

延伸面试题:

如何在海量的数据中 (例如 10亿 无序、不定长、不重复)快速判断一个元素是否存在?

直接放内存 10亿数据  无论放哪种 存储类型 都需要几个G的 内存

最简单的数据结构 0 1 (位、图 )其实就是有序的数据

如何 用 01 存储数据

映射 1.固定长度的输出 2.均匀

Hash 算法 位运算做映射

 

(Mic 和 Tom两个数据算法得位置一样就产生了 Hash碰撞:)

   如何降低Hash碰撞的概率?

  1. 扩大位数组的容量(缺点消耗内存)

  2. 增加hash函数的个数例如增加筛选条件(缺点 降低存取hash计算的效率)

   如何中和 1.2 的问题呢 —>

   1970  布隆发表了 布隆过滤器 

布隆过滤器的原理

本质(1.位数组(二进制向量)2.一系列随机映射函数)

那上图中 d 在 三个位置都是1一定确定 d存在吗? 不,还是会有hash碰撞 ( 布隆过滤器 存在误判 — 假阳性)

那上图中 e 在三个位置有一个0 ,那e 一定不存在

 

如果布隆过滤器判断元素在集合中存在—> 不一定存在

如果布隆过滤器判断元素判断不存在—> 一定不存在

 

所以 如果元素实际存在,布隆过滤器一定判断存在

         如果元素实际不存在,布隆过滤器可能判断存在

 

基于以上原理,我们可以解决缓存穿透的问题 (布隆过滤器判断元素不存在 则一定不存在)

Guavo — 谷歌 封装了 字符串、集合、缓存、限流、二维码 的工具包
国内叫 Hutool

Guavo 里面有个 Blomn Filter 布隆过滤器
使用:a.引入 guava 依赖

		<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>30.1-jre</version>
		</dependency>

           b. 创建过滤器 测试过滤器的误判率

public class BloomFilterDemo {


    private static final int insertions = 1000000;


    public static void main(String[] args) {

        //初始化一个存储String数据的布隆过滤器,初始化大小为 100w

        //默认误判率是 0.03

        BloomFilter<String> bf = BloomFilter.create(

                Funnels.stringFunnel(Charsets.UTF_8),insertions,0.0001);


        //用于存放所有实际存在的key,判断key是否存在

        Set<String> sets = new HashSet<>(insertions);


        //用于存放所有实际存在的key,可以取出使用

        List<String> lists = new ArrayList<>(insertions);


        //向三个容器初始化100w 个随机并且唯一的字符串

        for (int i = 0; i < insertions ; i++) {

            String uuid = UUID.randomUUID().toString();

            bf.put(uuid);

            sets.add(uuid);

            lists.add(uuid);

        }

        //判断正确的次数

        int right = 0;

        //判断错误的次数

        int wrong = 0;

        for (int i = 0; i < 10000; i++) {

            //可以被100整除的时候,取一个存在的数,否则随机生成一个uuid

            // 0-10000 之间,可以背100 整除的数有100个
            String data = i%100 == 0 ? lists.get(i/100):UUID.randomUUID().toString();

            //mightContain (可能存在)

            if (bf.mightContain(data)) {

                if (sets.contains(data)) {

                    //判断存在时命中

                    right ++;

                    continue;

                }

                //判断不存在时,错误

                wrong ++;

            }

        }

        //计算存在的判断正确 命中率

        NumberFormat percentFormat = NumberFormat.getPercentInstance();

        //最大小数位数

        percentFormat.setMaximumFractionDigits(2);

        float percent = (float)wrong/9900;

        float bingo = (float)(9900-wrong)/9900;

        System.out.println("在100w个元素中,判断100个实际存在的元素,布隆过滤器认为存在的"+right);

        System.out.println("在100w个元素中,判断9900个实际不存在的元素,布隆过滤器误认为存在的"+wrong+",命中率:"+percentFormat.format(bingo)+",误判率:"+percentFormat.format(percent));

    }


 

布隆过滤器的地位,数据库的数据加载到布隆过滤器,在访问redis之前
把数据库的所有数据加载到 布隆过滤器里,访问redis缓存之前先查询布隆过滤器,如果不存在则直接返回,这样避免了缓存穿透

为什么布隆过滤器里面没有提供删除方法,因为有hash碰撞

 

 

 

 

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值