网站主页特效欣赏,推进网站集约化建设 网络安全,校园网站建设方案,wordpress添加小人目录
介绍
缓存穿透
缓存击穿
缓存雪崩
原因
影响
解决方案
缓存穿透 防止缓存穿透-空值缓存案例 缓存击穿 使用互斥锁解决缓存击穿 介绍 缓存穿透
定义#xff1a;缓存穿透是指用户查询数据#xff0c;缓存和数据库中都不存在该数据#xff08;一般是发起恶意…目录
介绍
缓存穿透
缓存击穿
缓存雪崩
原因
影响
解决方案
缓存穿透 防止缓存穿透-空值缓存案例 缓存击穿 使用互斥锁解决缓存击穿 介绍 缓存穿透
定义缓存穿透是指用户查询数据缓存和数据库中都不存在该数据一般是发起恶意的查询试图击穿缓存直接查询数据库这时用户每次查询都会直接打到数据库上而数据库中也没有该数据如果用户不断发起这样的请求数据库压力会非常大甚至可能拖垮数据库。 解决方案 布隆过滤器Bloom Filter布隆过滤器可以快速判断一个元素是否在一个集合中但是会有一定的误判率。在数据放入缓存之前先使用布隆过滤器判断数据是否存在如果不存在则直接返回不进行数据库查询。空值缓存对于不存在的数据也在缓存中存放一个空值或者一个特殊标记这样下次查询相同的数据时可以直接返回缓存中的空值避免了对数据库的查询。但是这种方法需要设置合理的过期时间避免数据长时间不更新。请求限流对查询请求进行限流限制查询频率防止恶意查询。 缓存击穿
定义缓存击穿是指缓存中没有但数据库中有的数据一般是缓存时间到期这时由于并发用户特别多同时读缓存没读到数据又同时去数据库去取数据引起数据库压力瞬间增大造成过大压力。 解决方案 设置热点数据永不过期即逻辑缓存对于一些热点数据可以将其设置为永不过期从而避免缓存击穿问题。但是这种方法会占用较多的缓存空间需要谨慎使用。加互斥锁在查询数据库之前先尝试获取一个分布式锁如果获取到锁则查询数据库并更新缓存如果未获取到锁则等待一段时间后重试。这样可以保证同一时间只有一个请求去查询数据库从而减轻数据库压力。双缓存策略使用两个缓存一个缓存的过期时间较短用于应对大部分请求另一个缓存的过期时间较长用于应对缓存击穿的情况。当短缓存过期时先从长缓存中获取数据然后再去查询数据库并更新两个缓存。 缓存雪崩
缓存雪崩是指在缓存中存储的大量数据同时失效或过期导致缓存系统无法承载大量请求压力造成服务宕机甚至瘫痪的情况。这种情况下大量的请求会直接涌入数据库导致数据库崩溃或响应缓慢影响应用程序的正常使用。
原因
缓存雪崩通常由于以下几个原因引起
缓存过期策略如果大量的缓存数据被设置为相同的过期时间那么在这些数据同时过期时就会引发缓存雪崩。缓存服务器故障当缓存服务器如Redis宕机或网络中断时缓存服务将不可用所有请求都会直接打到数据库上。大量突发请求在特定时间段内如果请求量激增且超出缓存系统的处理能力也可能导致缓存雪崩。 影响
缓存雪崩的影响是灾难性的主要包括以下几个方面
数据库负载过高大量的请求直接打到数据库上导致数据库负载急剧增加响应时间延长。服务不可用在极端情况下数据库可能因为压力过大而崩溃导致整个服务不可用。性能瓶颈数据库成为瓶颈系统整体处理能力下降用户体验受到严重影响。连锁反应由于服务间的依赖关系缓存雪崩可能导致多个服务相继瘫痪形成连锁反应。 解决方案
为了防范和应对缓存雪崩可以采取以下策略
设置随机过期时间为不同的缓存数据设置随机的过期时间避免大量数据同时过期。限流和熔断当检测到缓存系统压力过大时通过限流组件限制请求量并在必要时采取熔断措施如返回默认数据或静态页面。主动更新缓存在缓存失效前主动提前重新加载数据至缓存以减轻数据库压力。加锁机制当缓存失效后通过加锁机制控制只有一个线程负责从数据库加载数据并回填缓存其他线程等待或返回旧数据。部署缓存集群部署Redis Sentinel或Cluster集群等缓存集群方案确保单点故障时能自动切换到其他节点。使用布隆过滤器在缓存之前使用布隆过滤器判断请求的键是否存在减少对数据库的无效访问。 缓存穿透
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在这样缓存永远不会生效这些请求都会打到数据库。给数据库带来巨大压力
常见的解决方案有两种
缓存空对象
优点实现简单维护方便缺点额外的内存消耗 可能造成短期的不一致
布隆过滤
优点内存占用较少没有多余key
缺点实现复杂 存在误判可能
还有的解决方案主动性的方案
增强id的复杂性避免被猜测id规律做好数据的基础格式校验加强用户权限校验做好热点参数的限流 防止缓存穿透-空值缓存案例
public Shop queryWithPassThrough(Long id) {String key CACHE_SHOP_KEY id;MapObject, Object shopFromCache stringRedisTemplate.opsForHash().entries(key);System.out.println(Redis: shopFromCache);// 检查 Redis 返回的 shopFromCache 是否为 nullif (shopFromCache ! null !shopFromCache.isEmpty()) {// 缓存中有值尝试反序列化 Shop 对象Shop shopFromCacheBean BeanUtil.fillBeanWithMap(shopFromCache, new Shop(), false);return shopFromCacheBean;}// Redis 中不存在从数据库中查询Shop shopFromDb baseMapper.selectById(id);// 数据库中也不存在则缓存空值以防缓存穿透if (ObjectUtil.isNull(shopFromDb)) {stringRedisTemplate.opsForHash().put(key, CACHE_SHOP_EMPTY_KEY, CACHE_SHOP_EMPTY_VALUE); // 使用特殊标记表示空值stringRedisTemplate.expire(key, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);return null;}// 将 Shop 对象转换为 Map 并写入 RedisMapString, Object shopMap BeanUtil.beanToMap(shopFromDb, new HashMap(),CopyOptions.create().ignoreNullValue().setFieldValueEditor((fieldName, fieldValue) - fieldValue ! null ? fieldValue.toString() : null));stringRedisTemplate.opsForHash().putAll(key, shopMap);// 设置超时时间stringRedisTemplate.expire(key, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);return shopFromDb;
} 缓存击穿
缓存击穿问题也叫热点key问题就是一个被高并发访问并且缓存重建业务复杂的key突然失效了无数的请求访问会在瞬间给数据库带来巨大的冲击 解决方案 优点 缺点
互斥锁 没有额外的内存消耗 线程需要等待性能受影响
保证一致性 可能有死锁风险
实现简单 逻辑过期 线程无需等待性能较好 不保证一致性
有额外内存消耗
实现复杂 使用互斥锁解决缓存击穿
Overridepublic Result queryById(Long id) throws InterruptedException {// 缓存穿透
// queryWithPassThrough(id);// 互斥锁解决缓存击穿Shop shop queryWithMutex(id);if (shopnull) {return Result.fail(商品不存在);}return Result.ok(shop);}public Shop queryWithMutex(Long id) throws InterruptedException {String key CACHE_SHOP_KEY id;MapObject, Object shopFromCache stringRedisTemplate.opsForHash().entries(key);System.out.println(Redis: shopFromCache);// 检查 Redis 返回的 shopFromCache 是否为 nullif (shopFromCache ! null !shopFromCache.isEmpty()) {// 缓存中有值尝试反序列化 Shop 对象Shop shopFromCacheBean BeanUtil.fillBeanWithMap(shopFromCache, new Shop(), false);return shopFromCacheBean;}// Redis 中不存在从数据库中查询// 4.实现缓存重建// 4.1获取互斥锁// 4.2获取失败休眠然后重试// 4.3获取成功返回数据释放锁Shop shopFromDbnull;try {boolean isLock tryLock(LOCK_SHOP_KEY id);if (!isLock) {// 拿不到锁休眠然后递归不断尝试// 这个返回结果会是在redis里拿到的数据Thread.sleep(50);return queryWithMutex(id);}// 拿到互斥锁查询数据库重建缓存shopFromDb baseMapper.selectById(id);
// 模拟重建延迟Thread.sleep(200);// 数据库中也不存在则缓存空值以防缓存穿透stringRedisTemplate.opsForHash().put(key, CACHE_SHOP_EMPTY_KEY, CACHE_SHOP_EMPTY_VALUE); // 使用特殊标记表示空值stringRedisTemplate.expire(key, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);return null;}// 将 Shop 对象转换为 Map 并写入 RedisMapString, Object shopMap BeanUtil.beanToMap(shopFromDb, new HashMap(),CopyOptions.create().ignoreNullValue().setFieldValueEditor((fieldName, fieldValue) - fieldValue ! null ? fieldValue.toString() : null));stringRedisTemplate.opsForHash().putAll(key, shopMap);// 设置超时时间stringRedisTemplate.expire(key, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {// 释放锁unLock(LOCK_SHOP_KEY id);}return shopFromDb;}
public boolean tryLock(String key) {Boolean b stringRedisTemplate.opsForValue().setIfAbsent(key, 1, 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(b);
}public void unLock(String key) {stringRedisTemplate.delete(key);
}