在Spring Integration中,我们有一个 Setup ,它看起来像这样:
--->
--->
(dispatcher) Messages --> Gateway ----> QueueChannel ---> MessageHandler (worker)
--->
--->
所以我们有一个Dispatcher Thread从MQTT-Broker获取消息并将它们转发到Queue中。 Queue的轮询器具有TaskExecuter,因此Consumer是多线程的。 我们设法实现了所有功能。所以刚刚描述的设置已经实现。
现在保证不会丢失数据我们想做两件事:
1: 我们希望我们的队列能够持久保存数据,因此当程序不合理地关闭时,队列中的所有数据仍然存在。 这对我们也有用,我们使用MongoDB作为数据库,因为我们在您的文档中的某处读到这是推荐的方法。
2: 我们要确保的第二件事是工作线程正在进行事务处理。因此,只有当工作线程正确返回时,才会永久地从队列中删除消息(因此永久删除消息存储)。如果程序在处理消息期间(由工作线程)关闭,则消息将在下次启动时仍在队列中。 此外,如果工作人员在处理消息期间抛出异常,它将被放回队列中。
我们的实施:
如前所述,该程序的基本设置已经实现。然后,我们使用队列的消息存储实现扩展了基本实现。
QueueChannel:
@Bean
public PollableChannel inputChannel(BasicMessageGroupStore mongoDbChannelMessageStore) {
return new QueueChannel(new MessageGroupQueue(mongoDbChannelMessageStore, "inputChannel"));
}
由Messagestore支持:
@Bean
public BasicMessageGroupStore mongoDbChannelMessageStore(MongoDbFactory mongoDbFactory) {
MongoDbChannelMessageStore store = new MongoDbChannelMessageStore(mongoDbFactory);
store.setPriorityEnabled(true);
return store;
}
匹配的Poller :
@Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller() {
PollerMetadata poll = Pollers.fixedDelay(10).get();
poll.setTaskExecutor(consumer);
return poll;
}
执行人:
private Executor consumer = Executors.newFixedThreadPool(5);
我们尝试了什么? 如现在所解释的,我们希望使用事务功能扩展此实现。我们尝试使用setTransactionSynchronizationFactory,如解释here,但它没有工作(没有出现错误或任何事情,但行为仍然像我们添加TransactionSynchronizer之前那样):
@Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller() {
PollerMetadata poll = Pollers.fixedDelay(10).get();
poll.setTaskExecutor(consumer);
BeanFactory factory = mock(BeanFactory.class);
ExpressionEvaluatingTransactionSynchronizationProcessor etsp = new ExpressionEvaluatingTransactionSynchronizationProcessor();
etsp.setBeanFactory(factory);
etsp.setAfterRollbackChannel(inputChannel());
etsp.setAfterRollbackExpression(new SpelExpressionParser().parseExpression("#bix"));
etsp.setAfterCommitChannel(inputChannel());
etsp.setAfterCommitExpression(new SpelExpressionParser().parseExpression("#bix"));
DefaultTransactionSynchronizationFactory dtsf = new DefaultTransactionSynchronizationFactory(etsp);
poll.setTransactionSynchronizationFactory(dtsf);
return poll;
}
在弹簧集成中实现我们要求的最佳方法是什么?
修改 根据答案中的建议,我选择使用JdbcChannelMessageStore执行此操作。所以我尝试将描述here(18.4.2)的XML实现转换为Java。我不太确定如何做到这一点,这是我到目前为止所尝试的:
我创建了H2数据库,并在其上运行显示here的脚本。
创建了JDBCChannelMessageStore Bean:
@Bean
public JdbcChannelMessageStore store() {
JdbcChannelMessageStore ms = new JdbcChannelMessageStore();
ms.setChannelMessageStoreQueryProvider(queryProvider());
ms.setUsingIdCache(true);
ms.setDataSource(dataSource);
return ms;
}
创建了H2ChannelMessageStoreQueryProvider
@Bean
public ChannelMessageStoreQueryProvider queryProvider() {
return new H2ChannelMessageStoreQueryProvider();
}
改编了poller:
@Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller() throws Exception {
PollerMetadata poll = Pollers.fixedDelay(10).get();
poll.setTaskExecutor(consumer);
poll.setAdviceChain(Collections.singletonList(transactionInterceptor()));
return poll;
}
自动装配我的PlaatformTransactionManager:
@Autowired
PlatformTransactionManager transactionManager;
并从TransactonManager创建了TransactionInterceptor:
@Bean
public TransactionInterceptor transactionInterceptor() {
return new TransactionInterceptorBuilder(true)
.transactionManager(transactionManager)
.isolation(Isolation.READ_COMMITTED)
.propagation(Propagation.REQUIRED)
.build();
}
答案 0 :(得分:1)
感谢Artem Bilan的大力支持。我终于找到了解决方案。似乎有另一个名为transactionManager和transactionInterceptor的bean处于活动状态。这导致了奇怪的行为,我的trans-manager从未被初始化,而另一个transactionmanager(null)被用于transactioninterceptor和PollingConsumer。这就是为什么我在PollingConsumer中的Transactionmanager为空,以及为什么我的交易永远不会工作。
解决方案是重命名我的所有bean,对于某些bean我也使用注释@Primary来告诉spring在autowired时总是使用这个特定的bean。
我还降低了两个4.3,只是为了确保这不是与版本5相关的错误。我还没有测试它是否适用于V 5,但我认为它也应该有效。
答案 1 :(得分:0)
如果您需要将队列作为事务性,那么您肯定应该查看事务MessageStore
。只有JDBC就是这样。仅仅因为只有JDBC支持事务。因此,当我们执行DELETE
时,只有在提交TX时才可以。
MongoDB或任何其他NoSQL数据库都支持这样的模型,因此您只能使用TransactionSynchronizationFactory
将失败的消息回滚到数据库。
<强>更新强>
@RunWith(SpringRunner.class)
@DirtiesContext
public class So47264688Tests {
private static final String MESSAGE_GROUP = "transactionalQueueChannel";
private static EmbeddedDatabase dataSource;
@BeforeClass
public static void init() {
dataSource = new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:/org/springframework/integration/jdbc/schema-drop-h2.sql")
.addScript("classpath:/org/springframework/integration/jdbc/schema-h2.sql")
.build();
}
@AfterClass
public static void destroy() {
dataSource.shutdown();
}
@Autowired
private PollableChannel transactionalQueueChannel;
@Autowired
private JdbcChannelMessageStore jdbcChannelMessageStore;
@Autowired
private PollingConsumer serviceActivatorEndpoint;
@Autowired
private CountDownLatch exceptionLatch;
@Test
public void testTransactionalQueueChannel() throws InterruptedException {
GenericMessage<String> message = new GenericMessage<>("foo");
this.transactionalQueueChannel.send(message);
assertTrue(this.exceptionLatch.await(10, TimeUnit.SECONDS));
this.serviceActivatorEndpoint.stop();
assertEquals(1, this.jdbcChannelMessageStore.messageGroupSize(MESSAGE_GROUP));
Message<?> messageFromStore = this.jdbcChannelMessageStore.pollMessageFromGroup(MESSAGE_GROUP);
assertNotNull(messageFromStore);
assertEquals(message, messageFromStore);
}
@Configuration
@EnableIntegration
public static class ContextConfiguration {
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public ChannelMessageStoreQueryProvider queryProvider() {
return new H2ChannelMessageStoreQueryProvider();
}
@Bean
public JdbcChannelMessageStore jdbcChannelMessageStore() {
JdbcChannelMessageStore jdbcChannelMessageStore = new JdbcChannelMessageStore(dataSource);
jdbcChannelMessageStore.setChannelMessageStoreQueryProvider(queryProvider());
return jdbcChannelMessageStore;
}
@Bean
public PollableChannel transactionalQueueChannel() {
return new QueueChannel(new MessageGroupQueue(jdbcChannelMessageStore(), MESSAGE_GROUP));
}
@Bean
public TransactionInterceptor transactionInterceptor() {
return new TransactionInterceptorBuilder()
.transactionManager(transactionManager())
.isolation(Isolation.READ_COMMITTED)
.propagation(Propagation.REQUIRED)
.build();
}
@Bean
public TaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(5);
return threadPoolTaskExecutor;
}
@Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller() {
return Pollers.fixedDelay(10)
.advice(transactionInterceptor())
.taskExecutor(threadPoolTaskExecutor())
.get();
}
@Bean
public CountDownLatch exceptionLatch() {
return new CountDownLatch(2);
}
@ServiceActivator(inputChannel = "transactionalQueueChannel")
public void handle(Message<?> message) {
System.out.println(message);
try {
throw new RuntimeException("Intentional for rollback");
}
finally {
exceptionLatch().countDown();
}
}
}
}