一:一致性问题及两种处理方案
事务同时操作db和redis,在高并发等场景下,可能会遇到db和redis存储数据不一致的问题,遇到这种问题应该怎么解决?根据使用场景提供了几种简单处理方案:
- 单系统中事务处理,事务提交后延迟再删除一次redis,保证数据库数据和redis最终一致性。
- 多活系统,A机房,B机房,A、B机房数据是底层binlog同步的,独立搭建的应用和redis集群,有业务场景可能到A、B机房访问数据,redis是不相互复制的,所以我们是把在一个机房处理的缓存,使用MQ的方式推送到各个机房,然后当前机房也监听到信息后再处理一次,保证数据库数据和redis的最终一致性。
在处理系统间数据的最终一致性,通常也是考虑使用MQ异步的处理方式,同时考虑异常重处理、告警等。
二、举例常用发生的场景
- 详细场景问题描述:一个事务,同时操作db和redis,事务提交和redis操作不是同时的,不能保证数据库数据和redis数据的强一致性,例如事务提交前,删除了缓存,然后有其他场景又查询了redis缓存,redis为空时,会从db加载,由于事务没有提交,导致查询出来的数据和事务提交后的数据不一致,导致缓存和db不一致,在某些应用场景,会严重影响用户体验产生投诉,或者影响后续功能处理。
- 场景对应设计:会员信息表、会员信息缓存(redis)
查询会员信息接口:用户查询会员信息,会优先查询会员信息缓存,如果查询不到,调用db查询会员信息并且加载到redis中,如果查询不到,默认给设置一个空值标识到redis,防止高并发查询透传到数据库,减轻数据库压力。
更新会员信息接口:更新会员信息,删除会员信息redis缓存。
三、场景解决方案详解
- 单系统redis延迟处理
时序图:
代码块:
//延迟删除缓存的时间配置(毫秒),从zookeeper获取
long delayMillis = 2000;
// 开始执行任务时间戳,计算单位:毫秒
long doTaskMillis = System.currentTimeMillis();
// 还需要延迟的时间 = zookeeper配置的延迟时间 - 任务从创建到执行的时间差(原因:该任务可能在队列中呆了一段时间, 这段时间也属于延迟时间的范围,需要减掉)
// createTaskMillis是任务创建时间
long delayMillis = delayMillis - (doTaskMillis - createTaskMillis);
try {
// 还需要延迟的时间 > 0
if (delayMillis > 0) {
// 睡眠还需要延迟这么长时间,(延迟的方法需要优化)
Thread.sleep(delayMillis);
}
// TODO 删除缓存操作
} catch (InterruptedException e) {
// TODO 输出日志,用于分析问题
// 中断该线程
Thread.currentThread().interrupt();
} catch (Exception e) {
// TODO 输出日志,用于分析问题
}
2、多活系统处理方案
事务中删除当前机房缓存后,把需要删除的缓存使用json的格式插入到待推送表,异步job推送需要删除的缓存信息到MQ,各机房再监听MQ消息,再删除缓存,保证数据库和缓存最终一致性