数据库事务和redis最终一致性的解决方法

博客围绕事务同时操作db和redis时的数据一致性问题展开。介绍了单系统和多活系统中保证数据库数据和redis最终一致性的处理方案,如单系统事务提交后延迟删redis,多活系统用MQ推送缓存信息。还举例了常见场景及对应设计,并给出场景解决方案详解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一:一致性问题及两种处理方案

事务同时操作db和redis,在高并发等场景下,可能会遇到db和redis存储数据不一致的问题,遇到这种问题应该怎么解决?根据使用场景提供了几种简单处理方案:

  • 单系统中事务处理,事务提交后延迟再删除一次redis,保证数据库数据和redis最终一致性。
  • 多活系统,A机房,B机房,A、B机房数据是底层binlog同步的,独立搭建的应用和redis集群,有业务场景可能到A、B机房访问数据,redis是不相互复制的,所以我们是把在一个机房处理的缓存,使用MQ的方式推送到各个机房,然后当前机房也监听到信息后再处理一次,保证数据库数据和redis的最终一致性。

在处理系统间数据的最终一致性,通常也是考虑使用MQ异步的处理方式,同时考虑异常重处理、告警等。

 

二、举例常用发生的场景

  1. 详细场景问题描述:一个事务,同时操作db和redis,事务提交和redis操作不是同时的,不能保证数据库数据和redis数据的强一致性,例如事务提交前,删除了缓存,然后有其他场景又查询了redis缓存,redis为空时,会从db加载,由于事务没有提交,导致查询出来的数据和事务提交后的数据不一致,导致缓存和db不一致,在某些应用场景,会严重影响用户体验产生投诉,或者影响后续功能处理。
  2. 场景对应设计:会员信息表、会员信息缓存(redis)

查询会员信息接口:用户查询会员信息,会优先查询会员信息缓存,如果查询不到,调用db查询会员信息并且加载到redis中,如果查询不到,默认给设置一个空值标识到redis,防止高并发查询透传到数据库,减轻数据库压力。

更新会员信息接口:更新会员信息,删除会员信息redis缓存。

三、场景解决方案详解

  1. 单系统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消息,再删除缓存,保证数据库和缓存最终一致性

 

 

### 数据库Redis缓存一致性的实际生产案例及解决方案 在高并发场景下,数据库往往成为性能瓶颈,而引入Redis作为缓存可以有效缓解这一问题。然而,随着系统的复杂度增加,缓存数据库之间的数据一致性成为一个常见的挑战[^3]。以下是针对该问题的一些实际生产环境中的案例及其对应的解决方案。 #### 1. 双写策略 双写策略是最简单的一种方法,即每次更新操作时同时修改数据库Redis缓存。这种方法的优点在于逻辑清晰易懂,但在分布式环境下容易因网络延迟或其他异常导致部分失败的情况发生。为了提高可靠性,通常会加入事务机制或者重试机制来保障最终一致性[^2]。 #### 2. 删除缓存+懒加载模式 在这种模式下,当数据发生变化时并不立即去同步更新Redis里的值而是选择将其删除;当下次查询这个key的时候如果发现它不存在于Redis中,则重新从DB获取最新版本的数据并填充回缓存里。这种方式能够很好地避免由于频繁刷新带来的额外开销,并且天然支持强一致性一致性两种需求切换[^1]。 #### 3. 使用消息队列解耦 通过引入Kafka/RabbitMQ之类的消息中间件来进行异步处理也是一个不错的选择。具体做法是在业务层触发变更事件后发送一条通知给订阅者服务端口,后者负责执行相应的动作比如清除指定范围内的keys或者是批量预热某些热点资源等等。这样做的好处是可以降低主流程的压力同时也便于后续扩展维护。 #### 示例代码片段展示如何利用Spring Data Redis实现基本功能: ```java @Autowired private StringRedisTemplate redisTemplate; public void updateData(Long id, String newValue){ try{ // 更新数据库 myRepository.updateById(id,newValue); // 清除关联的redis key redisTemplate.delete("myKey:"+id.toString()); }catch(Exception e){ log.error("Error during updating data",e); } } ``` 上述例子展示了基于Java Spring框架下的一个典型应用场景——每当有新的记录被保存至持久化存储之前都会先移除掉对应可能存在的旧状态副本从而确保两者之间不会存在冲突现象出现。 ### 总结 综上所述,在面对不同类型的业务诉求时可以选择适合自己的技术手段应对可能出现的各种状况。值得注意的是无论采取哪种办法都需要考虑到极端条件下的恢复能力以及长期运行期间的成本效益分析等问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值