我试图了解 Spring Listener Container 如何处理重试上下文中的事务。
我配置了这样的内容:
<rabbit:listener-container connection-factory="connectionFactory"
transaction-manager="chainedTransactionManager"
channel-transacted="true"
advice-chain="retryAdvice">
<rabbit:listener ref="myMessageProcessor" queue-names="test.messages" method="handleMessage"/>
</rabbit:listener-container>
我希望交易包含在重试中,这样如果我的交易因任何原因失败,我可以决定针对特定异常重试,而对于其他异常,只需将消息发送到我的 DLQ。
但是,我惊讶地发现重试代码包含在事务代码中,而不是相反,这似乎更明智。
换句话说,Spring 监听器似乎是这样做的:
doIntransaction -> doWithRetry -> invokeMyCode
我希望它会是这样的:
doWithRetry - doIntransaction -> invokeMyCode
我的计划是使用包含 JpaTransactionManager
和 RabbitTransactionManager
的 ChainedTransactionManager 来处理这两个事务,即我读取的消息的确认,以及我在此事务期间发送的消息的提交,并根据某些条件重试整个事务,但这似乎不是这样工作的。
不仅如此,事务中发生异常后,上下文可能会变得无用。我需要一个新的事务才能使重试有意义。
还有一个问题是,在提交/回滚阶段发生的任何异常都不会被重试,因为它们发生在重试上下文之外。我假设它们仅根据 ErrorHandler
配置重试,而不是基于我建议的代码。不幸的是,ErrorHandler
没有退避策略,也没有有用的 RetryContext
详细信息来计算我重试事务的次数。
在这种情况下,使用事务管理器配置监听器并重试功能的正确或最推荐的方法是什么?
最佳答案
我还没有尝试过,但是您应该能够通过从容器中删除事务管理器并向建议链添加一个普通的 Spring TransactionInterceptor
来实现您想要的行为(在重试建议之后) )。
当容器具有事务管理器时,您可以告诉它在调用监听器(包含在建议链中)之前启动事务。
但是,您可能会在日志中收到一些噪音,因为容器可能仍会尝试确认/提交交付,因为它“认为”它正在使用本地事务(如果拦截器具有 RabbitTransactionManager
已配置)。
只要您不将 RabbitTransactionManager
包含在 chainedTransactionManager
中,就不会发生这种情况;容器将仅使用本地事务。
如果您包含 RTM,您可能需要使用手动确认或向容器添加虚拟事务管理器来防止这种情况发生。
让我们知道您的想法;明天我可以去看看。
编辑
如下所述,使用有状态重试是一种更简单的解决方案,因为消息被拒绝并重新传递。但是,您需要一个 messageId
header (或自定义 key 生成器)。
关于java - 带有事务管理器和重试建议顺序的 Spring 监听器容器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44058741/