缓存穿透、击穿、雪崩的应对策略
引言
在现代分布式系统中,缓存是提升系统性能和用户体验的关键组件。然而,不当的缓存策略可能导致缓存穿透、击穿、雪崩等问题,严重影响系统稳定性。理解这些问题的成因和解决方案,对于构建健壮的缓存系统至关重要。
缓存穿透问题分析与解决
缓存穿透的成因机制
缓存穿透是指查询一个不存在的数据,由于缓存中没有该数据,请求直接打到数据库,当大量不存在的请求访问时,会导致数据库压力过大。
// 缓存穿透问题演示
public class CachePenetrationProblem {
private RedisTemplate<String, Object> redisTemplate;
private UserService userService;
// 问题代码:直接查询不存在的数据
public User getUserProblem(String userId) {
String cacheKey = "user:" + userId;
// 1. 从缓存获取
User user = (User) redisTemplate.opsForValue().get(cacheKey);
if (user == null) {
// 2. 缓存未命中,查询数据库
user = userService.findById(userId); // 可能返回null
if (user != null) {
// 3. 如果存在,写入缓存
redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30));
}
// 4. 如果不存在,没有缓存,下次请求还会直达数据库!
}
return user;
}
// 恶意请求示例
public void simulatePenetrationAttack() {
List<String> nonExistentIds = generateNonExistentIds(1000);
nonExistentIds.parallelStream().forEach(id -> {
getUserProblem(id); // 每次都会查询数据库
});
}
private List<String> generateNonExistentIds(int count) {
List<String> ids = new ArrayList<>();
for (int i = 0; i < count; i++) {
ids.add("nonexistent_" + i);
}
return ids;
}
}
布隆过滤器解决方案
布隆过滤器是解决缓存穿透的经典方案,通过概率性数据结构快速判断数据是否存在。
// 布隆过滤器实现
public class BloomFilter<T> {
private final BitSet bitSet;
private final int bitSetSize;
private final double falsePositiveProbability;
private final int numberOfHashFunctions;
private final List<HashFunction> hashFunctions;
public BloomFilter(int expectedElements, double falsePositiveProbability) {
this.falsePositiveProbability = falsePositiveProbability;
this.bitSetSize = (int) Math.ceil(expectedElements *
(-Math.log(falsePositiveProbability) / (Math.log(2) * Math.log(2))));
this.numberOfHashFunctions = (int) Math.ceil(Math.log(2) * bitSetSize / expectedElements);
this.bitSet = new BitSet(bitSetSize);
this.hashFunctions = new ArrayList<>();
// 初始化多个哈希函数
for (int i = 0; i < numberOfHashFunctions; i++) {
hashFunctions.add(new SimpleHash(bitSetSize, i));
}
}
// 添加元素
public void add(T element) {
for (HashFunction hashFunction : hashFunctions) {
int position = hashFunction.hash(element.toString());
bitSet.set(position);
}
}
// 检查元素是否存在
public boolean contains(T element) {
for (HashFunction hashFunction : hashFunctions) {
int position = hashFunction.hash(element.toString());
if (!bitSet.get(position)) {
return false; // 肯定不存在
}
}
return true; // 可能存在
}
// 哈希函数接口
private interface HashFunction {
int hash(String value);
}
// 简单哈希函数实现
private static class SimpleHash implements HashFunction {
private int cap;
private int seed;
public SimpleHash(int cap, int seed) {
this.cap = cap;
this.seed = seed;
}
@Override
public int hash(String value) {
int result = 0;
int len = value.length();
for (int i = 0; i < len; i++) {
result = seed * result + value.charAt(i);
}
return (cap - 1) & result;
}
}
}
// 布隆过滤器缓存实现
public class BloomFilterCache {
private final BloomFilter<String> bloomFilter;
private final RedisTemplate<String, Object> redisTemplate;
private final UserService userService;
public BloomFilterCache() {
// 初始化布隆过滤器,预计100万个元素,误判率0.01
this.bloomFilter = new BloomFilter<>(1_000_000, 0.01);
initializeBloomFilter();
}
// 初始化布隆过滤器
private void initializeBloomFilter() {
List<String> allUserIds = userService.getAllUserIds();
allUserIds.forEach(id -> bloomFilter.add(id));
}
// 使用布隆过滤器的缓存获取
public User getUserWithBloomFilter(String userId) {
// 1. 先检查布隆过滤器
if (!bloomFilter.contains(userId)) {
return null; // 肯定不存在
}
// 2. 布隆过滤器通过,检查缓存
String cacheKey = "user:" + userId;
User user = (User) redisTemplate.opsForValue().get(cacheKey);
if (user == null) {
// 3. 缓存未命中,查询数据库
user = userService.findById(userId);
if (user != null) {
// 4. 存在则写入缓存
redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30));
} else {
// 5. 不存在也写入缓存(空值缓存)
redisTemplate.opsForValue().set(cacheKey, "NULL", Duration.ofMinutes(5));
}
} else if ("NULL".equals(user)) {
return null; // 空值缓存
}
return user;
}
// 动态更新布隆过滤器
public void addUserToBloomFilter(String userId) {
bloomFilter.add(userId);
}
public void removeUserFromBloomFilter(String userId) {
// 布隆过滤器不支持删除,可以使用计数布隆过滤器或重新构建
}
}
空值缓存解决方案
空值缓存是另一种解决缓存穿透的有效方案,将查询结果为空的键也缓存起来。
// 空值缓存实现
public class NullValueCache {
private RedisTemplate<String, Object> redisTemplate;
private UserService userService;
public User getUserWithNullCache(String userId) {
String cacheKey = "user:" + userId;
Object cachedValue = redisTemplate.opsForValue().get(cacheKey);
if (cachedValue != null) {
// 检查是否是空值缓存
if ("NULL".equals(cachedValue)) {
return null;
}
return (User) cachedValue;
}
// 查询数据库
User user = userService.findById(userId);
if (user != null) {
// 存在则正常缓存
redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30));
} else {
// 不存在则缓存空值
redisTemplate.opsForValue().set(cacheKey, "NULL", Duration.ofMinutes(5));
}
return user;
}
// 批量空值缓存
public Map<String, User> batchGetUsersWithNullCache(List<String> userIds) {
Map<String, User> result = new HashMap<>();
// 批量获取缓存
List<String> cacheKeys = userIds.stream()
.map(id -> "user:" + id)
.collect(Collectors.toList());
List<Object> cachedValues = redisTemplate.opsForValue().multiGet(cacheKeys);
List<String> needQueryIds = new ArrayList<>();
for (int i = 0; i < userIds.size(); i++) {
Object cachedValue = cachedValues.get(i);
if (cachedValue == null) {
needQueryIds.add(userIds.get(i));
} else if (!"NULL".equals(cachedValue)) {
result.put(userIds.get(i), (User) cachedValue);
}
}
if (!needQueryIds.isEmpty()) {
// 批量查询数据库
Map<String, User> dbResults = userService.batchFindByIds(needQueryIds);
// 批量设置缓存
Map<String, Object> cacheToSet = new HashMap<>();
for (String userId : needQueryIds) {
User user = dbResults.get(userId);
if (user != null) {
cacheToSet.put("user:" + userId, user);
result.put(userId, user);
} else {
cacheToSet.put("user:" + userId, "NULL");
}
}
redisTemplate.opsForValue().multiSet(cacheToSet);
// 设置过期时间
cacheToSet.keySet().forEach(key ->
redisTemplate.expire(key, Duration.ofMinutes(30)));
}
return result;
}
}
请求校验与过滤
通过请求参数校验来过滤无效请求。
// 请求校验缓存
public class RequestValidationCache {
private RedisTemplate<String, Object> redisTemplate;
private UserService userService;
// 用户ID格式校验
private static final Pattern USER_ID_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]{1,64}$");
public User getUserWithValidation(String userId) {
// 1. 参数校验
if (!isValidUserId(userId)) {
return null;
}
// 2. 使用空值缓存
return getUserWithNullCache(userId);
}
private boolean isValidUserId(String userId) {
return userId != null && USER_ID_PATTERN.matcher(userId).matches();
}
private User getUserWithNullCache(String userId) {
String cacheKey = "user:" + userId;
Object cachedValue = redisTemplate.opsForValue().get(cacheKey);
if (cachedValue != null) {
return "NULL".equals(cachedValue) ? null : (User) cachedValue;
}
User user = userService.findById(userId);
if (user != null) {
redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30));
} else {
redisTemplate.opsForValue().set(cacheKey, "NULL", Duration.ofMinutes(5));
}
return user;
}
// 黑名单机制
private final Set<String> blackList = ConcurrentHashMap.newKeySet();
public void addToBlackList(String userId) {
blackList.add(userId);
}
public boolean isBlackListed(String userId) {
return blackList.contains(userId);
}
public User getUserWithBlackList(String userId) {
if (isBlackListed(userId)) {
return null; // 黑名单用户直接返回
}
return getUserWithValidation(userId);
}
}
缓存击穿问题分析与解决
缓存击穿的成因机制
缓存击穿是指热点数据在缓存中过期的瞬间,大量请求同时访问数据库,导致数据库压力激增。
// 缓存击穿问题演示
public class CacheBreakdownProblem {
private RedisTemplate<String, Object> redisTemplate;
private UserService userService;
// 问题代码:热点数据过期时的并发请求
public User getHotUserProblem(String userId) {
String cacheKey = "hot_user:" + userId;
User user = (User) redisTemplate.opsForValue().get(cacheKey);
if (user == null) {
// 多个请求同时到达这里,都会查询数据库!
user = userService.findById(userId);
if (user != null) {
redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30));
}
}
return user;
}
// 模拟高并发场景
public void simulateBreakdown() {
String hotUserId = "hot_user_001";
List<CompletableFuture<User>> futures = new ArrayList<>();
for (int i = 0; i < 100; i++) {
futures.add(CompletableFuture.supplyAsync(() ->
getHotUserProblem(hotUserId)));
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.join();
}
}
分布式锁解决方案
使用分布式锁保证同一时间只有一个请求查询数据库并更新缓存。
// 分布式锁缓存实现
public class DistributedLockCache {
private RedisTemplate<String, Object> redisTemplate;
private UserService userService;
// 使用Redis实现分布式锁
public User getUserWithDistributedLock(String userId) {
String cacheKey = "user:" + userId;
String lockKey = "lock:user:" + userId;
String lockValue = UUID.randomUUID().toString();
User user = (User) redisTemplate.opsForValue().get(cacheKey);
if (user != null) {
return user;
}
try {
// 获取分布式锁
if (tryAcquireLock(lockKey, lockValue, Duration.ofSeconds(10))) {
// 双重检查
user = (User) redisTemplate.opsForValue().get(cacheKey);
if (user != null) {
return user;
}
// 查询数据库
user = userService.findById(userId);
if (user != null) {
// 随机过期时间,避免缓存雪崩
Duration expireTime = Duration.ofMinutes(25 + new Random().nextInt(10));
redisTemplate.opsForValue().set(cacheKey, user, expireTime);
}
return user;
} else {
// 获取锁失败,等待一段时间后重试
Thread.sleep(50);
return getUserWithDistributedLock(userId);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted while waiting for lock", e);
} finally {
// 释放锁
releaseLock(lockKey, lockValue);
}
}
// 尝试获取锁
private boolean tryAcquireLock(String lockKey, String lockValue, Duration expireTime) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
Boolean result = redisTemplate.execute(
new DefaultRedisScript<>("return redis.call('set', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2])", Boolean.class),
Collections.singletonList(lockKey),
lockValue,
String.valueOf(expireTime.getSeconds())
);
return Boolean.TRUE.equals(result);
}
// 释放锁
private void releaseLock(String lockKey, String lockValue) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey),
lockValue
);
}
}
逻辑过期解决方案
使用逻辑过期时间,让缓存数据在物理过期前异步更新。
// 逻辑过期缓存实现
public class LogicalExpireCache {
private RedisTemplate<String, Object> redisTemplate;
private UserService userService;
// 缓存对象包装类
public static class CacheObject<T> {
private T value;
private long expireTime; // 逻辑过期时间
public CacheObject(T value, long expireTime) {
this.value = value;
this.expireTime = expireTime;
}
public T getValue() {
return value; }
public long getExpireTime() {
return expireTime; }
public boolean isExpired() {
return System.currentTimeMillis() > expireTime; }
}
public User getUserWithLogicalExpire(String userId) {
String cacheKey = "user:" + userId;
CacheObject<User> cacheObject = (CacheObject<User>) redisTemplate.opsForValue().get(cacheKey);
if (cacheObject == null) {
return loadAndSetCache(userId, cacheKey);
}
// 检查逻辑过期
if (!cacheObject.isExpired()) {
return cacheObject.getValue();
}
// 逻辑过期,异步更新缓存
asyncUpdateCache(userId, cacheKey);
return cacheObject.getValue(); // 返回过期数据,保证可用性
}
private User loadAndSetCache(String userId, String cacheKey) {
User user = userService.findById(userId);
if (user != null) {
// 设置逻辑过期时间
long logicalExpireTime = System.currentTimeMillis() + Duration.ofMinutes(25).toMillis();
// 设置物理过期时间更长
long physicalExpireTime = System.currentTimeMillis() + Duration.ofHours(2).toMillis();
CacheObject<User> cacheObject = new CacheObject<>(user, logicalExpireTime);
redisTemplate.opsForValue().set(cacheKey, cacheObject, Duration.ofHours(2));
}
return user;
}
private void asyncUpdateCache(String userId, String cacheKey) {
// 使用线程池异步更新
CompletableFuture.runAsync(() -> {
String lockKey = "lock:async:" + cacheKey;
String lockValue = UUID.randomUUID().toString();
if (tryAcquireLock(lockKey, lockValue, Duration.ofSeconds(5))) {
try {
User user = userService.findById(userId);
if (user != null) {
long logicalExpireTime = System.currentTimeMillis() + Duration.ofMinutes(25).toMillis();
CacheObject<User> cacheObject = new CacheObject<>(user, logicalExpireTime);
redisTemplate.opsForValue().set(cacheKey, cacheObject, Duration.ofHours(2));
}
} finally {
releaseLock(lockKey, lockValue);
}
}
});
}
private boolean tryAcquireLock(String lockKey, String lockValue, Duration expireTime) {
return redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, expireTime);
}
private void releaseLock(String lockKey, String lockValue) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey),
lockValue
);
}
}
信号量限流方案
使用信号量控制并发访问数据库的请求数量。
// 信号量限流缓存
public class SemaphoreCache {
private RedisTemplate<String, Object> redisTemplate;
private UserService userService;
private final Map<String, Semaphore> semaphoreMap = new ConcurrentHashMap<>();
public User getUserWithSemaphore(String userId) {
String cacheKey = "user:" + userId;
User user = (User) redisTemplate.opsForValue().get(cacheKey);
if (user != null) {
return user;
}
// 获取信号量
Semaphore semaphore = semaphoreMap.computeIfAbsent(userId, k -> new Semaphore(1));
try {
if (semaphore.tryAcquire(5, TimeUnit.SECONDS)) {
try {
// 双重检查
user = (User) redisTemplate.opsForValue().get(cacheKey);
if (user != null) {
return user;
}
// 查询数据库
user = userService.findById(userId);
if (user != null) {
redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30));
}
return user;
} finally {
semaphore.release();
}
} else {
// 获取信号量超时,返回默认值或抛出异常
throw new RuntimeException("Too many concurrent requests for user: " + userId);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted while waiting for semaphore", e);
}
}
}
缓存雪崩问题分析与解决
缓存雪崩的成因机制
缓存雪崩是指大量缓存同时失效,导致大量请求直达数据库,造成数据库压力过大甚至宕机。
// 缓存雪崩问题演示
public class CacheAvalancheProblem {
private RedisTemplate<String, Object> redisTemplate;
private UserService userService;
// 问题代码:统一设置相同的过期时间
public void setUserWithSameExpireTime(String userId, User user) {
String cacheKey = "user:" + userId;
// 所有缓存都在同一时间过期!
redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30));
}
// 批量设置缓存,导致雪崩
public void batchSetCache(List<User> users) {
users.forEach(user -> {
setUserWithSameExpireTime(user.getId(), user);
});
}
// 模拟缓存雪崩
public void simulateAvalanche() {
List<User> hotUsers = userService.getHotUsers(1000);
batchSetCache(hotUsers);
// 30分钟后,所有缓存同时失效
// 大量请求直达数据库,造成雪崩
}
}
随机过期时间方案
为缓存设置随机的过期时间,避免同时失效。
// 随机过期时间缓存
public class RandomExpireCache {
private RedisTemplate<String, Object> redisTemplate;
private UserService userService;
public void setUserWithRandomExpire(String userId, User user) {
String cacheKey = "user:" + userId;
// 基础过期时间
Duration baseDuration = Duration.ofMinutes(20);
// 随机范围:0-10分钟
Duration randomRange = Duration.ofMinutes(new Random().nextInt(10));
// 总过期时间
Duration finalDuration = baseDuration.plus(randomRange);
redisTemplate.opsForValue().set(cacheKey, user, finalDuration);
}
// 分层缓存策略
public void setUserWithTieredExpire(String userId, User user) {
String cacheKey = "user:" + userId;
// 主缓存:较短的随机过期时间
Duration mainExpire = Duration.ofMinutes(20 + new Random().nextInt(10));
redisTemplate.opsForValue().set(cacheKey, user, mainExpire);
// 备份缓存:较长的固定过期时间
String backupKey = "user_backup:" + userId;
Duration backupExpire = Duration.ofHours(2);
redisTemplate.opsForValue().set(backupKey, user, backupExpire);
}
public User getUserWithTieredCache(String userId) {
String cacheKey = "user:" + userId;
String backupKey = "user_backup:" + userId;
// 先查主缓存
User user = (User) redisTemplate.opsForValue().get(cacheKey);
if (user != null) {
return user;
}
// 主缓存未命中,查备份缓存
user = (User) redisTemplate.opsForValue().get(backupKey);
if (user != null) {
// 重新设置主缓存
setUserWithRandomExpire(userId, user);
return user;
}
// 都未命中,查询数据库
user = userService.findById(userId);
if (user != null) {
setUserWithRandomExpire(userId, user);
}
return user;
}
}
多级缓存架构
使用多级缓存架构来分散缓存失效的压力。
// 多级缓存实现
public class MultiLevelCache {
private RedisTemplate<String, Object> redisTemplate;
private UserService userService;
// 本地缓存(L1)
private final LoadingCache<String, User> localCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(Duration.ofMinutes(5))
.build(this::loadFromRemoteCache);
// 远程缓存(L2)
public User getUserWithMultiLevel(String userId) {
try {
return localCache.get(userId);
} catch (Exception e) {
log.error("Error getting user from local cache", e);
// 降级到远程缓存
return getUserFromRemote(userId);
}
}
private User loadFromRemoteCache(String userId) {
return getUserFromRemote(userId);
}
private User getUserFromRemote(String userId) {
String cacheKey = "user:" + userId;
User user = (User) redisTemplate.opsForValue().get(cacheKey);
if (user == null) {
user = userService.findById(userId);
if (user != null) {
setUserWithRandomExpire(cacheKey, user);
}
}
return user;
}
private void setUserWithRandomExpire(String cacheKey, User user) {
Duration expireTime = Duration.ofMinutes(20 + new Random().nextInt(10));
redisTemplate.opsForValue().set(cacheKey, user, expireTime);
}
// 缓存预热
@PostConstruct
public void cacheWarmUp() {
List<String> hotUserIds = userService.getHotUserIds();
hotUserIds.parallelStream().forEach(userId -> {
try {
User user = userService.findById(userId);
if (user != null) {
localCache.put(userId, user);
String cacheKey = "user:" + userId;
Duration expireTime = Duration.ofMinutes(20 + new Random().nextInt(10));
redisTemplate.opsForValue().set(cacheKey, user, expireTime);
}
} catch (Exception e) {
log.error("Error warming up cache for user: " + userId, e);
}
});
}
}
缓存预热和降级策略
在系统启动或高峰期前进行缓存预热,同时实现降级策略。
// 缓存预热和降级
public class CacheWarmupAndFallback {
private RedisTemplate<String, Object> redisTemplate;
private UserService userService;
private final AtomicBoolean isHealthy = new AtomicBoolean(true);
// 缓存预热
@EventListener(ApplicationReadyEvent.class)
public void warmupCache() {
try {
List<String> hotUserIds = userService.getHotUserIds();
log.info("Starting cache warmup for {} hot users", hotUserIds.size());
hotUserIds.parallelStream().forEach(userId -> {
try {
User user = userService.findById(userId);
if (user != null) {
String cacheKey = "user:" + userId;
Duration expireTime = Duration.ofMinutes(20 + new Random().nextInt(10));
redisTemplate.opsForValue().set(cacheKey, user, expireTime);
}
} catch (Exception e) {
log.error("Error warming up cache for user: " + userId, e);
}
});
log.info("Cache warmup completed");
} catch (Exception e) {
log.error("Cache warmup failed", e);
}
}
// 定时缓存刷新
@Scheduled(fixedRate = 300000) // 每5分钟执行
public void scheduledCacheRefresh() {
if (!isHealthy.get()) {
return; // 系统不健康时跳过刷新
}
List<String> hotUserIds = userService.getHotUserIds();
hotUserIds.parallelStream().forEach(userId -> {
try {
String cacheKey = "user:" + userId;
Object cachedValue = redisTemplate.opsForValue().get(cacheKey);
if (cachedValue != null) {
// 延长缓存时间
redisTemplate.expire(cacheKey, Duration.ofMinutes(30));
} else {
// 重新加载数据
User user = userService.findById(userId);
if (user != null) {
Duration expireTime = Duration.ofMinutes(20 + new Random().nextInt(10));
redisTemplate.opsForValue().set(cacheKey, user, expireTime);
}
}
} catch (Exception e) {
log.error("Error refreshing cache for user: " + userId, e);
}
});
}
// 降级策略
public User getUserWithFallback(String userId) {
if (!isHealthy.get()) {
// 系统降级,返回默认值或从数据库直接获取
return userService.findById(userId);
}
try {
String cacheKey = "user:" + userId;
User user = (User) redisTemplate.opsForValue().get(cacheKey);
if (user == null) {
user = userService.findById(userId);
if (user != null) {
Duration expireTime = Duration.ofMinutes(20 + new Random().nextInt(10));
redisTemplate.opsForValue().set(cacheKey, user, expireTime);
}
}
return user;
} catch (Exception e) {
log.error("Error getting user from cache", e);
isHealthy.set(false);
// 降级到数据库
return userService.findById(userId);
}
}
// 健康检查
@Scheduled(fixedRate = 60000) // 每分钟检查一次
public void healthCheck() {
try {
String testKey = "health_check";
String testValue = "test";
redisTemplate.opsForValue().set(testKey, testValue, Duration.ofSeconds(10));
Object result = redisTemplate.opsForValue().get(testKey);
if (testValue.equals(result)) {
isHealthy.set(true);
} else {
isHealthy.set(false);
}
} catch (Exception e) {
isHealthy.set(false);
log.error("Health check failed", e);
}
}
}
综合解决方案
统一缓存管理器
将各种解决方案整合到一个统一的缓存管理器中。
// 统一缓存管理器
public class UnifiedCacheManager {
private RedisTemplate<String, Object> redisTemplate;
private UserService userService;
private BloomFilter<String> bloomFilter;
private final Map<String, Semaphore> semaphoreMap = new ConcurrentHashMap<>();
// 初始化布隆过滤器
@PostConstruct
public void initBloomFilter() {
List<String> allUserIds = userService.getAllUserIds();
this.bloomFilter = new BloomFilter<>(allUserIds.size() * 2, 0.01);
allUserIds.forEach(id -> bloomFilter.add(id));
}
public User getUser(String userId) {
// 1. 参数校验
if (!isValidUserId(userId)) {
return null;
}
// 2. 布隆过滤器检查
if (!bloomFilter.contains(userId)) {
return null;
}
// 3. 尝试从缓存获取
String cacheKey = "user:" + userId;
User user = (User) redisTemplate.opsForValue().get(cacheKey);
if (user != null) {
if ("NULL".equals(user)) {
return null;
}
return user;
}
// 4. 使用分布式锁防止缓存击穿
return getUserWithLock(userId, cacheKey);
}
private User getUserWithLock(String userId, String cacheKey) {
String lockKey = "lock:user:" + userId;
String lockValue = UUID.randomUUID().toString();
try {
if (tryAcquireLock(lockKey, lockValue, Duration.ofSeconds(10))) {
// 双重检查
User user = (User) redisTemplate.opsForValue().get(cacheKey);
if (user != null && !"NULL".equals(user)) {
return (User) user;
}
// 查询数据库
user = userService.findById(userId);
if (user != null) {
// 设置随机过期时间防止雪崩
Duration expireTime = Duration.ofMinutes(25 + new Random().nextInt(10));
redisTemplate.opsForValue().set(cacheKey, user, expireTime);
} else {
// 空值缓存
redisTemplate.opsForValue().set(cacheKey, "NULL", Duration.ofMinutes(5));
}
return user;
} else {
// 获取锁失败,等待后重试
Thread.sleep(50);
return getUser(userId);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted while waiting for lock", e);
} finally {
releaseLock(lockKey, lockValue);
}
}
private boolean isValidUserId(String userId) {
return userId != null && userId.matches("^[a-zA-Z0-9_-]{1,64}$");
}
private boolean tryAcquireLock(String lockKey, String lockValue, Duration expireTime) {
return redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, expireTime);
}
private void releaseLock(String lockKey, String lockValue) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey),
lockValue
);
}
// 批量获取
public Map<String, User> batchGetUsers(List<String> userIds) {
Map<String, User> result = new HashMap<>();
// 过滤无效ID
List<String> validIds = userIds.stream()
.filter(this::isValidUserId)
.filter(bloomFilter::contains)
.collect(Collectors.toList());
// 批量获取缓存
List<String> cacheKeys = validIds.stream()
.map(id -> "user:" + id)
.collect(Collectors.toList());
List<Object> cachedValues = redisTemplate.opsForValue().multiGet(cacheKeys);
Map<String, User> dbResults = new HashMap<>();
List<String> needQueryIds = new ArrayList<>();
for (int i = 0; i < validIds.size(); i++) {
Object cachedValue = cachedValues.get(i);
String userId = validIds.get(i);
if (cachedValue == null) {
needQueryIds.add(userId);
} else if ("NULL".equals(cachedValue)) {
result.put(userId, null);
} else {
result.put(userId, (User) cachedValue);
}
}
if (!needQueryIds.isEmpty()) {
dbResults = userService.batchFindByIds(needQueryIds);
// 批量设置缓存
Map<String, Object> cacheToSet = new HashMap<>();
for (String userId : needQueryIds) {
User user = dbResults.get(userId);
if (user != null) {
cacheToSet.put("user:" + userId, user);
result.put(userId, user);
} else {
cacheToSet.put("user:" + userId, "NULL");
}
}
if (!cacheToSet.isEmpty()) {
redisTemplate.opsForValue().multiSet(cacheToSet);
// 设置随机过期时间
cacheToSet.keySet().forEach(key -> {
Duration expireTime = Duration.ofMinutes(25 + new Random().nextInt(10));
redisTemplate.expire(key, expireTime);
});
}
}
return result;
}
}
监控和告警
建立完善的监控体系来检测缓存问题。
// 缓存监控
public class CacheMonitor {
private MeterRegistry meterRegistry;
private RedisTemplate<String, Object> redisTemplate;
// 缓存命中率监控
public void recordCacheHit(String cacheType, boolean hit) {
Counter.builder("cache.requests")
.tag("type", cacheType)
.tag("hit", String.valueOf(hit))
.register(meterRegistry)
.increment();
}
// 缓存穿透监控
public void recordCachePenetration(String userId) {
Counter.builder("cache.penetration")
.tag("user_id", userId)
.register(meterRegistry)
.increment();
}
// 缓存击穿监控
public void recordCacheBreakdown(String userId) {
Counter.builder("cache.breakdown")
.tag("user_id", userId)
.register(meterRegistry)
.increment();
}
// 缓存雪崩监控
public void recordCacheAvalanche() {
Counter.builder("cache.avalanche")
.register(meterRegistry)
.increment();
}
// 定时检查缓存健康度
@Scheduled(fixedRate = 60000) // 每分钟检查
public void checkCacheHealth() {
try {
// 检查缓存命中率
String hitRateKey = "cache.hit.rate";
String hitCount = redisTemplate.opsForValue().get("cache.hit.count");
String totalCount = redisTemplate.opsForValue().get("cache.total.count");
if (hitCount != null && totalCount != null) {
double hitRate = Double.parseDouble(hitCount) / Double.parseDouble(totalCount);
Gauge.builder("cache.hit.rate")
.register(meterRegistry, () -> hitRate);
if (hitRate < 0.8) {
// 命中率低于80%
log.warn("Cache hit rate is low: {}", hitRate);
}
}
} catch (Exception e) {
log.error("Error checking cache health", e);
}
}
}
缓存穿透、击穿、雪崩是缓存系统设计中的三大挑战。通过合理的策略选择和实现,可以有效解决这些问题。在实际应用中,应根据业务特点和性能要求,选择最适合的解决方案,并建立完善的监控体系来保障系统稳定性。
关于作者
🌟 我是suxiaoxiang,一位热爱技术的开发者
💡 专注于Java生态和前沿技术分享
🚀 持续输出高质量技术内容
如果这篇文章对你有帮助,请支持一下:
👍 点赞
⭐ 收藏
👀 关注
您的支持是我持续创作的动力!感谢每一位读者的关注与认可!