Redis-预热雪崩击穿穿透

预热雪崩穿透击穿

缓存预热

缓存雪崩

有这两种原因

  1. redis key 永不过期or过期时间错开
  2. redis 缓存集群实现高可用
    1. 主从哨兵
    2. Redis Cluster
    3. 开启redis持久化aof,rdb,尽快恢复集群
  3. 多缓存结合预防雪崩:本地缓存 ehcache + redis 缓存
  4. 服务降级:Hystrix 或者 sentinel 限流降级
  5. 人民币玩家:阿里云给了你多少广告?笑

缓存穿透

恶意请求不存在的数据

  1. guava BloomFilter

  • 误判问题,但是概率小可以接受,不能从布隆过滤器删除 -> 布隆过滤器可能会错误地判断某个元素存在于集合中(称为误报),但不会错误地判断一个存在的元素不存在
  • 全部合法的key都需要放入 Guava布隆过滤器+redis里面,不然数据就是返回null

案例

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version>
</dependency>
@Service
@Slf4j
public class GuavaBloomFilterService {

    // 1.定义一个常量
    public static final int _1W = 10000;
    // 2.定义我们guava布隆过滤器,初始容量
    public static final int SIZE = 100 * _1W;
    // 3.误判率,它越小误判的个数也越少(思考:是否可以无限小? 没有误判岂不是更好)
    public static double fpp = 0.0000000000003;  // 这个数越小所用的hash函数越多,bitmap占用的位越多  默认的就是0.03,5个hash函数   0.01,7个函数
    // 4.创建guava布隆过滤器
    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), SIZE, fpp);

    public void guavaBloomFilter() {
        // 1. 往 bloomFilter 中添加数据
        for (int i = 0; i < SIZE; i++) {
            bloomFilter.put(i);
        }
        // 2. 故意取10w个不在范围内的数据进行测试,来进行误判率演示
        List<Integer> list = new ArrayList<>(10 * _1W);

        // 3. 验证
        for (int i = SIZE; i < SIZE + (10 * _1W); i++) {
            if (bloomFilter.mightContain(i)) {
//                log.info("被误判了:{}", i);
                list.add(i);
            }
        }
        log.info("误判总数量:{}", list.size());
        log.info("误判率:{}", list.size() / (10 * _1W));
    }
}

@SpringBootTest
public class GuavaTest {

    @Resource
    GuavaBloomFilterService guavaBloomFilterService;

    /**
     * guava版本布隆过滤器,helloworld 入门级演示
     */
    @Test
    public void testGuavaWithBloomFilter() {
        System.out.println("testGuavaWithBloomFilter");
        // 1. 创建 guava版布隆过滤器
        BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), 100);

        //2. 判断指定的元素是否存在
        System.out.println(bloomFilter.mightContain(1));
        System.out.println(bloomFilter.mightContain(2));

        // 2. 添加数据
        bloomFilter.put(1);
        bloomFilter.put(2);

        System.out.println(bloomFilter.mightContain(1));
        System.out.println(bloomFilter.mightContain(2));
    }

    @Test
    public void testGuavaWithBloomFilter2() {
        guavaBloomFilterService.guavaBloomFilter();
    }

}

fpp 默认 0.03

fpp要求越高,bit位数越多,hash函数越多

guava 黑名单使用

缓存击穿

对比穿透和击穿

互斥更新->对于更新的方法

聚划算案例

功能分析

数据结构使用 list

代码

@ApiModel(value = "聚划算活动product信息")
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Product {
    // 产品id
    private Long id;
    // 产品名称
    private String name;
    // 产品价格
    private Integer price;
    // 产品详情
    private String detail;
}
@Service
@Slf4j
public class JHSTaskService {

    private static final String JHS_KEY = "jhs";
    private static final String JHS_KEY_A = "jhs:a";
    private static final String JHS_KEY_B = "jhs:b";

    @Autowired
    RedisTemplate redisTemplate;

    /**
     * 模拟从数据库读取20件特价商品
     * @return 商品列表
     */
    private List<Product> getProductsFromMysql() {
        List<Product> list = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            Random random = new Random();
            int id = random.nextInt(1000);
            Product product = new Product((long) id, "product" + i, i, "detail");
            list.add(product);
        }
        log.info("模拟从数据库读取20件特价商品完成{}", list);
        return list;
    }

    @PostConstruct
    public void initJHSAB() {
        log.info("启动AB的定时器 天猫聚划算模拟开始===========");
        new Thread(() -> {
            while (true) {
                // 2.模拟从mysql查到数据,加到 redis 并返回给页面
                List<Product> list = getProductsFromMysql();

                redisTemplate.delete(JHS_KEY);
                redisTemplate.opsForList().leftPushAll(JHS_KEY, list);
                redisTemplate.expire(JHS_KEY, 86410L, TimeUnit.SECONDS);


                // 5.暂停一分钟,间隔1分钟执行一次,模拟聚划算一天执行的参加活动的品牌
                try {
                    Thread.sleep(1000* 60);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1").start();
    }

}

测试类

@SpringBootTest
@Slf4j
public class JhsTest {

    private static final String JHS_KEY = "jhs";
    private static final String JHS_KEY_A = "jhs:a";
    private static final String JHS_KEY_B = "jhs:b";

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void find() {
        int page = 1;
        int size = 10;

        List<Product> list = null;

        long start = (page - 1) * size;
        long end = start + size - 1;

        try {
            list = redisTemplate.opsForList().range(JHS_KEY, start, end);

            if (CollectionUtils.isEmpty(list)) {
                // TODO 走 mysql 查询
            }

            log.info("参加活动的商家={}", list);
        } catch (Exception e) {
            // 出异常了,一般 redis 宕机了或者redis网络抖动导致timeout
            log.error("jhs exception{}", e);
            e.printStackTrace();
            //  ..... 重试机制 再次查询 mysql
        }

        log.info(list.toString());

    }

}

测试方法,先跑主启动类(后台更新聚划算商品信息),然后手动执行测试类测试查询

问题分析

delete 执行间隙,这一瞬间缓存击穿,打到mysql

解决

@PostConstruct
    public void initJHSAB() {
        log.info("启动AB的定时器 天猫聚划算模拟开始===========");
        new Thread(() -> {
            while (true) {
                // 2.模拟从mysql查到数据,加到 redis 并返回给页面
                List<Product> list = getProductsFromMysql();

//                redisTemplate.delete(JHS_KEY);
//                redisTemplate.opsForList().leftPushAll(JHS_KEY, list);
//                redisTemplate.expire(JHS_KEY, 86410L, TimeUnit.SECONDS);

                // 3.先更新B缓存并且让B缓存过期时间超过A时间,如果A突然失效了还有B兜底,防止击穿
                redisTemplate.delete(JHS_KEY_B);
                redisTemplate.opsForList().leftPushAll(JHS_KEY_B, list);
                redisTemplate.expire(JHS_KEY_B, 86410L, TimeUnit.SECONDS);

                // 4.再更新A缓存
                redisTemplate.delete(JHS_KEY_A);
                redisTemplate.opsForList().leftPushAll(JHS_KEY_A, list);
                redisTemplate.expire(JHS_KEY_A, 86400L, TimeUnit.SECONDS);

                // 5.暂停一分钟,间隔1分钟执行一次,模拟聚划算一天执行的参加活动的品牌
                try {
                    Thread.sleep(1000* 60);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1").start();
    }
@Test
    public void findAB() {
        int page = 1;
        int size = 10;

        List<Product> list = null;

        long start = (page - 1) * size;
        long end = start + size - 1;

        try {
            list = redisTemplate.opsForList().range(JHS_KEY_A, start, end);

            if (CollectionUtils.isEmpty(list)) {
                log.info("---------A缓存已经过期或活动结束了,记得人工修补,B缓存继续顶着");

                // A 没有来找 B
                list = redisTemplate.opsForList().range(JHS_KEY_B, start, end);

                if (CollectionUtils.isEmpty(list)) {
                    // TODO 走 mysql 查询
                }
            }

            log.info("参加活动的商家={}", list);
        } catch (Exception e) {
            // 出异常了,一般 redis 宕机了或者redis网络抖动导致timeout
            log.error("jhs exception{}", e);
            e.printStackTrace();
            //  ..... 重试机制 再次查询 mysql
        }

        log.info(list.toString());

    }

小总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值