高并发场景下的缓存击穿问题探析与应对策略

📅 发布时间:2026/9/7 13:45:55
高并发场景下的缓存击穿问题探析与应对策略 目录一、问题描述二、解决策略分析一解决策略一互斥锁Mutex二解决策略二软过期互斥锁三解决策略三静态数据 Lazy Expiration 方案三、总结干货分享感谢您的阅读在高并发场景下缓存作为前置查询机制显著减轻了数据库的压力提高了系统性能。然而这也带来了缓存失效、增加回溯率等风险。常见的问题包括缓存穿透、缓存雪崩、热Key和大Key等。这些问题如果不加以处理会影响系统的稳定性和性能。因此采用有效的缓存策略如缓存空结果、布隆过滤器、缓存过期时间随机化、多级缓存等对于保障系统在高并发情况下的可靠性至关重要。本次我们将详细探讨缓存击穿及其应对策略。一、问题描述在缓存系统中有些数据可能会被频繁访问这些数据被称为热点数据。为了保证缓存的有效性缓存通常会设置一个过期时间。然而当一个热点数据的缓存失效时所有对该数据的请求会同时到达数据库。这种情况会导致以下问题数据库压力大当大量请求同时涌向数据库时数据库的负载会瞬间增加可能导致数据库性能下降甚至崩溃。无效的重复查询所有请求都试图从数据库中读取相同的数据并更新缓存这种重复的查询是无效的因为只需要一次查询结果就能满足所有请求。缓存击穿是由于热点数据的缓存失效导致的数据库压力过大问题。为了解决这个问题可以采用互斥锁、永不过期和提前预热缓存等方法。这些方法各有优缺点可以根据具体业务场景选择合适的解决方案以确保系统在高并发访问下的稳定性和高性能。二、解决策略分析一解决策略一互斥锁Mutex在缓存失效时通过加锁机制保证只有一个请求能访问数据库其余请求等待该请求完成后再返回缓存数据。具体实现可以使用分布式锁如 Redis 的分布式锁。这种方式可以有效防止缓存击穿但需要注意加锁的开销。public Object getCache(final String key) { Object value redis.get(key); if (value ! null) { return value; } synchronized (this) { value redis.get(key); if (value ! null) { return value; } value getValueFromDb(key); redis.set(key, value, expireTime); } return value; }二解决策略二软过期互斥锁软过期指的是在缓存值中存储一个逻辑过期时间这个时间比实际要过期的时间小即。在业务取值时首先校验是否过期如果过期则引入互斥锁。首先将时间延长即并设置到缓存中然后去数据库查询新数据。其他线程这时看到延长了的过期时间就会继续使用旧数据等获取最新数据的线程更新缓存后所有线程都会使用新的数据。相比单纯的互斥锁方案这种方案的优点在于进一步减少了读请求线程的阻塞时间。public class CacheService { Autowired private RedisTemplateString, CacheValue redisTemplate; Autowired private DatabaseService databaseService; private static final String MUTEX_KEY_SUFFIX :mutex; public CacheValue getCache(final String key) { CacheValue value redisTemplate.opsForValue().get(key); long currentTime System.currentTimeMillis(); if (value ! null) { // 检查缓存中的逻辑过期时间 if (value.getTimeout() currentTime) { // 如果逻辑过期时间已过期尝试获取互斥锁 if (redisTemplate.opsForValue().setIfAbsent(key MUTEX_KEY_SUFFIX, currentTime 30000)) { // 立即延长逻辑过期时间减少阻塞时间 value.setTimeout(currentTime 5000); redisTemplate.opsForValue().set(key, value, Duration.ofMinutes(1)); // 获取最新的数据库数据并重新设置新的逻辑过期时间覆盖旧数据 CacheValue newValue databaseService.getValueFromDb(key); newValue.setTimeout(currentTime 60000); redisTemplate.opsForValue().set(key, newValue, Duration.ofMinutes(1)); // 删除互斥锁 redisTemplate.delete(key MUTEX_KEY_SUFFIX); } else { // 如果没有获取到互斥锁等待一段时间后重试 try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return getCache(key); } } return value; } else { // 缓存不存在的情况与上面类似 if (redisTemplate.opsForValue().setIfAbsent(key MUTEX_KEY_SUFFIX, currentTime 30000)) { CacheValue newValue databaseService.getValueFromDb(key); newValue.setTimeout(currentTime 60000); redisTemplate.opsForValue().set(key, newValue, Duration.ofMinutes(1)); // 删除互斥锁 redisTemplate.delete(key MUTEX_KEY_SUFFIX); } else { // 如果没有获取到互斥锁等待一段时间后重试 try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return getCache(key); } } return value; } // 定义缓存值的类 public static class CacheValue { private Object data; private long timeout; public Object getData() { return data; } public void setData(Object data) { this.data data; } public long getTimeout() { return timeout; } public void setTimeout(long timeout) { this.timeout timeout; } } }通过软过期互斥锁的方案可以有效防止缓存击穿问题并减少读请求线程的阻塞时间。合理设置逻辑过期时间和互斥锁的过期时间结合重试机制可以提高系统在高并发场景下的稳定性和性能。三解决策略三静态数据 Lazy Expiration 方案静态数据缓存策略指的是在 Redis 中不设置过期时间TTL让数据看起来是“永不过期”的。实际上通过在缓存值中设置逻辑过期时间来控制数据的有效性。当逻辑过期时间到达时后台异步线程会更新缓存。这种方式性能最好因为避免了频繁的缓存失效和重建。具体实现逻辑过期时间在缓存值中设置一个逻辑过期时间当取值时判断逻辑过期时间是否已经到达。异步更新如果逻辑过期时间已过则启动一个异步线程来更新缓存而不是阻塞当前请求。互斥锁避免多线程同时更新缓存使用互斥锁来保证只有一个线程能进行更新操作。public class CacheService { Autowired private RedisTemplateString, CacheValue redisTemplate; Autowired private DatabaseService databaseService; Autowired private ExecutorService executorService; private static final String MUTEX_KEY_SUFFIX :mutex; public CacheValue getCache(final String key) { CacheValue value redisTemplate.opsForValue().get(key); long currentTime System.currentTimeMillis(); if (value ! null) { // 检查缓存中的逻辑过期时间 if (value.getTimeout() currentTime) { // 另起一个异步线程更新缓存 executorService.execute(() - { if (redisTemplate.opsForValue().setIfAbsent(key MUTEX_KEY_SUFFIX, 1)) { redisTemplate.expire(key MUTEX_KEY_SUFFIX, Duration.ofMinutes(3)); CacheValue dbValue databaseService.getValueFromDb(key); dbValue.setTimeout(currentTime 60000); // 设置新的逻辑过期时间 redisTemplate.opsForValue().set(key, dbValue); redisTemplate.delete(key MUTEX_KEY_SUFFIX); } }); } return value; } else { // 缓存不存在的情况与上面类似 if (redisTemplate.opsForValue().setIfAbsent(key MUTEX_KEY_SUFFIX, 1)) { CacheValue dbValue databaseService.getValueFromDb(key); dbValue.setTimeout(currentTime 60000); // 设置逻辑过期时间 redisTemplate.opsForValue().set(key, dbValue); redisTemplate.delete(key MUTEX_KEY_SUFFIX); } else { // 如果没有获取到互斥锁等待一段时间后重试 try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return getCache(key); } } return value; } // 定义缓存值的类 public static class CacheValue { private Object data; private long timeout; public Object getData() { return data; } public void setData(Object data) { this.data data; } public long getTimeout() { return timeout; } public void setTimeout(long timeout) { this.timeout timeout; } } }通过静态数据 Lazy Expiration 方案可以有效防止缓存击穿问题同时避免了频繁的缓存失效和重建提升系统的性能与稳定性。三、总结在高并发场景中缓存的有效性对于系统性能和稳定性至关重要。缓存击穿问题是由于热点数据缓存失效导致的数据库压力过大现象为了解决这一问题可以采取多种策略。互斥锁机制通过加锁确保只有一个请求能访问数据库防止同时访问造成的压力软过期互斥锁结合逻辑过期时间和分布式锁减少读请求线程的阻塞时间静态数据Lazy Expiration策略则通过逻辑过期时间和异步更新机制避免频繁的缓存失效和重建。通过合理选择和实施缓存策略系统可以更好地应对高并发场景下的挑战提升整体性能和用户体验。