这可能是最全且带示例的缓存穿透、缓存击穿、缓存雪崩的解释与总结

案例演示缓存穿透、缓存击穿、缓存雪崩

缓存处理流程分析:

前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库更新到缓存,并返回结果,数据库也没取到则返回空结果。

缓存穿透:

描述:

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,这种请求会导致数据库压力过大从而引发宕机

解决方案:

  1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
  2. 从缓存取到不同的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存设置的时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样也可以防止攻击的用户反复用同一个id暴力攻击
  3. 使用布隆过滤器
    /**
     * 查询spu商品库存
     * @param goodsId
     * @return
     */
    public Map<Object, Object> getItemsStock(long goodsId) {
        //防止缓存穿透,非法id直接返回
        if(goodsId <= 0){
            Map<Object, Object> result = new HashMap<>();
            result.put("0", 0);
            return result;
        }
        //查询数据库之前先查询缓存
        Map<Object, Object> entries = redisTemplate.opsForHash().entries("goodsstock:" + goodsId);
        if (entries != null && !entries.isEmpty()) {
            return entries;
        }
        //查询数据库
        List<Item> itemList = goodsFeign.getItemList(goodsId);

        //判断数据库中查不到数据的话,添加空值缓存,防止缓存穿透
        if(itemList == null || itemList.isEmpty()){
            // 将空数据进行缓存起来
            redisTemplate.opsForHash().put("goodsstock:" + goodsId, "0", "0");
            redisTemplate.expire("goodsstock:" + goodsId, 5, TimeUnit.MINUTES);
            Map<Object, Object> result = new HashMap<>();
            result.put("0", "0");
            return result;
        }

        Map<Object, Object> result = new HashMap<>();
        itemList.forEach(item -> {
            result.put(item.getId(), item.getNum());
            //添加到缓存
            redisTemplate.opsForHash().put("goodsstock:" + goodsId, item.getId().toString(), item.getNum().toString());
        });
        //提高缓存的利用率设置缓存的过期时间
        redisTemplate.expire("goodsstock:" + goodsId, 1, TimeUnit.DAYS);
        return result;
    }

在这里插入图片描述

正常的库存:
在这里插入图片描述

非法库存:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

当然,也可以使用过滤器直接对其进行拦截或者使用zull / gateway网关对这些直接进行拦截,由于比较简单就不再赘述。

缓存击穿:

描述:

缓存击穿是指缓存中没有数据但数据库中有数据(一般是缓存过期),这时由于并发用户特别多,同时读取缓存没有读到数据,又同时取数据库取数据,引起数据压力过大导致宕机

解决方案

  1. 设置热点数据永不过期,热点数据只要连续访问即可达到“永不过期”的状态
//查询数据库之前先查询缓存
Map<Object, Object> entries = redisTemplate.opsForHash().entries("goodsstock:" + goodsId);
if (entries != null && !entries.isEmpty()) {
	//重置过期时间,连续访问就可以达到永不过期的状态
    redisTemplate.expire("goodsstock:" + goodsId, 1, TimeUnit.DAYS);
    return entries;
}
  1. 加互斥锁
//全局锁
package com.supergo.manager.config;

import org.springframework.stereotype.Component;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

@Component
public class GoodsMap {

    //使用ConcurrentHashMap,线程同步
    private ConcurrentHashMap<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();
    //获得锁
    public ReentrantLock getLock(Long goodsId){
        return lockMap.getOrDefault(goodsId, new ReentrantLock());
    }
    //释放锁
    public void removeLock(Long goodsId){
        lockMap.remove(goodsId);
    }
}
//互斥锁
/**
     * 查询spu商品库存
     * @param goodsId
     * @return
     */
public Map<Object, Object> getItemsStock(long goodsId) {
    //防止缓存穿透,非法id直接返回
    if(goodsId <= 0){
        Map<Object, Object> result = new HashMap<>();
        result.put("0", 0);
        return result;
    }

    //先获得锁, 获得goodsId对应的lock, 保证只能有一个线程访问同一个商品
    ReentrantLock lock = goodsLock.getLock(goodsId);
    if(lock.tryLock()){
        //查询数据库
        List<Item> itemList = goodsFeign.getItemList(goodsId);
        //判断数据库中查不到数据的话,添加空值缓存,防止缓存穿透
        if(itemList == null || itemList.isEmpty()){
            // 将空数据进行缓存起来
            redisTemplate.opsForHash().put("goodsstock:" + goodsId, "0", "0");
            redisTemplate.expire("goodsstock:" + goodsId, 1, TimeUnit.MINUTES);
            Map<Object, Object> result = new HashMap<>();
            result.put("0", "0");
            return result;
        }
        Map<Object, Object> result = new HashMap<>();
        itemList.forEach(item -> {
            result.put(item.getId(), item.getNum());
            //添加到缓存
            redisTemplate.opsForHash().put("goodsstock:" + goodsId, item.getId().toString(), item.getNum().toString());
        });
        //提高缓存的利用率设置缓存的过期时间
        redisTemplate.expire("goodsstock:" + goodsId, 1, TimeUnit.DAYS);
        //释放锁
        lock.unlock();
        //全局锁释放
        goodsLock.removeLock(goodsId);
        return result;
    }else{
        try {
           Thread.sleep(100);
        } catch (InterruptedException e) {
           e.printStackTrace();
        }
        //进行递归获得锁
        return getItemsStock(goodsId);
   }
}
缓存雪崩:

描述:

缓存雪崩是指缓存中大批量的key到过期时间,而引起数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿是指并发查一条数据,缓存雪崩是大量不同数据都过期了,很多数据都查不到从而查数据库。

解决方案:

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 如果缓存数据库是分布式部署的话,将热点数据均匀分布在不同的缓存数据库中。
  3. 设置热点数据永不过期
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值