redis 缓存雪崩、缓存击穿、缓存穿透

一、缓存雪崩

描述

大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。

简单的例子
目前电商首页以及热点数据都会去做缓存 ,一般缓存都是定时任务去刷新,或者是查不到之后去更新的,定时任务刷新就有一个问题。如果所有首页的Key失效时间都是12小时,中午12点刷新的,零点有个秒杀活动大量用户涌入,假设当时每秒 6000 个请求,本来缓存在可以扛住每秒 5000 个请求,但是缓存当时所有的Key都失效了。此时 1 秒 6000 个请求全部落数据库,数据库必然扛不住,它会报一下警,真实情况可能DBA都没反应过来就直接挂了。此时,如果没用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。
解决方案
 给缓存设置过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。
  
setRedis(Key,value,time + Math.random() * 10000)

如果Redis是集群部署,将热点数据均匀分布在不同的Redis库中也能避免全部失效的问题,或者设置热点数据永远不过期,有更新操作就更新缓存就好了(比如运维更新了首页商品,那你刷下缓存就完事了,不要设置过期时间),电商首页的不频繁更新的数据也可以用这个操作。

二、缓存穿透

描述:

访问一个不存在的key,缓存不起作用,请求会穿透到DB,同时数据库也没有相应数据,流量大时DB会挂掉

简单的例子:

用户不断发起请求,数据库的 id 都是1开始自增上去的,如发起为id值为 -1 的数据或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大,严重会击垮数据库。

 

 

 

 解决方案:

1、在接口层增加校验,比如用户鉴权校验,参数做校验,不合法的参数直接代码Return,比如:id 做基础校验,id <=0的直接拦截

2、采用布隆过滤器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤;

3、访问key未在DB查询到值,也将空值写进缓存,但可以设置较短过期时间。

三、缓存击穿

描述:

一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。

解决方案:

在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。此处采用redlock加锁。

const Redis = require("ioredis");
const Redlock = require('redlock');
const redis = new Redis({
    port: 6379, // Redis port
    host: "127.0.0.1", // Redis host
    db: 13,
  });
const redlock = new Redlock(
    [redis],
    {
        driftFactor: 0.01, // time in ms
        retryCount:  10,
        // the time in ms between attempts
        retryDelay:  200, // time in ms
        retryJitter:  200 // time in ms
    }
);
const resource = 'locks:account:322456';
const ttl = 3000;
async function getData(key){
    let result = await redis.get(key);
    if(!result){
        //获得锁
        try {
            const lock = await redlock.lock(resource, ttl);
            if(lock){
                const dbData = await getDataFromDb(key);
                if(dbData){
                    await redis.set(key,JSON.stringify(dbData),'PX',300000,'NX');
                }
                //释放锁
                await lock.unlock();
            }else{
                // 等待一会继续
                await new Promise(resolve => setTimeout(resolve,3000));
                result =  await getData(key);
            }
        } catch (error) {
            
        }
       
    }
    return result;
}
// 数据库查询
async function getDataFromDb(key){

}

 参考:

https://juejin.im/post/5dbef8306fb9a0203f6fa3e2#heading-9

https://www.csdn.net/gather_2e/MtTaQgysNTg1OTItYmxvZwO0O0OO0O0O.html

posted @ 2020-06-02 23:48  不知不觉、  阅读(365)  评论(0编辑  收藏  举报