Spring Integration:持久性和事务性QueueChannel

时间:2017-11-13 12:45:36

标签: java spring spring-integration

在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();
}

2 个答案:

答案 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();
            }
        }

    }

}