收录于合集

#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));
}

收获

源码+文档资料

java Redis缓存 定时处理数据 redis定时刷新缓存_缓存

java Redis缓存 定时处理数据 redis定时刷新缓存_本地缓存_02