高并发下缓存失效问题-缓存穿透,缓存击穿,缓存雪崩
2022-05-29 16:22
1106 查看
1.缓存穿透
缓存穿透是指:
- 大量并发访问一个不存在的数据,先去看缓存中,发现缓存中不存在,所以就去数据库中查询,但是数据库中也不存在并且并没有把数据库中这个不存在的数据null放入缓存,导致所有查询这个不存在的请求全部压到了数据库上,失去了缓存的意义.请求特别大就会导致数据库崩掉
风险:
- 利用不存在的数据进行攻击,数据库瞬时压力增大,最终导致崩溃
- 随机key,大量攻击(预防);随机值穿透攻击
解决办法:
- 缓存null值: 针对不存在的数据,我们将null缓存并且加入短暂的过期时间
-
针对随机key穿透,我们可以使用布隆过滤器
布隆过滤器
布隆过滤器数据一致性
执行流程
2.缓存击穿
缓存击穿是指:
- 大量并发查询一个热点数据,但是呢我们的热点数据在某一刻刚好过期了,这样大量的并发请求会先经过缓存,但是缓存中没有,再进入布隆过滤器bloom保存了该热点数据的ID所以会让请求去查询数据库,结果这大量请求就把数据库压垮了
风险:
- 由于缓存某一刻会过期,刚好该时刻大量并发出来,数据库瞬时压力增大,最终导致崩溃
解决办法:
- 加锁: 本地锁: 直接使用synchronize,juc.lock不适用于分布式情况,分布式下他们只能锁住当前自己的服务
- 分布式锁:
分布式锁阶段演进
加锁,就是"抢坑位"
第一阶段
第二阶段
第三阶段
第四阶段
第五阶段
Redis原生实现分布式锁核心代码如下:
/** * 根据skuId查询商品详情 * * 使用Redis实现分布式锁: * 解决大并发下,缓存击穿|穿透问题 * * @param skuId * @return */ @Override public SkuItemTo findSkuItem(Long skuId) { // 缓存key String cacheKey = RedisConstants.SKU_CACHE_KEY_PREFIX + skuId; // 查询缓存 SkuItemTo data = cacheService.getData(RedisConstants.SKU_CACHE_KEY_PREFIX + skuId, new TypeReference<SkuItemTo>() { }); // 判断是否命中缓存 if (data == null) { // 缓存没有,回源查询数据库.但是这个操作之前先问一下bloom是否需要回源 if (skuIdBloom.contains(skuId)) { // bloom返回true说明数据库中有 log.info("缓存没有,bloom说有,回源"); SkuItemTo skuItemTo = null; // 使用UUID作为锁的值,防止修改别人的锁 String value = UUID.randomUUID().toString(); // 摒弃setnx ,加锁个设置过期时间不是原子的 // 原子加锁,防止被击穿 分布式锁 设置过期时间 Boolean ifAbsent = stringRedisTemplate.opsForValue() .setIfAbsent(RedisConstants.LOCK, value, RedisConstants.LOCK_TIMEOUT, TimeUnit.SECONDS); if (ifAbsent) { try { // 设置自动过期时间,非原子的,加锁和设置过期时间不是原子的操作,所以会出现问题 // stringRedisTemplate.expire(RedisConstants.LOCK, RedisConstants.LOCK_TIMEOUT, TimeUnit.SECONDS); // 大量请求,只有一个抢到锁 log.info(Thread.currentThread().getName() + "抢到锁,查询数据库"); skuItemTo = findSkuItemDb(skuId); // 执行回源查询数据库 // 把数据库中查询的数据缓存里存一份 cacheService.saveData(cacheKey, skuItemTo); } finally { // 解锁前有可能出现各种问题导致解锁失败,从而出现死锁 // 释放锁,非原子,不推荐使用 // String myLock = stringRedisTemplate.opsForValue().get(RedisConstants.LOCK); //删锁: 【对比锁值+删除(合起来保证原子性)】 String deleteScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Long executeResult = stringRedisTemplate.execute(new DefaultRedisScript<Long>(deleteScript,Long.class), Arrays.asList(RedisConstants.LOCK), value); // 判断是否解锁成功 if (executeResult.longValue() == 1) { log.info("自己的锁:{},解锁成功", value); stringRedisTemplate.delete(RedisConstants.LOCK); } else { log.info("别人的锁,解不了"); } } } else { // 抢锁失败,自旋抢锁. 但是实际业务为我们只需要让让程序缓一秒再去查缓存就好了 try { log.info("抢锁失败,1秒后去查询缓存"); Thread.sleep(1000); data = cacheService.getData(RedisConstants.SKU_CACHE_KEY_PREFIX + skuId, new TypeReference<SkuItemTo>() { }); return data; } catch (InterruptedException e) { } } return skuItemTo; } else { log.info("缓存没有,bloom也说没有,直接打回"); return data; } } log.info("缓存中有数据,直接返回,不回源"); // 价格不缓存,有些需要变的数据,可以"现用现拿" Result<BigDecimal> decimalResult = productFeignClient.findPriceBySkuId(skuId); if (decimalResult.isOk()) { BigDecimal price = decimalResult.getData(); data.setPrice(price); } return data; }
- Redisson框架实现分布式锁
3.缓存雪崩
缓存雪崩是指:
- 大量key同时过期,正好百万请求进来,全部要查这些数据?一查数据库就炸了
解决办法:
- 过期时间+随机值防止大面积同时失效; 单点失效,自然会由防击穿来加锁处理
@Override public void saveData(String key, Object data) { if (data == null) { // 缓存null值,防止缓存穿透.设置缓存过期时间 stringRedisTemplate.opsForValue().set(key, cacheConfig.getNullValueKey(), cacheConfig.getNullValueTimeout(), cacheConfig.getNullTimeUnit()); } else { // 为了防止缓存同时过期,发生缓存雪崩.给每个缓存过期时间加上随机值 Double value = Math.random() * 10000000L; long mill = 1000 * 60 * 24 * 3 + value.intValue(); stringRedisTemplate.opsForValue().set(key, JsonUtils.objectToJson(data), mill, cacheConfig.getDataTimeUnit()); } }
相关文章推荐
- 关于缓存穿透,缓存击穿,缓存雪崩,热点数据失效问题的解决方案
- 缓存失效、穿透、并发、雪崩问题及解决方法
- Redis缓存相关问题(雪崩、穿透、击穿、并发)
- Redis缓存穿透、缓存雪崩、redis并发问题分析
- Redis缓存穿透、缓存雪崩、Redis并发问题分析
- 缓存穿透、缓存并发、缓存失效问题以及解决方案
- redis三个缓存问题 (缓存雪崩,缓存击穿,缓存穿透)
- 缓存的穿透、击穿、以及雪崩问题的解决方案
- 缓存穿透、缓存击穿、缓存雪崩、热点数据失效
- 缓存问题(缓存穿透、缓存雪崩、缓存击穿)怎么处理?
- Redis缓存穿透、缓存雪崩、并发问题分析与解决方案
- Redis缓存穿透、缓存雪崩、redis并发问题分析
- 缓存问题 - 缓存雪崩 缓存穿透 缓存击穿
- 如何解决常见的缓存穿透、并发和失效问题?
- Redis缓存穿透、缓存雪崩、redis并发问题分析
- 如何处理缓存失效、缓存穿透、缓存并发等问题
- Redis 面试常见问题———缓存雪崩、缓存击穿以及缓存穿透
- 关于【缓存穿透,缓存击穿,缓存雪崩,热点数据丢失】问题的解决方案
- 缓存相关——缓存穿透、缓存并发、缓存失效、缓存预热、缓存雪崩、缓存算法
- 【Redis】缓存常见问题解决思路(缓存穿透、缓存雪崩、缓存击穿)