[转] Redis 缓存穿透、缓存击穿、缓存雪崩

参考链接: https://juejin.cn/post/6844904132323590151

2个月前 143次点击 来自 后端

原文地址:图解缓存穿透、缓存击穿、缓存雪崩的区别,附解决方法

最早接触3个概念是看尚硅谷的教学视频,里面也提供相应的代码解决方案,缓存3种问题都发生在没有命中缓存的情况下 - 缓存穿透、缓存击穿、缓存雪崩,以下的解决过程已经处理完以上三种情况:

    public PmsSkuInfo getSkuById(String skuId,String ip) {
        System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"进入的商品详情的请求");
        PmsSkuInfo pmsSkuInfo = new PmsSkuInfo();
        // 链接缓存,查询缓存
        Jedis jedis = redisUtil.getJedis();
        String skuKey = "sku:"+skuId+":info";
        String skuJson = jedis.get(skuKey);

        if(StringUtils.isNotBlank(skuJson)){
            System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"从缓存中获取商品详情");

            pmsSkuInfo = JSON.parseObject(skuJson, PmsSkuInfo.class);
        }else{
            // 如果缓存中没有,查询mysql
            System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"发现缓存中没有,申请缓存的分布式锁:"+"sku:" + skuId + ":lock");

            // 设置分布式锁
            String token = UUID.randomUUID().toString();
            String OK = jedis.set("sku:" + skuId + ":lock", token, "nx", "px", 10*1000);// 拿到锁的线程有10秒的过期时间
            if(StringUtils.isNotBlank(OK)&&OK.equals("OK")){
                // 设置成功,有权在10秒的过期时间内访问数据库
                System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"有权在10秒的过期时间内访问数据库:"+"sku:" + skuId + ":lock");

                pmsSkuInfo =  getSkuByIdFromDb(skuId);

                if(pmsSkuInfo!=null){
                    // mysql查询结果存入redis
                    jedis.set("sku:"+skuId+":info",JSON.toJSONString(pmsSkuInfo));
                }else{
                    // 数据库中不存在该sku
                    // 为了防止缓存穿透将,null或者空字符串值设置给redis
                    jedis.setex("sku:"+skuId+":info",60*3,JSON.toJSONString(""));
                }

                // 在访问mysql后,将mysql的分布锁释放
                System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"使用完毕,将锁归还:"+"sku:" + skuId + ":lock");
                String lockToken = jedis.get("sku:" + skuId + ":lock");
								
								// 处理锁过期 使用UUID生成随机识别用户锁
                if(StringUtils.isNotBlank(lockToken)&&lockToken.equals(token)){
                    //jedis.eval("lua");可与用lua脚本,在查询到key的同时删除该key,防止高并发下的意外的发生
                    jedis.del("sku:" + skuId + ":lock");// 用token确认删除的是自己的sku的锁
                }
            }else{
                // 设置失败,自旋(该线程在睡眠几秒后,重新尝试访问本方法)
                System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"没有拿到锁,开始自旋");

                return getSkuById(skuId,ip);
            }
        }
        jedis.close();
        return pmsSkuInfo;
    }

虽然3个概念都懂,也能简单的处理相关的问题,但是上次面试问到相关问题依旧结结巴巴的,使用此文来复习下八股面试。

1.缓存处理流程

1.1 前台get请求,后台服务从缓存中获取数据

redis请求流程图

1.2 前台post/put请求来创建或修改数据,后台缓存处理流程

set Redis

2.缓存穿透、缓存击穿、缓存雪崩

2.1 缓存穿透
  • 缓存穿透是指缓存和数据库中都没有的数据。用户不断发起请求,caceh拦截不了,直接使用数据库查询数据,可能会对数据库性能造成影响。
  • 图解

缓存穿透

  • 解决方法(1.设置key-value,value的值为null; 2.将用户请求拦截在上游)

缓存穿透解决方法

2.2 缓存击穿
  • 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期)。由于用户特别多,同时去读取数据,引起数据库压力瞬间增大。
  • 图解

缓存击穿

  • 解决方法(1.设置热点数据不过期;2.加互斥锁,附参考代码) Redis回收机制不会动没有设置有效期的数据。所以未设置过期时间的cache会长期有效。
# 伪代码
dict_lock = {'lock':'开'}  # 设置初始锁状态

url = ['^RebbitMQ$']  # 任务消息队列路由设置

RebbitMQ = ['server1','server2', . . .]  # 服务线程添加到queue中

def open_lock(view):
    """获取数据操作权限,关锁"""
    dict_lock.get('lock') = '关'
    return Response(0)

def close_lock(view):
    """数据使用完毕,释放资源"""
    dict_lock.get('lock') = '开'
    return Response(1)
    
# 队列先进先出
RebbitMQ.last.open_lock()  # 队列最后一个元素操作数据
RebbitMQ.last.delete()  # 数据操作完毕,删除这个元素
RebbitMQ.close_lock()  # 释放资源

2.3 缓存雪崩
  • 缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指的是并发地查询同一条数据,缓存雪崩是指不同数据都过期了,很多数据缓存拦截不了,从而查数据库。
  • 图解(同缓存击穿)
  • 解决方法
    • 1.设置随机的过期时间,防止同一时间大量的数据过期现象

    • 2.设置热点数据永不过期

    • 3.使用分布式缓存,将热点数据均匀分布在不同的缓存数据库中。

      • 分布式cache(多台服务器,每台服务器都启动了一个或多个redis服务)

分布式cache

主从服务

参考文章

Made with in Shangrao,China By Devler.

Copyright © Devler 2012 - 2022

赣ICP备19009883号-1

Top ↑