Redis7之缓存预热 + 缓存雪崩 + 缓存击穿 + 缓存穿透(八)

这篇具有很好参考价值的文章主要介绍了Redis7之缓存预热 + 缓存雪崩 + 缓存击穿 + 缓存穿透(八)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

8.1 缓存预热

8.1.1 是什么

缓存预热就是系统上线后,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

8.1.2 解决

使用 @PostConstruct 初始化白名单数据

8.2 缓存雪崩

8.2.1 是什么

缓存雪崩就是瞬间过期数据量太大,导致对数据库服务器造成压力。

8.2.2 发生

  • redis 主机挂了, Redis全盘崩溃,偏硬件运维
  • redis 中有大量key 同时过期大面积失效,偏软件开发

8.2.3 预防 + 解决

  • redis 中 key 设置为永不过期 or 过期时间错开
  • redis 缓存集群实现高可用
    • 主从 + 哨兵
    • Redis 集群
    • 开启Redis 持久化机制 aof / rdb,尽快恢复缓存集群
  • 多缓存结合预防雪崩
    • ehcache 本地缓存 + redis缓存
  • 服务降级
    • Hystrix 或者 sentinel 限流 & 降级

8.3 缓存穿透

8.3.1 是什么

缓存穿透 就是请求去查询一条数据,先查redis,redis里面没有,再查mysql,mysql里面无,都查询不到该条记录,但是请求每次都会打到数据库上面去,导致后台数据库压力暴增

8.3.2 解决

Redis7之缓存预热 + 缓存雪崩 + 缓存击穿 + 缓存穿透(八)

Redis7之缓存预热 + 缓存雪崩 + 缓存击穿 + 缓存穿透(八)

1 空对象缓存或者缺省值

如果发生缓存穿透,可以针对要查询的数据,在Redis里存一个和业务部门商量后确定的缺省值(比如 零、负数、defaultNull等)

    public Customer findCustomerById(Integer customerId) {
        Customer customer = null;
        // 缓存redis的key名称
        String key = CACHE_KEY_CUSTOMER + customerId;
        // 1.去redis上查询
        customer = (Customer) redisTemplate.opsForValue().get(key);

        // 2. 如果redis有,直接返回  如果redis没有,在mysql上查询
        if (customer == null) {
            // 3.对于高QPS的优化,进来就先加锁,保证一个请求操作,让外面的redis等待一下,避免击穿mysql(大公司的操作 )
            synchronized (CustomerService.class) {
                // 3.1 第二次查询redis,加锁后
                customer = (Customer) redisTemplate.opsForValue().get(key);
                // 4.再去查询我们的mysql
                customer = customerMapper.selectByPrimaryKey(customerId);

                // 5.mysql有,redis无
                if (customer != null) {
                    // 6.把mysql查询到的数据会写到到redis, 保持双写一致性  7天过期
                    redisTemplate.opsForValue().set(key, customer, 7L, TimeUnit.DAYS);
                }else {
                    // defaultNull 规定为redis查询为空、MySQL查询也没有,缓存一个defaultNull标识为空,以防缓存穿透
                    redisTemplate.opsForValue().set(key, "defaultNull", 7L, TimeUnit.DAYS);
                }
            }
        }
        return customer;
    }

2 Google布隆过滤器Guava

案例:白名单过滤器

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

    • GUavaBloomFilterController
    import com.xfcy.service.GuavaBloomFilterService;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    
    /**
     * @author 晓风残月Lx
     * @date 2023/3/29 19:06
     * guava版的布隆过滤器  谷歌开源
     */
    @Api(tags = "gogle工具Guava处理布隆过滤器")
    @RestController
    @Slf4j
    public class GuavaBloomFilterController {
    
        @Resource
        private GuavaBloomFilterService guavaBloomFilterService;
    
        @ApiOperation("guava布隆过滤器插入100万样本数据,额外10w(110w)测试是否存在")
        @RequestMapping(value = "/guavafilter", method = RequestMethod.GET)
        public void guavaBloomFilter() {
            guavaBloomFilterService.guavaBloomFilter();
        }
    }
    
    • GUavaBloomFilterService

      import com.google.common.hash.BloomFilter;
      import com.google.common.hash.Funnels;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.stereotype.Service;
      
      import java.util.ArrayList;
      
      /**
       * @author 晓风残月Lx
       * @date 2023/3/29 19:17
       */
      @Slf4j
      @Service
      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.01;  // 这个数越小所用的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加入100w白名单数据
              for (int i = 0; i < SIZE; i++) {
                  bloomFilter.put(i);
              }
              // 2.故意取10w个不在合法范围内的数据,来进行误判率的演示
              ArrayList<Integer> list = new ArrayList<>(10 * _1W);
      
              // 3.验证
              for (int i = SIZE + 1; i < SIZE + (10 * _1W); i++){
                  if (bloomFilter.mightContain(i)){
                      log.info("被误判了:{}", i);
                      list.add(i);
                  }
              }
              log.info("误判总数量:{}", list.size());
          }
      }
      

    Redis7之缓存预热 + 缓存雪崩 + 缓存击穿 + 缓存穿透(八)

8.4 缓存击穿

8.4.1 是什么

缓存击穿就是大量请求同时查询一个key时,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去,也就是热点key突然都失效了,MySQL承受高并发量

8.4.2 解决

Redis7之缓存预热 + 缓存雪崩 + 缓存击穿 + 缓存穿透(八)

1.差异失效时间,对于访问频繁的热点key,干脆就不设置过期时间

2.互斥更新,采用双检加锁

8.4.3 案例编码(防止缓存击穿)

对于分页显示数据,在高并发下,绝对不能使用mysql,可以用redis的list结构

差异失效时间 用在双缓存架构

Redis7之缓存预热 + 缓存雪崩 + 缓存击穿 + 缓存穿透(八)

  • Product类

    import io.swagger.annotations.ApiModel;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    /**
     * @author 晓风残月Lx
     * @date 2023/3/30 9:40
     */
    @ApiModel(value = "聚划算活动product信息")
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    public class Product {
        // 产品id
        private Long id;
        // 产品名称
        private String name;
        // 产品价格
        private Integer price;
        // 产品详情
        private String detail;
    }
    
  • JHSTaskService(采用定时器将参加活动的商品加入redis)

    import com.xfcy.entities.Product;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.PostConstruct;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author 晓风残月Lx
     * @date 2023/3/30 9:43
     */
    @Service
    @Slf4j
    public class JHSTaskService {
    
        public static final String JHS_KEY = "jhs";
        public static final String JHS_KEY_A = "jhs:a";
        public static final String JHS_KEY_B = "jhs:b";
    
        @Autowired
        private 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(10000);
                Product product = new Product((long) id, "product" + i, i, "detail");
                list.add(product);
            }
            return list;
        }
    
        //@PostConstruct    // 测试单缓存
        public void initJHS(){
            log.info("启动定时器 天猫聚划算模拟开始 ===============");
    
            // 1.用线程模拟定时任务,后台任务定时将mysql里面的特价商品刷新的redis
            new Thread(() -> {
                while (true){
                    // 2.模拟从mysql查到数据,加到redis并返回给页面
                    List<Product> list = this.getProductsFromMysql();
                    // 3.采用redis list数据结构的lpush命令来实现存储
                    redisTemplate.delete(JHS_KEY);
                    // 4.加入最新的数据给redis
                    redisTemplate.opsForList().leftPushAll(JHS_KEY, list);
                    // 5.暂停1分钟,间隔1分钟执行一次,模拟聚划算一天执行的参加活动的品牌
                    try {
                        TimeUnit.MINUTES.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"t1").start();
    
        }
    
        /**
         * 差异失效时间
         */
        @PostConstruct         // 测试双缓存
        public void initJHSAB(){
            log.info("启动AB的定时器 天猫聚划算模拟开始 ===============");
            // 1.用线程模拟定时任务,后台任务定时将mysql里面的特价商品刷新的redis
            new Thread(() -> {
                while (true){
                    // 2.模拟从mysql查到数据,加到redis并返回给页面
                    List<Product> list = this.getProductsFromMysql();
    
                    // 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分钟,间隔1分钟执行一次,模拟聚划算一天执行的参加活动的品牌
                    try {
                        TimeUnit.MINUTES.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"t1").start();
        }
    }
    
  • JHSProductController类文章来源地址https://www.toymoban.com/news/detail-403124.html

    import com.xfcy.entities.Product;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.util.CollectionUtils;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.List;
    
    /**
     * @author 晓风残月Lx
     * @date 2023/3/30 9:55
     */
    @RestController
    @Slf4j
    @Api(tags = "聚划算商品列表接口")
    public class JHSProductController {
    
        public static final String JHS_KEY = "jhs";
        public static final String JHS_KEY_A = "jhs:a";
        public static final String JHS_KEY_B = "jhs:b";
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        /**
         * 分页查询:在高并发情况下,只能走redis查询,走db必定会把db打垮
         * @param page
         * @param size
         * @return
         */
        @RequestMapping(value = "/product/find", method = RequestMethod.GET)
        @ApiOperation("聚划算案例,每次1页每页5条显示")
        public List<Product> find(int page, int size) {
            List<Product> list = null;
    
            long start = (page - 1) * size;
            long end = start + size - 1;
    
            try {
                // 采用redis list结构里面的range命令来实现加载和分页
                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
    
            }
            return list;
        }
    
    
        @RequestMapping(value = "/product/findAB", method = RequestMethod.GET)
        @ApiOperation("AB双缓存架构,防止热点key突然消失")
        public List<Product> findAB(int page, int size) {
            List<Product> list = null;
    
            long start = (page - 1) * size;
            long end = start + size - 1;
    
            try {
                // 采用redis list结构里面的range命令来实现加载和分页
                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
            }
            return list;
        }
    }
    
    

8.5 总结

缓存问题 产生原因 解决方案
缓存更新不一致 数据变更、缓存时效性 同步更新、失效更新、异步更新、定时更新
缓存不一致 同步更新失败、异步更新 增加重试、补偿任务、最终一致
缓存穿透 恶意攻击 空对象缓存、bloomFilter 过滤器
缓存击穿 热点key失效 互斥更新、随即退避、差异失效时间
缓存雪崩 缓存挂掉 快速失败熔断、主从模式、集群模式

到了这里,关于Redis7之缓存预热 + 缓存雪崩 + 缓存击穿 + 缓存穿透(八)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Redis 之 缓存预热 & 缓存雪崩 & 缓存击穿 & 缓存穿透

    目录 一、缓存预热 1.1 缓存预热是什么? 1.2 解决方案: 二、缓存雪崩 2.1 缓存雪崩是什么?怎么发生的? 2.2 怎么解决 三、缓存穿透 3.1 是什么?怎么产生的呢? 3.2 解决方案  3.2.1、采用回写增强,  3.2.2、加上一个布隆过滤器, 四、缓存击穿 4.1 是什么? 4.2 怎么解决 五

    2024年02月13日
    浏览(25)
  • 第十八章_Redis缓存预热+缓存雪崩+缓存击穿+缓存穿透

    缓存预热 缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据。 可以通过@PostConstruct初始化白名单数据 缓存雪崩 发生  redis主机挂了,Redis 全盘崩溃

    2024年02月07日
    浏览(38)
  • 探讨Redis缓存问题及解决方案:缓存穿透、缓存击穿、缓存雪崩与缓存预热(如何解决Redis缓存中的常见问题并提高应用性能)

    Redis是一种非常流行的开源缓存系统,用于缓存数据以提高应用程序性能。但是,如果我们不注意一些缓存问题,Redis也可能会导致一些性能问题。在本文中,我们将探讨Redis中的一些常见缓存问题,并提供解决方案。 缓存穿透指的是当一个请求尝试访问一个不存在于缓存中的

    2024年02月03日
    浏览(49)
  • 14、缓存预热+缓存雪崩+缓存击穿+缓存穿透

    缓存预热+缓存雪崩+缓存击穿+缓存穿透 ● 缓存预热、雪崩、穿透、击穿分别是什么?你遇到过那几个情况? ● 缓存预热你是怎么做到的? ● 如何避免或者减少缓存雪崩? ● 穿透和击穿有什么区别?它两一个意思还是截然不同? ● 穿透和击穿你有什么解决方案?如何避免

    2024年02月12日
    浏览(24)
  • 07_缓存预热&缓存雪崩&缓存击穿&缓存穿透

    提前将数据从数据库同步到redis。 在程序启动的时候,直接将数据刷新到redis 懒加载,用户访问的时候,第一次查询数据库,然后将数据写入redis 发生情况 redis主机挂了,redis全盘崩溃,偏硬件运维 redis中有大量key同时过期或大面积失效,偏软件开发 预防解决方法 redis中key设

    2024年02月12日
    浏览(30)
  • redis 缓存雪崩 && 缓存击穿 && 缓存穿透

    什么是缓存雪崩 当我们提到缓存系统中的问题,缓存雪崩是一个经常被讨论的话题。缓存雪崩是指在某一时刻发生大量的缓存失效,导致瞬间大量的请求直接打到了数据库,可能会导致数据库瞬间压力过大甚至宕机。尤其在高并发的系统中,这种情况会导致连锁反应,整个系

    2024年02月07日
    浏览(37)
  • Redis 缓存穿透、缓存雪崩、缓存击穿

    缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。 常见的解决方案有两种:         缓存空对象                  优点:实现简单,维护方便                 缺点: 额外的内存消耗 可能造

    2024年02月02日
    浏览(33)
  • REDIS缓存穿透 击穿 雪崩

    一、前言   在我们日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及大数据量的需求,比如一些商品抢购的情景,或者是主页访问量瞬间较大的时候,单一使用数据库来

    2024年02月09日
    浏览(30)
  • Redis缓存穿透,雪崩,击穿

    1、定义 缓存 就是数据交换的 缓冲区 ,缓存就是 缓冲区内的数据 ,一般从数据库中获取,存储于本地代码。 由于其被 Static 修饰,所以随着类的加载而被加载到 内存之中 ,作为本地缓存,由于其又被 final 修饰,所以其引用和对象之间的关系是固定的,不能改变,因此不用担心赋值(=

    2024年02月10日
    浏览(37)
  • Redis缓存击穿、雪崩、穿透

    我们通过对数据库的数据进行查询后在redis中缓存,但是在高并发环境下,某个保存的key值在失效的瞬间被大量并发请求访问,这些持续的大并发量就会穿破缓存,直接请求数据库,会对数据库造成巨大压力,这些请求还会使数据多次写入缓存,多了许多不必要的操作。    

    2024年02月08日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包