欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

redis缓存预热、缓存雪崩、缓存击穿、缓存穿透、缓存降级、缓存更新

程序员文章站 2022-06-19 11:32:57
...

缓存预热

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

缓存预热就是在 redis 启动的时候,就开始往 redis 中写了数据,然后再给应用提供服务。而不是在应用访问的时候才开始往 redis 中写数据。

那么启动的时候,应该往 redis 中写入什么样的数据呢?

  • 应该根据当天的具体的访问情况,进行实时的统计出访问量频率较高的数据,即热数据,比如热门商品,热门博客等,把这部分数据提前写入 redis集群中。我们不需要把所有的数据都写入 redis中,也没有这个必要,一是数据量会很大,写入的数据会很慢,二是访问量不大的数据也没有必要写入 redis 中。
  • 如果热数据比较多的话,我们可以启动多个服务并行读取数据去写入 redis 集群,进行分布式的缓存预热。
  • 当把热数据写入 redis 集群后,才开始对处提供服务。那么这样,大部分的请求都会走 redis 集群,而不需要访问 mysql 数据库。

怎么解决?

  • 首先,通过 nginx + lua 的方式,把访问流量数据上报到 Kafka,也可以是其它的 mq 队列。
  • 然后使用实时计算框架(如 storm 、spark streaming、flume)从 kafka中消费访问流量数据,实时计算出访问频率高的数据,这里统计出来的可能只会有编号信息,如商品编号或博客编号等。
  • 最后,根据编号从 mysql 数据库中查询出具体的信息,写入 redis,开始提供服务。

还有一种解决思路:

  • 直接写个缓存刷新页面,上线时手工操作下。
  • 数据量不大,可以在WEB系统启动的时候加载。
  • 定时刷新缓存,

缓存雪崩

缓存雪崩就是瞬间过期数据量太大,导致对数据库服务器造成压力。如能够有效避免过期时间集中,可以有效解决雪崩现象的出现 (约40%),配合其他策略一起使用,并监控服务器的运行数据,根据运行记录做快速调整。

数据库服务器崩溃

  • 1、系统平稳运行过程中,忽然数据库连接量激增
  • 2、应用服务器无法及时处理请求
  • 3、大量408,500错误页面出现
  • 4、客户反复刷新页面获取数据
  • 5、数据库崩溃
  • 6、应用服务器崩溃
  • 7、重启应用服务器无效
  • 8、Redis服务器崩溃
  • 9、Redis集群崩溃
  • 10、重启数据库后再次被瞬间流量放倒

缓存雪崩

  • 1、在一个较短的时间内,缓存中较多的key集中过期
  • 2、此周期内请求访问过期的数据,redis未命中,redis向数据库获取数据
  • 3、数据库同时接收到大量的请求无法及时处理
  • 4、Redis大量请求被积压,开始出现超时现象
  • 5、数据库流量激增,数据库崩溃
  • 6、重启后仍然面对缓存中无数据可用
  • 7、Redis服务器资源被严重占用,Redis服务器崩溃
  • 8、Redis集群呈现崩塌,集群瓦解
  • 9、应用服务器无法及时得到数据响应请求,来自客户端的请求数量越来越多,应用服务器崩溃
  • 10、应用服务器,redis,数据库全部重启,效果不理想

问题分析

  • 短时间范围内
  • 大量key集中过期

解决思路

  • 1、更多的页面静态化处理
  • 2、构建多级缓存架构
    Nginx缓存+redis缓存+ehcache缓存
  • 3、检测Mysql严重耗时业务进行优化
    对数据库的瓶颈排查:例如超时查询、耗时较高事务等
  • 4、灾难预警机制
    监控redis服务器性能指标
    • CPU占用、CPU使用率
    • 内存容量
    • 查询平均响应时间
    • 线程数
  • 5、限流、降级
    短时间范围内牺牲一些客户体验,限制一部分请求访问,降低应用服务器压力,待业务低速运转后再逐步放开访问

解决方案

  • 1、LRU与LFU切换
  • 2、数据有效期策略调整
    • 根据业务数据有效期进行分类错峰,A类90分钟,B类80分钟,C类70分钟
    • 过期时间使用固定时间+随机值的形式,稀释集中到期的key的数量
  • 3、超热数据使用永久key
  • 4、定期维护(自动+人工)
    对即将过期数据做访问量分析,确认是否延时,配合访问量统计,做热点数据的延时
  • 5、加锁 慎用!

参考链接:https://blog.csdn.net/qq_29373285/article/details/88544299

缓存击穿

缓存击穿就是单个高热数据过期的瞬间,数据访问量较大,未命中redis后,发起了大量对同一数据的数据库访问,导致对数据库服 务器造成压力。应对策略应该在业务数据分析与预防方面进行,配合运行监控测试与即时调整策略,毕竟单个key的过期监控难度 较高,配合雪崩处理策略即可。

缓存击穿和缓存雪崩很相似,缓存雪崩是针对所有的key,缓存击穿针对的是某一个key

场景,假如秒杀一个商品的key,但是这个key过期了,对这个商品的并发量过高。缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。

解决方案

  • 1、设置热点数据永远不过期
  • 2、接口限流与熔断,降级。重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口中的某些 服务
    不可用时候,进行熔断,失败快速返回机制。
  • 3、布隆过滤器。bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个key是 否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小,
  • 4、加互斥锁,互斥锁参考代码如下:
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private Jedis               jedis;
    private final String        MUTEX_KEY = "MUTEX_";

    public String getData(String key) throws InterruptedException {
        String value = stringRedisTemplate.opsForValue().get(key);
        //缓存失效
        if (StringUtils.isBlank(value)) {
            //设置分布式锁,只允许一个线程去查询DB,同时指定过期时间为1min,防止del操作失败,导致死锁,缓存过期无法加载DB数据
            if (tryLock(MUTEX_KEY + key, 60L)) {
                //从数据库查询数据,将查询的结果缓存起来
                value = getValueFromDB();
                stringRedisTemplate.opsForValue().set(key, value);

                //释放分布式锁
                stringRedisTemplate.delete(MUTEX_KEY + key);
            } else {
                //当锁被占用时,睡眠5s继续调用获取数据请求
                Thread.sleep(5000);
                getData(key);}
        }
        return value;
    }

    /**
     * redis实现分布式事务锁 尝试获取锁
     * 
     * @param lockName  锁
     * @param expireTime 过期时间
     * @return
     */
    public Boolean tryLock(String lockName, long expireTime) {
        //RedisCallback redis事务管理,将redis操作命令放到事务中处理,保证执行的原子性
        String result = stringRedisTemplate.opsForValue().getOperations().execute(new RedisCallback<String>() {

            /**
             * @param key 使用key来当锁,因为key是唯一的。
             * @param value 请求标识,可通过UUID.randomUUID().toString()生成,解锁时通value参数可识别出是哪个请求添加的锁
             * @param nx 表示SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作
             * @param ex 表示过期时间的单位是秒
             * @param time 表示过期时间
             */
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                return jedis.set(lockName, UUID.randomUUID().toString(), "NX", "EX", expireTime);
            }
        });

        if ("OK".equals(result)) {
            return true;
        }
        return false;
    }

    public String getValueFromDB() {
        return "";
    }

缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求。由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。

如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。