• 八、缓存穿透


    一、简介

    请求去查询一条记录,先 redis 后 mysql 发现都查询不到该条记录,但是请求每次都会打到数据库上面去,导致后台数据库压力暴增,这种现象我们称为缓存穿透,这个redis变成了一个摆设。

    危害:

    第一次来查询后,一般我们有回写 redis 机制。第二次来查的时候 redis 就有了,偶尔出现穿透现象一般情况无关紧要。

    要防止恶意攻击,用 redis 和 mysql 都不存在的数据,不断查询。

    二、解决方案1:空对象缓存或者缺省值

    黑客会对你的系统进行攻击,拿一个不存在的 id 去查询数据,会产生大量的请求到数据库去查询,可能会导致你的数据库由于压力过大而宕掉。

    id相同打你系统。第一次打到mysql,空对象缓存后第二次就返回null了,避免mysql被攻击,不用再到数据库中去走一圈了。

    id不同打你系统。由于存在空对象缓存和缓存回写(看自己业务不限死),redis中的无关紧要的key也会越写越多(记得设置redis过期时间)。

    三、解决方案2:Google布隆过滤器Guava解决缓存穿透

    Guava适合应用在单机项目。

    1、pom文件

            <!--guava Google 开源的 Guava 中自带的布隆过滤器-->
            <dependency>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
                <version>23.0</version>
            </dependency>

    2、HelloWorld级的demo

        @Test
        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));
        }

    3、100w数据的误判率的demo

    public class BloomfilterDemo {
        public static final int _1W = 10000;
        //布隆过滤器里预计要插入多少数据
        public static int size = 100 * _1W;
        //误判率
        public static double fpp = 0.03;
    
        public static void main(String[] args) {
            // 构建布隆过滤器
            BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);
            //  1 先往布隆过滤器里面插入100万的样本数据
            for (int i = 0; i < size; i++) {
                bloomFilter.put(i);
            }
            //  故意取10万个不在过滤器里的值,看看有多少个会被认为在过滤器里
            List<Integer> list = new ArrayList<>(10 * _1W);
            for (int i = size + 1; i < size + 100000; i++) {
                if (bloomFilter.mightContain(i)) {
                    System.out.println(i + "\t" + "被误判了.");
                    list.add(i);
                }
            }
            System.out.println("误判的数量:" + list.size());
        }
    }

    输出:

    ...
    1099707 被误判了.
    1099850 被误判了.
    1099882 被误判了.
    1099947 被误判了.
    误判的数量:3033

     4、误判率分析

     fpp=0.03,它越小误判的个输也就越少。这样是不是可以设置无限小?

    源码底层会根据设置的误判率,不断添加hash函数和布隆过滤器的 bit 位。因此误判率设置的越小,内存开销越大,执行效率也越低,计算量急剧增大。0.03 是默认系数。

    5、布隆过滤器常用方案

    四、解决方案3:Redis布隆过滤器解决缓存穿透

    为了Guava只能单机使用的这个问题,诞生了 Redis 中的布隆过滤器。

    1、白名单demo

     

    public class RedissonBloomFilterDemo {
        public static final int _1W = 10000;
        //布隆过滤器里预计要插入多少数据
        public static int size = 100 * _1W;
        //误判率,它越小误判的个数也就越少
        public static double fpp = 0.03;
    
        static RedissonClient redissonClient = null;
        static RBloomFilter rBloomFilter = null;
    
        static {
            Config config = new Config();
            config.useSingleServer().setAddress("redis://192.168.1.101:6379").setDatabase(0);
            //构造redisson
            redissonClient = Redisson.create(config);
            //通过redisson构造rBloomFilter
            rBloomFilter = redissonClient.getBloomFilter("phoneListBloomFilter", new StringCodec());
    
            rBloomFilter.tryInit(size, fpp);
    
            //  测试一、布隆过滤器有+redis有
            rBloomFilter.add("10086");
            redissonClient.getBucket("10086", new StringCodec()).set("chinamobile10086");
    
            //  测试二、隆过滤器有+redis无
            //rBloomFilter.add("10087");
    
            //  测试三、都没有
    
        }
    
        public static void main(String[] args) {
            String phoneListById = getPhoneListById("10086");
            System.out.println("------查询出来的结果: " + phoneListById);
    
            //暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            redissonClient.shutdown();
        }
    
        private static String getPhoneListById(String IDNumber) {
            String result = null;
    
            if (IDNumber == null) {
                return null;
            }
            //  1 先去布隆过滤器里面查询
            if (rBloomFilter.contains(IDNumber)) {
                //  2 布隆过滤器里有,再去redis里面查询
                RBucket<String> rBucket = redissonClient.getBucket(IDNumber, new StringCodec());
                result = rBucket.get();
                if (result != null) {
                    return "i come from redis: " + result;
                } else {
                    result = getPhoneListByMySQL(IDNumber);
                    if (result == null) {
                        return null;
                    }
                    // 重新将数据更新回redis
                    redissonClient.getBucket(IDNumber, new StringCodec()).set(result);
                }
                return "i come from mysql: " + result;
            }
            return result;
        }
    
        private static String getPhoneListByMySQL(String IDNumber) {
            return "chinamobile" + IDNumber;
        }
    }
  • 相关阅读:
    十万个为什么 —— 理化篇
    地行、地貌
    工业镜头基础知识整理
    parfor —— matlab 下的并行循环
    浅谈设计模式在GIS中的应用
    android ellipsize 属性详解
    Servlet 学习总结-1
    搜索引擎有用的外链建设的方式
    Servlet 学习总结-2
    如何用批处理命令批量配对重命名
  • 原文地址:https://www.cnblogs.com/shiblog/p/15826678.html
Copyright © 2020-2023  润新知