数据库运维中的缓存架构选型与Redis 7.x新特性
数据库运维实践中,缓存层是读写性能的分水岭。一个高并发读场景直接查MySQL,QPS上限约3000(单实例),加Redis缓存后读QPS可达5万+。但缓存不是简单加一层Redis就完事——缓存击穿、缓存雪崩、数据一致性是三大核心难题。Redis 7.x引入了Function特性(替代Lua脚本)、Multi-part AOF和ACL v2,为多级缓存架构提供了更可靠的底层支撑。
多级缓存架构设计:本地缓存+Redis+数据库分层
多级缓存的核心思路是在请求链路上设置多层缓存,逐层拦截热点查询。典型三层架构:
请求 → 本地缓存(Caffeine) → Redis集群 → MySQL主从
本地缓存层配置(Spring Boot集成Caffeine):
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(Duration.ofMinutes(5))
.recordStats());
return manager;
}
@Bean
public RedisCacheManager redisCacheManager(
RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration
.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeValuesWith(
RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer())
);
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
本地缓存5分钟过期,Redis缓存30分钟过期,两个过期时间必须错开。本地缓存处理极热点数据(如首页配置),Redis缓存处理中频查询,MySQL兜底。本地缓存命中率目标40%+,Redis命中率85%+,整体缓存命中率95%以上。
缓存击穿防御:互斥锁与逻辑过期双方案
缓存击穿指高并发请求同时穿透到数据库查同一条热点数据。两种防御方案:
方案一:Redis分布式锁(Mutex Lock)
public String getWithMutex(String key) {
String value = redisTemplate.opsForValue().get(key);
if (value != null) {
return value;
}
String lockKey = "lock:" + key;
try {
// 尝试获取互斥锁,等待3秒
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (locked) {
// 双重检查
value = redisTemplate.opsForValue().get(key);
if (value != null) {
return value;
}
// 查数据库
value = queryFromDB(key);
redisTemplate.opsForValue()
.set(key, value, 30, TimeUnit.MINUTES);
} else {
// 等待重试
Thread.sleep(50);
return getWithMutex(key);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
redisTemplate.delete(lockKey);
}
return value;
}
方案二:逻辑过期(Logical Expiry)
缓存永不过期(TTL设为永久),数据内嵌逻辑过期时间。读取时检查逻辑过期时间,过期则异步刷新:
@Data
public class CacheData<T> {
private T data;
private LocalDateTime expireTime;
}
public T getWithLogicalExpire(String key) {
CacheData<T> cache = (CacheData<T>) redisTemplate
.opsForValue().get(key);
if (cache == null) {
return null;
}
if (cache.getExpireTime().isAfter(LocalDateTime.now())) {
return cache.getData();
}
// 逻辑过期,触发异步刷新
String lockKey = "lock:" + key;
if (redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS)) {
CompletableFuture.runAsync(() -> {
try {
T freshData = queryFromDB(key);
CacheData<T> newCache = new CacheData<>();
newCache.setData(freshData);
newCache.setExpireTime(
LocalDateTime.now().plusMinutes(30));
redisTemplate.opsForValue().set(key, newCache);
} finally {
redisTemplate.delete(lockKey);
}
});
}
// 返回旧数据,用户无感知
return cache.getData();
}
逻辑过期方案的代价是短暂的数据不一致窗口,但优势是永远不会击穿到数据库。电商秒杀等极端高并发场景优先用逻辑过期,数据一致性要求高的场景用互斥锁。
缓存雪崩防御:随机TTL与熔断降级
缓存雪崩指大量缓存key在同一时刻过期,瞬间大量请求涌入数据库。防御手段:TTL添加随机偏移量。
public void setWithRandomTTL(String key, Object value,
long baseMinutes) {
long randomOffset = ThreadLocalRandom.current()
.nextLong(0, baseMinutes / 2);
long ttl = baseMinutes + randomOffset;
redisTemplate.opsForValue()
.set(key, value, ttl, TimeUnit.MINUTES);
}
// 基础TTL 30分钟 + 0~15分钟随机偏移
setWithRandomTTL("user:profile:123", data, 30);
加上熔断降级策略——当Redis集群不可用时,通过Hystrix或Sentinel的熔断器快速返回降级数据,防止请求全部打到MySQL:
@SentinelResource(value = "getUserProfile",
fallback = "getUserProfileFallback")
public User getUserProfile(Long userId) {
return cacheService.getWithLogicalExpire(
"user:profile:" + userId);
}
public User getUserProfileFallback(Long userId) {
// 返回兜底数据或静态页面
return User.defaultProfile(userId);
}
Redis 7.x的Function特性可以将互斥锁+逻辑过期封装为Redis Function,原子性比Lua更可控。数据库高可用架构中,缓存层的健壮性直接决定了整体系统的抗压能力——缓存设计不是可选优化,是必做项。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/redis7x-duo-ji-huan-cun-jia-gou-she-ji-yu-huan-cun-ji-chuan/