在分布式系统中,Redis作为分布式缓存已广泛应用。但当系统面临超高并发读取(如热点商品详情页访问)或超低延迟要求(如金融行情数据推送)时,纯远程缓存面临两大瓶颈:
通过引入Caffeine本地缓存作为一级缓存,Redis作为二级缓存,可实现:
public User getUserById(Long userId) {
String key = "user:" + userId;
// 1. 先查Caffeine
User user = caffeineCache.get(key, k -> {
// 2. 未命中则查Redis
Object obj = redisTemplate.opsForValue().get(k);
if (obj != null) return obj;
// 3. Redis未命中查DB
User dbUser = userMapper.selectById(userId);
redisTemplate.opsForValue().set(k, dbUser, 30, TimeUnit.SECONDS);
return dbUser;
});
return user;
}
命中率提升效果:本地缓存可达95%+,整体命中率99%+
指标 | 纯Redis缓存 | 两级缓存架构 | 提升幅度 |
---|---|---|---|
平均响应时间 | 1-10ms | 100ns-1ms | 10-100倍 |
数据库请求量 | 100% | <1% | 99%+ |
Redis带宽占用 | 100% | 10%-30% | 70%-90% |
挑战一:缓存一致性
// 配置Redis消息监听
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory factory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(factory);
container.addMessageListener((message, pattern) -> {
String key = new String(message.getBody());
caffeineCache.invalidate(key); // 失效本地缓存
}, new ChannelTopic("cacheEvict"));
return container;
}
// 数据更新时发布消息
public void updateUser(User user) {
userMapper.updateById(user);
redisTemplate.delete(key);
redisTemplate.convertAndSend("cacheEvict", key); // 发布失效通知
}
实测效果:万级QPS下,缓存同步延迟<5ms
挑战二:缓存穿透/雪崩
Caffeine.newBuilder()
.maximumSize(10_000) // 限制本地缓存数量
.expireAfterWrite(10, TimeUnit.SECONDS) // 短TTL
.refreshAfterWrite(1, TimeUnit.SECONDS) // 异步刷新
.recordStats() // 开启监控
配合Redis:spring:
redis:
timeout: 100ms # 快速失败
lettuce:
pool:
max-active: 200 # 连接池优化
适用场景:需要精细控制缓存逻辑
// 手动查询两级缓存
public User queryUser(long userId) {
String key = "user-" + userId;
return (User) caffeineCache.get(key, k -> {
Object redisVal = redisTemplate.opsForValue().get(k);
if (redisVal != null) return redisVal;
return userMapper.selectById(userId);
});
}
优点:完全掌控缓存逻辑
缺点:代码侵入性强
配置示例:
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.SECONDS)
.maximumSize(1000));
return manager;
}
}
// 业务层使用
@Service
public class UserService {
@Cacheable(value="users", key="#userId", condition="#userId%2==0")
public User getUser(Long userId) {
return userMapper.selectById(userId);
}
}
注解对比:
注解 | 作用 | 关键参数 |
---|---|---|
@Cacheable |
查询数据时缓存结果 | key , condition , unless |
@CachePut |
强制更新缓存 | key |
@CacheEvict |
删除缓存 | allEntries , beforeInvocation |
定义注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DoubleCache {
String cacheName();
String key(); // 支持SpEL表达式
long l2TimeOut() default 120;
CacheType type() default CacheType.FULL; // FULL/PUT/DELETE
}
切面核心逻辑:
@Aspect
@Component
public class CacheAspect {
@Around("@annotation(doubleCache)")
public Object handleCache(ProceedingJoinPoint pjp, DoubleCache doubleCache) throws Throwable {
String realKey = parseKey(doubleCache, pjp); // 解析SpEL
switch (doubleCache.type()) {
case PUT:
Object result = pjp.proceed();
updateCache(realKey, result);
return result;
case DELETE:
deleteCache(realKey);
return pjp.proceed();
default: // FULL
if (caffeineCache.getIfPresent(realKey) != null)
return caffeineCache.getIfPresent(realKey);
if (redisTemplate.hasKey(realKey)) {
Object val = redisTemplate.opsForValue().get(realKey);
caffeineCache.put(realKey, val);
return val;
}
Object dbResult = pjp.proceed();
updateCache(realKey, dbResult);
return dbResult;
}
}
}
本地缓存策略优化
Caffeine.newBuilder()
.maximumSize(10_000) // 防OOM
.expireAfterWrite(15, TimeUnit.SECONDS) // 短TTL保新鲜
.refreshAfterWrite(1, TimeUnit.SECONDS) // 后台刷新
.recordStats() // 监控命中率
.writer(new CacheWriter() { // 淘汰监听
public void delete(String key, Object value, RemovalCause cause) {
log.info("Evicted key: {}, Cause: {}", key, cause);
}
});
Redis层优化建议
user:{12345}:profile
baseTTL + random(0, 300)
redisTemplate.setValueSerializer(new SnappyRedisSerializer())
监控指标体系
监控项 | 健康阈值 | 工具 |
---|---|---|
Caffeine命中率 | >85% | cache.stats().hitRate() |
Redis延迟 | <50ms | Redis SLOWLOG |
本地缓存内存占用 | JMX Metrics |
|
在4节点集群测试环境(16Core/32GB):
场景 | 纯Redis QPS | 两级缓存 QPS | 平均延迟 |
---|---|---|---|
商品详情读取 | 12,000 | 58,000 | 8ms → 0.3ms |
用户信息查询 | 8,500 | 45,000 | 15ms → 0.5ms |
库存扣减 | 3,200 | 3,500 | 25ms → 22ms |
结论:读密集型场景性能提升5X+,写操作提升有限
选型建议:
通过两级缓存架构,某电商平台在2025年大促期间成功支撑1.2亿QPS,Redis成本降低60%。正确实施该架构可让您的系统在性能和成本间获得最佳平衡!