最近需要对别的部门提供一个接口,第1版直接查库,第2版增加了Redis缓存。
//具体执行查询
private List<RecommendOuterVo> doQueryRecommendOuterVoList(ServiceAdCondition serviceAdCondition) {
//根据参数构造缓存key
String cacheKey = KeyPerBuilderUtil.buildRecommendOutListKey(serviceAdCondition);
//redis
List<RecommendOuterVo> recommendOuterVoList = doQueryRecommendOuterVoListFromRedis(serviceAdCondition,cacheKey);
//redis没有,再从数据库查1次
if(CollectionUtils.isEmpty(recommendOuterVoList)){
recommendOuterVoList=doQueryRecommendOuterVoListFromDb(serviceAdCondition);
//db查询之后,更新到redis,5分钟
if(CollectionUtils.isNotEmpty(recommendOuterVoList)) {
Integer cacheSeconds = 5 * 60;
String cacheValue = JSON.toJSONString(recommendOuterVoList);
redisUtil.setex(cacheKey, cacheSeconds,cacheValue);
}
}else{
logger.info("RecommendServiceOuterProviderImpl->doQueryRecommendOuterVoListFromRedis,voList size is {}",recommendOuterVoList.size());
}
return recommendOuterVoList;
}
写好之后,同事A过问了下缓存设计,在考虑要不要 特别注意“高并发下的缓存失效、缓存穿透、瞬间大量查询DB”。
最终没有去在代码实现,参考了几篇文章,做了一点总结,然后和同事B探讨了下。
参考过的文章
缓存穿透、缓存并发、缓存失效之思路变迁
https://www.jianshu.com/p/d96906140199
最佳实践 缓存穿透,瞬间并发,缓存雪崩的解决方法
https://blog.csdn.net/fei33423/article/details/79027790
我的总结,仅供参考,不做过多解释了
业务场景:
1、1个key
String CACHE_KEY = "cache_key";
首页-xxx:单个key的解决方案
托底Redis1个月、JVM缓存3分钟、正常Redis5分钟、DB数据库、配置中心)
2、多个key。
根据多个参数,动态构造( platform,sortId)
业务方1:缓存就1个key。每分钟1000到3000。
业务方2:platform+sortId(30个左右),大约30个key。
业务方3:待接入
1个调用方,1秒,1000到3000。
2个调用方,2000到6000。
1、多个key同时缓存失效
比如 首页4个xx栏目,4个独立的key,就有可能同时失效。
托底不常用,JVM和Redis可能同时失效。DB承压。DB异常,查询配置中心。DB完全挂了,配置中心或托底缓存。
2、同1个key多台机器同时缓存失效,导致高并发穿透DB(多个的话,翻几倍)
1级缓存:同1个key,用redis(中心化)同时失效。
解决办法:
JVM本地缓存(3分钟)+Redis缓存(5分钟)。
4台机器(A、B、C、D)可能会在JVM缓存同时失效。
不会同时在2级缓存同时失效?仍然可能。
10点1分,JVM缓存(10点4分过期),Redis(10点6分)。
10点4分,JVM缓存(10点7分过期),Redis(10点6分+5,11分过期)
10点7分,JVM缓存(10点10分过期),Redis(11过期)
2次缓存过期时间, 距离时间 从2变为了1。
10点10分,JVM缓存(10点13分过期),Redis(16过期)
10点13,
10点16
2次缓存过期时间, 距离时间 从1变为了0。
这充分,2级缓存也无法避免“缓存穿透,大量查询直接到DB”。
说明1:
JVM固定3分钟,Redis不能固定为6分钟。
答1:存在2个都为null的情况。
3分钟和5分钟不会存在这种情况。
(仔细分析之后,发现不对)
说明2:
2级缓存之后,多个key还有必要分开 过期吗?
比如:JVM本地缓存(1到5分钟)+Redis缓存(2级缓存,5到10分钟)。
答2:多个key缓存同时失效
过期时间,分散化。1到10分钟随机,避免多个key-value同时过期。
参考:https://www.jianshu.com/p/d96906140199
(2级缓存之后,多个key同时失效,只会发生在同1级缓存中)
2级缓存,时间随机,可以减少“同时失效”的情况,但无法避免。
说明3:
早上9点,瞬间,从0到3000个访问
答3:正常情况,不太可能。
(秒杀场景,压测场景) 分布式锁,只让1台机器查询db
说明4.定时任务(不太行,适合同1个key)
platform+sortId,定时任务不断查询,1分钟主动更新缓存。
(很多key不查询,浪费很多空间。key太多,查询数据库很频繁)
说明5:托底缓存、配置中心,不现实。多个key,而且key是动态增加和减少的。
首页xxx,1个key,而且是一直使用。
如果要实现,目前想到的解决方案:
JVM缓存(1到5分钟)+Redis缓存(6到30分钟)。
不同key,配置不同的过期时间。("123,456" 1分钟过期,"789,234" 1.8分钟过期)
如果查询Redis为空,通过分布式锁,只让1台机器去查询DB,其它线程等待50毫秒。
if(jvm缓存有值){
return cacheJvm;
}
if(redis缓存有值){
return cacheRedis;
}else{
lock = get lock();
if(lock){
dbData=queryDb();
updateJvmCache(buildKey(condition),dbData, random time(1-5);
updateRedis(buildKey(condition),dbData, random time(6,10);
}
}
分布式锁
1、直接redis
while(set失败){
sleep100毫秒。
查询redis。
有值就跳出,返回。
}
2、Zookeeper
分布式锁,参考以前转载的文章。
一般公司的一般场景,好像没有必要考虑 缓存穿透的问题。