缓存穿透、雪崩、击穿

本文全部代码地址

缓存穿透

介绍

缓存穿透是指客户端请求的数据再缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库,失去了缓存保护数据库的意义.

常见的解决方案有两种:

  • 缓存空对象:当我们客户端访问不存在的数据时,先请求redis,但此时redis中没有数据,此时会访问到数据库,但是数据库中也没有数据,这个数据穿透了缓存,直击数据库,我们都知道数据库能够承载的并发不如redis这么高,如果大量的请求同时过来访问这种不存在的数据,这些请求就都会访问到数据库,简单的解决方案就是哪怕这个数据在数据库中也不存在,我们也把这个数据存入到redis中,设置一个较短的过期时间.这样,下次用户过来访问这个不存在的数据,那么在redis中也能找到这个数据就不会进入到缓存了.但是如果这条记录后来在数据库中存在对应的记录,就会造成数据短暂的不一致
  • 布隆过滤器:布隆过滤器详解博客

代码实现

/**
 * @author fanqiechaodan
 * @Classname UserServiceImpl
 * @Description
 */
@Service
@Slf4j
public class UserServiceImpl implements UserService {
    
    @Autowired
    UserMapper userMapper;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    /**
     * 储存用户信息的key前缀
     */
    private static final String REDIS_USER_PREFIX = "user_";

    @Override
    public User getUser(String id) {
        String resStr = stringRedisTemplate.opsForValue().get(REDIS_USER_PREFIX + id);
        if (StringUtils.isNotBlank(resStr)) {
            log.info("从redis获取返回...");
            return JSON.parseObject(resStr, User.class);
        }
        if (StringUtils.EMPTY.equals(resStr)) {
            log.info("缓存虽然命中,但是为StringUtils.EMPTY,直接返回null");
            return null;
        }
        log.info("从mysql获取...");
        User res = userMapper.getById(id);
        if (Objects.isNull(res)) {
            log.info("从mysql获取到的仍然为null,缓存空对象,设置较短过期时间");
            stringRedisTemplate.opsForValue().set(REDIS_USER_PREFIX + id, StringUtils.EMPTY, 60, TimeUnit.SECONDS);
            return res;
        }
        log.info("从mysql获取到的不为null设置较长的过期时间");
        stringRedisTemplate.opsForValue().set(REDIS_USER_PREFIX + id, JSON.toJSONString(res), 5, TimeUnit.MINUTES);
        return res;
    }
    
}

缓存雪崩

介绍

缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力.

常用的解决方案:

  • 给不同key的TTL添加随机值
  • 利用redis集群提高服务的可用性

代码实现

/**
 * @author fanqiechaodan
 * @Classname UserServiceImpl
 * @Description
 */
@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Autowired
    UserMapper userMapper;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    /**
     * 储存用户信息的key前缀
     */
    private static final String REDIS_USER_PREFIX = "user_";

    @Override
    public User getUser(String id) {
        String resStr = stringRedisTemplate.opsForValue().get(REDIS_USER_PREFIX + id);
        if (StringUtils.isNotBlank(resStr)) {
            log.info("从redis获取返回...");
            return JSON.parseObject(resStr, User.class);
        }
        log.info("从mysql获取...");
        User res = userMapper.getById(id);
        if (Objects.nonNull(res)) {
            log.info("从mysql获取到的不为null设置过期时间(5-10之间的随机数)");
            long timeout = (int) (Math.random() * 5 + 5);
            stringRedisTemplate.opsForValue().set(REDIS_USER_PREFIX + id, JSON.toJSONString(res), timeout, TimeUnit.MINUTES);
        }
        return res;
    }

}

缓存击穿

介绍

缓存击穿就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击.

常见的解决方案就是使用互斥锁来解决.因为锁能实现互斥性,假设现在线程1过来访问,他查询缓存没有命中,但是此时他获得到了锁的资源,那么线程1就会一个人去查询数据库构建缓存,假设现在线程2过来,线程2在执行过程中,并没有获得到锁,那么线程2就可以进行休眠,然后重试,直到线程1重新构建完缓存把锁释放返回后,线程2再来执行,此时就能从缓存中拿到数据了.

代码实现

/**
 * @author fanqiechaodan
 * @Classname UserServiceImpl
 * @Description
 */
@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Autowired
    UserMapper userMapper;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    /**
     * 储存用户信息的key前缀
     */
    private static final String REDIS_USER_PREFIX = "user_";

    /**
     * redis加锁前缀
     */
    private static final String REDIS_LOCK_PREFIX = "lock_";

    @Override
    public User getUser(String id) {
        String resStr = stringRedisTemplate.opsForValue().get(REDIS_USER_PREFIX + id);
        if (StringUtils.isNotBlank(resStr)) {
            log.info("从redis获取返回...");
            return JSON.parseObject(resStr, User.class);
        }
        // 使用redis进行加锁
        Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_PREFIX + id, "1", 10, TimeUnit.SECONDS);
        User res = null;
        try {
            if (lockFlag) {
                log.info("加锁成功,重新构建缓存...");
                res = userMapper.getById(id);
                stringRedisTemplate.opsForValue().set(REDIS_USER_PREFIX + id, JSON.toJSONString(res), 5, TimeUnit.MINUTES);
            } else {
                log.info("加锁失败,休眠进行重试...");
                Thread.sleep(50);
                getUser(id);
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            if (lockFlag) {
                stringRedisTemplate.delete(REDIS_LOCK_PREFIX + id);
            }
        }
        return res;
    }

}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

.番茄炒蛋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值