收录于合集
#redis2个
#本地缓存4个
#数据一致性4个
#定时任务刷新Redis缓存与本地缓存1个
20
23
不疯魔不成活,大家好呀,我是科哥,江湖ID 不码不疯魔
真实场景:
面试官:你好,请问你做过的项目中,服务的最高QPS是多少?
候选人:我们的服务高峰访问量非常大,在双十一活动的时候 QPS大概10w左右
面试官:这么大的访问量,服务面临的压力应该非常高,你们是怎么设计的呢?
候选人:我们的服务后端设计是采用了二级缓存,把一些热点的数据放到本地缓存,把一些非热点的数据放到 Redis 缓存,接口优先查询一级缓存,如果一级缓存没数据,接着会查询二级缓存,二级缓存不存在,才访问数据库。这样设计可以减少数据库的访问压力,加快查询效率。
面试官:从你的设计看,你的数据存储到三个地方(DB、Redis、Caffeine),如果涉及数据更新,你是怎么保证他们三者中间的一致性的呢?
候选人:基于定时任务刷新Redis缓存,同时采用Redis发布订阅实现更新本地缓存。
基于定时任务的解决方案中,定时任务程序schedule会在特定的时间间隔内执行特定的任务。这个任务通常会扫描数据库和缓存,并保持它们之间的一致性。
看
重
点
重点掌握:执行任务流程?
1. 定时任务程序在特定的时间间隔(每隔5分钟)执行一次任务;
2. 任务首先从数据库中获取所有最新的数据;
3. 然后,任务将所有最新的数据写入缓存Redis;
4. 如果在写入过程中发现任何缓存已经存在,那么任务将更新该缓存;
5. 紧接着采用Redis发布订阅广播消息;
6. 最后,编写监听类,变更广播消息订阅,刷新本地缓存,处理各个节点本地缓存一致性;
注意:此篇文章,具体的商用级代码已更新,记得关注,免费领取,标记星号每日更多干货分享哟!!!
01
定时任务(每隔5分钟执行一次)
/**
* 字典缓存与DB一致性检查
*/
@Configuration
@EnableScheduling
@Slf4j
public class SysDictCacheAndDBConsistenceJob {
private static final String SYSDICT_CACHE_DB_CONSISTENCE_LOCK = "sysdict_cache_db_consistence_lock";
@Resource
private DistributeLockHelper distributeLockHelper;
@Resource
private SysDictService sysDictService;
@Resource
private BloomFilerHelper bloomFilerHelper;
// 每隔5分钟刷新一次
@Scheduled(cron = "0 0/5 * * * ?")
public void sysDictCacheAndDBConsistence() {
// 尝试获取分布式锁
if(!distributeLockHelper.tryLock(SYSDICT_CACHE_DB_CONSISTENCE_LOCK, TimeUnit.MINUTES, 1)){
return;
}
try{
// 查询所有字典数据
List<SysDictVO> actDOList = sysDictService.findDictList();
actDOList.forEach(sysDictVO->{
//添加布隆元素
bloomFilerHelper.add(sysDictVO.getIdentification());
// 刷新Redis缓存与本地缓存
sysDictService.refreshRedisAndLocalCache(sysDictVO);
});
log.info("完成DB与缓存一致性处理");
} catch (Exception e){
log.error("通过定时任务补偿开始或结束失败!errMsg:{}", e.getMessage(), e);
} finally {
//释放锁
distributeLockHelper.unlock(SYSDICT_CACHE_DB_CONSISTENCE_LOCK);
}
}
}
02
刷新Redis缓存
// 刷新Redis缓存
@Override
public void refreshRedisCache(SysDictVO sysDictVO) {
long expireTime = sysDictVO.getCreateDate().getTime()-new Date().getTime();
// 设置redis过期时间为创建字典时间+ 3分钟
if(expireTime <= 0){
expireTime = 0;
}
redisHelper.set(String.format(CacheConstants.SysDict.SYS_BASE_DICT, sysDictVO.getIdentification()), JSONUtil.toJsonStr(sysDictVO), expireTime + 180 * 1000L);
// 更新字典列表redis缓存信息
List<SysDict> sysDictList = sysDictMapper.findSysDictListByCondition(StatusConst.OK);
if(CollUtil.isNotEmpty(sysDictList)){
Map<String,String> redisMap = Maps.newConcurrentMap();
for(SysDict sysDict : sysDictList){
redisMap.put(sysDict.getIdentification(), JSONUtil.toJsonStr(sysDict));
}
redisHelper.setMap(CacheConstants.SysDict.DICT_LIST, redisMap,CacheConstants.SysDict.DICT_LIST_EXPIRE_TIME);
}
}
03
Redis发布订阅实现更新本地缓存
@Override
public void refreshRedisAndLocalCache(SysDictVO sysDictVO) {
// 刷新redis缓存
sysDictCacheService.refreshRedisCache(sysDictVO);
// 采用redis发布订阅实现,更新本地缓存
messagePublishUtil.sendMsg(MessageChannelEnum.DICT_CHANNEL.getCode(), sysDictVO);
}
04
广播消息工具类(采用Redis发布订阅实现)
/**
*
* 广播消息工具类(采用redis发布订阅实现)
*
*/
@Slf4j
@Component
public class MessagePublishUtil {
@Resource
private RedisHelper redisHelper;
/**
* 广播消息
* @param channel 消息频道
* @param value 消息value
*/
public <T> void sendMsg(String channel, T value){
log.info("采用redis发布订阅广播消息,channel:{},value:{}", channel, DCJSONUtil.toJsonStr(value));
redisHelper.getRedisTemplate().convertAndSend(channel ,value instanceof String ? value
: DCJSONUtil.toJsonStr(value));
}
}
05
变更广播消息订阅
/**
* 字典变更广播消息订阅(刷新本地缓存,处理各个节点本地缓存一致性)
*/
@Slf4j
@Component
public class DictChangeInfoMessageSubscribeListener implements MessageListener {
@Resource
private SysDictCacheService sysDictCacheService;
@Resource
private RedisHelper redisHelper;
public DictChangeInfoMessageSubscribeListener(RedisMessageListenerContainer listenerContainer) {
listenerContainer.addMessageListener(this, new ChannelTopic(MessageChannelEnum.DICT_CHANNEL.getCode()));
}
@Override
public void onMessage(Message message, byte[] bytes) {
Object data = redisHelper.getRedisTemplate().getValueSerializer().deserialize(message.getBody());
if(ObjectUtil.isNotNull(data)){
// 刷新本地缓存
sysDictCacheService.refreshLocalCache(JSONUtil.toBean(data.toString(), SysDictVO.class));
}
log.info("订阅变更信息频道:{},接收数据:{}", new String(message.getChannel()), data);
}
}
06
刷新本地缓存Caffeine
@Override
public void refreshLocalCache(SysDictVO sysDictVO) {
caffeineCacheUtil.putCache(CacheConstants.SysDict.CAFFEINE_SYS_DICT, sysDictVO.getIdentification(), JSONUtil.toJsonStr(sysDictVO));
}
收获
源码+文档资料