dubbo重试(retry)导致事务重复执行提交

本文详细介绍了在公司模块更新后,由于数据库出现重复数据引发的问题。作者通过排查发现,问题源于Dubbo服务调用超时触发的重试机制,导致Service被重复执行,从而引发了事务的双重提交。分析还指出,当超时重试时,由于前一个事务未完成,第二个事务尝试访问相同数据,造成锁表并可能导致超时错误。最后,作者强调了避免业务逻辑超过Dubbo超时时间的重要性,以防止类似问题的发生。

1.背景

公司有个模块最近有新需求,因此有些地方需要改动,而测试同学在测的过程中发现有个地方报了错让我看看,于是我看了一下报错日志,是因为数据库有重复数据,初步推断应该是在写接口导致的数据写入多条。

2.排查过程

​ 我先是把重复数据手动删除,然后尝试复现同事的操作。发现在编辑的时候,接口返回时长特别慢,导致了报错。然后数据库多了一条重复数据,最开始的想法是,是不是在界面的操作中出现了重复点击这个问题,于是我本地连接测试数据库进行调试。

​ 我在controller层和service层都打上了断点,然后重新调用了几次,发现有几次是产生了重复数据的,但是有几次又没有。同事之前写这段代码的时候,不是利用@Transactional注解而是利用的手动在代码中处理事务(代码如下),我在提交事务和回滚事务的地方都打上了断点,发现有时候tx.commit(status)会被断点拦截两次,而有时候tx.commit(status);tx.rollback(status);各会被断点拦截一次,但是controller层的断点却又只被拦截一次,于是我判断这不是来自重复点击的问题,因为如果重复点击,controller层也会被调用两次才对。

事务控制

​ 那会是什么问题呢?我看了一下controller层报错,报错信息如下,这段信息表名,controller层在利用dubbo调用service时,出现了超时的情况,我们超时时长设置的是60s,并且设置了reties=1,即超时重试机制,于是我明白了,应该是controller在调用service出现超时后,重试机制再调用了service一次,导致了service被执行了两次,导致事务重复提交,所以数据库出现了重复数据。

duboo调用超时报错

​ 可是为什么有时候不会出现重复数据呢?我继续打断点寻找,终于,在一次没有出现重复数据时发现了以下报错。就是说在dubbo重试机制执行时,重新开启事务,但是因为前面的事务没有结束,而后面的事务操作的数据跟前一个事务操作的是一样的数据,所以导致第一个事务锁住了表,第二个事务就需要等待第一个事务执行完,但是有时候第一个事务执行的时间过长,导致第二个事务锁超时。这样也就出现了有时会出现重复数据,有时不会。

事务执行超时报错

3. 总结

在使用dubbo的时候,业务逻辑不要超过dubbo设置的超时时长,不然会触发dubbo的重试机制,导致service被重复调用执行

### 重试机制的基本原则 在 Java 中实现方法调用的重试机制时,遵循一些基本的设计原则是关键。例如,采用 **指数退避(Exponential Backoff)** 可以有效避免系统在短时间内承受过多请求,从而减少雪崩效应,提高系统的稳定性。此外,应为重试机制设置 **最大重试次数限制**,以防止无限循环的重试操作,通常建议设置 3 到 5 次为上限,避免因持续失败导致资源浪费。 在设计重试逻辑时,还应 **区分可重试与不可重试错误**。例如,网络超时或服务暂时不可用可以重试,而像 400 错误(客户端错误)则不应重试,因为重试也不会改变结果。为了确保重试的安全性,**幂等性设计(Idempotency)** 是必不可少的,即多次执行相同操作不会产生副作用,这对于防止重复提交重复支付等场景至关重要。 ### 重试机制的技术实现 在 Java 中,有多种方式可以实现方法调用的重试机制。一种常见的方式是使用 **Dubbo 的服务调用重试机制**,它允许开发者根据不同的服务或操作调整重试次数、退避时间等参数,实现最优的容错能力。Dubbo 提供了多种重试策略,并且可以与断路器模式结合使用,进一步提升系统的稳定性。 另一种实现方式是使用 **Guava-retrying**,它提供了一种简便、灵活的方式来控制接口的重试,包括重试的条件、次数以及策略选择等[^2]。通过 Guava-retrying,开发者可以更加精细地控制重试行为,从而提高系统的可靠性。 此外,还可以使用 **Spring Retry** 或 **Resilience4j** 等库来实现重试机制。这些库不仅支持简单的重试功能,还提供了断路器模式的支持,使得开发者可以在失败次数达到阈值时,阻止进一步的请求,直到系统恢复。 ```java import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Service; @Service public class RetryService { @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000)) public void retryableMethod() { // 模拟可能失败的操作 throw new RuntimeException("Service failure"); } } ``` ### 适用场景 重试机制广泛应用于 **网络请求失败**、**外部服务不可用** 以及 **分布式事务中的部分失败** 等场景。例如,在 RocketMQ 中,生产者与消费者触发重试的条件和具体行为需要根据实际情况进行配置,以帮助构建弹性、高可用的系统。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值