简体   繁体   English

2个JMS代理(ActiveMQ)之间的XA事务

[英]XA Transactions between 2 JMS Brokers (ActiveMQ)

I am trying to move jms messages between 2 different, remote, activeMQ brokers and after a lot of reading 我正在尝试在2个不同的远程activeMQ代理之间移动jms消息,并且经过大量阅读

I am using Atomikos, as I am writing a standalone application, and I am also using spring to get the whole thing working. 我正在使用Atomikos,因为我正在编写一个独立的应用程序,同时我还在使用spring使整个事情正常进行。

I have the following bean javaconfig setup 我有以下bean javaconfig设置

@Bean(name="atomikosSrcConnectionFactory")
    public AtomikosConnectionFactoryBean consumerXAConnectionFactory() {
        AtomikosConnectionFactoryBean consumerBean = new AtomikosConnectionFactoryBean();
        consumerBean.setUniqueResourceName("atomikosSrcConnectionFactory");
        consumerBean.setLocalTransactionMode(false);
        return consumerBean;
    }

    @Bean(name="atomikosDstConnectionFactory")
    public AtomikosConnectionFactoryBean producerXAConnectionFactory() {
        AtomikosConnectionFactoryBean producerBean = new AtomikosConnectionFactoryBean();
        producerBean.setUniqueResourceName("atomikosDstConnectionFactory");
        producerBean.setLocalTransactionMode(false);
        return producerBean;
    }

    @Bean(name="jtaTransactionManager")
    public JtaTransactionManager jtaTransactionManager() throws SystemException {
        JtaTransactionManager jtaTM = new JtaTransactionManager();
        jtaTM.setTransactionManager(userTransactionManager());
        jtaTM.setUserTransaction(userTransactionImp());
        return jtaTM;
    }

    @Bean(initMethod="init", destroyMethod="close", name="userTransactionManager")
    public UserTransactionManager userTransactionManager() {
        UserTransactionManager utm = new UserTransactionManager();
        utm.setForceShutdown(false);
        return utm;
    }

    @Bean(name="userTransactionImp")
    public UserTransactionImp userTransactionImp() throws SystemException {
        UserTransactionImp uti = new UserTransactionImp();
        uti.setTransactionTimeout(300);
        return uti;
    }

    @Bean(name="jmsContainer")
    @Lazy(value=true)
    public DefaultMessageListenerContainer jmsContainer() throws SystemException {
        DefaultMessageListenerContainer dmlc = new DefaultMessageListenerContainer();
        dmlc.setAutoStartup(false);
        dmlc.setTransactionManager(jtaTransactionManager());
        dmlc.setSessionTransacted(true);
        dmlc.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
        dmlc.setConnectionFactory(consumerXAConnectionFactory());
        dmlc.setDestinationName("srcQueue");
        return dmlc;
    }

    @Bean(name="transactedJmsTemplate")
    public JmsTemplate transactedJmsTemplate() {

        DynamicDestinationResolver dest = new DynamicDestinationResolver();

        JmsTemplate jmsTmp = new JmsTemplate(producerXAConnectionFactory());

        jmsTmp.setDeliveryPersistent(true);
        jmsTmp.setSessionTransacted(true);
        jmsTmp.setDestinationResolver(dest);
        jmsTmp.setPubSubDomain(false);
        jmsTmp.setReceiveTimeout(20000);
        jmsTmp.setExplicitQosEnabled(true);
        jmsTmp.setSessionTransacted(true);
        jmsTmp.setDefaultDestination(new ActiveMQQueue("destQueue"));
        jmsTmp.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);

        return jmsTmp;
    }

The 2 AtomikosConnectionFactoryBean are wrapping an ActiveMQXAConnectionFactory (One for each broker) at runtime before I start the DMLC. 在启动DMLC之前,2个AtomikosConnectionFactoryBean在运行时包装ActiveMQXAConnectionFactory(每个代理一个)。

I then setup a simple messageListener (which is assigned to the dmlc before it is started), with the following method: 然后,我使用以下方法设置一个简单的messageListener(在启动之前已分配给dmlc):

public void onMessage(Message message) {
    final Message rcvedMsg = message;

    try{
        MessageCreator msgCreator = new MessageCreator(){
                public Message createMessage(Session session) throws JMSException{
                    Message returnMsg = null;
                    if(rcvedMsg instanceof TextMessage){
                        TextMessage txtMsg = session.createTextMessage();
                        txtMsg.setText(((TextMessage) rcvedMsg).getText());
                        returnMsg = txtMsg;
                    }
                    else if(rcvedMsg instanceof BytesMessage){
                        BytesMessage bytesMsg = session.createBytesMessage();
                        if(!(((BytesMessage) rcvedMsg).getBodyLength() > Integer.MAX_VALUE)){
                            byte[] bodyContent = new byte[(int) ((BytesMessage) rcvedMsg).getBodyLength()];
                            bytesMsg.writeBytes(bodyContent);
                            returnMsg = bytesMsg;
                        }
                    }
                    return returnMsg;
                }
            };

            jmsTemplate.send(msgCreator);
    }
    catch(JmsException | JMSException e){
        logger.error("Error when transfering message: '{}'. {}",message,e);
    }
}

The application starts without any specific errors or warnings however as soon as I put a message in the source queue I can see, through logs, that the onMessage method is being run over and over again for the same message, as if the transaction keeps being rolled back and restarted again (No errors are throw anywhere). 该应用程序启动时没有任何特定的错误或警告,但是,一旦我在源队列中放入一条消息,我就可以通过日志看到onMessage方法针对同一条消息反复运行,就像事务一直在回滚并再次重新启动(任何地方都不会抛出错误)。

I have also noticed that if I happen to use the same source and destination url (Meaning the same broker but each with it's own connectionFactory), it works and messages are transfered as intended between the source and destination queue. 我还注意到,如果我碰巧使用相同的源和目标url(意味着相同的代理,但每个代理都有其自己的connectionFactory),它将起作用,并且消息将按预期在源队列和目标队列之间进行传输。

What I am wondering is 我想知道的是

  1. What am I doing wrong in the setup? 我在设置中做错了什么? Why is my transaction "seemingly" being rolled back over and over again when using 2 different brokers but working when using the same (but over 2 different connection factories)? 为什么当使用2个不同的代理时,我的事务“似乎”被一遍又一遍地回滚,但是当使用相同的代理(但是在2个不同的连接工厂中)时却可以工作?
  2. I am not completely convinced that the onMessage is currently doing proper transaction as I am currently catching all exceptions and doing nothing and I believe this will commit the transaction of the dmlc before the jmstemplate is done sending the message but I am uncertain. 我并不完全相信onMessage当前正在执行正确的事务,因为我当前正在捕获所有异常并且什么也不做,并且我相信这将在jmstemplate完成发送消息之前提交dmlc的事务,但是我不确定。 If this is the case, would a SessionAwareMessageListener be better instead? 如果是这样,那么SessionAwareMessageListener会更好吗? Should I set @Transacted in the onMessage method? 我应该在onMessage方法中设置@Transacted吗?

Could anybody help shine a light on the issue? 有人可以帮助解决这个问题吗? All input is welcome. 欢迎所有输入。

UPDATE: 更新:

I realized that the issue with the "rollback" was due to the fact that both AMQs I was using were connected to each other via a network of brokers and I happened to be using the same queue name for source and destination. 我意识到“回滚”的问题是由于我使用的两个AMQ通过代理网络相互连接,而我恰巧对源和目标使用了相同的队列名称。 This led to the fact that the message was transfered by the application from one AMQ to another and then immediately, because there was a consumer on the source AMQ, the message would be transfered back to the original AMQ, which in turn was seen as a new message by the my application and transfered again and the cycle went on infinitely. 这导致以下事实:应用程序将消息从一个AMQ传输到另一个AMQ,然后立即将其传输,因为源AMQ上有一个使用者,因此该消息将被传输回原始AMQ,这又被视为原始AMQ。我的应用程序发送了新消息并再次传输,循环无限进行。 The solution posted below helped with other issues. 下面发布的解决方案有助于解决其他问题。

try {
   ... Code
} catch (JmsException je) {
    logger.error("Error when transfering message: '{}'. {}",message,e);
}

The code above is swallowing the exception, you should either not catch the exception or rethrow so that the transaction management can handle it appropriatly. 上面的代码吞没了异常,您不应捕获异常或将其重新抛出,以便事务管理可以适当地处理它。 Currently no exception is seen, a commit is performed which can lead to strange results. 当前没有异常,执行提交会导致奇怪的结果。

I would do something like the following, JmsException is from Spring and, as most exceptions in Spring, a RuntimeException . 我将执行以下操作, JmsException来自Spring,并且作为Spring中的大多数异常,它来自RuntimeException Simply rehtrow, also to log the exception stacktrace properly remove the second {} in your log-statement. 只需rehtrow,还要记录异常stacktrace,以正确除去日志语句中的第二个{}

try {
   ... Code
} catch (JmsException je) {
    logger.error("Error when transfering message: '{}'.",message,e);
    throw je;
}

However this will duplicate the logging as Spring will also log the error. 但是,这将复制日志记录,因为Spring还将记录错误。

For a JMSException do something like this, converting it to a JmsException . 对于JMSException请执行以下操作,将其转换为JmsException

try {
   ... Code
} catch (JMSException je) {
    logger.error("Error when transfering message: '{}'.",message,e);
    throw JmsUtils.convertJmsAccessException(je);
}

To get more information on what happens you probably want to enable DEBUG logging for the org.springframework.jms package. 要获取有关发生的情况的更多信息,您可能需要为org.springframework.jms包启用DEBUG日志记录。 This will give you insight in what happens on send/receive of the message. 这将使您深入了解发送/接收消息时会发生什么。

Another thing you use transactional sessions and manual acknowledging of messages, however you don't do a message.acknowledge() in your code. 另一件事是您使用事务会话和手动确认消息,但是您没有在代码中执行message.acknowledge() Spring will not call it due to the JTA transaction. 由于JTA事务,Spring不会调用它。 Try switching it to SESSION_TRANSACTED instead. 尝试将其切换为SESSION_TRANSACTED At least for the DMLC . 至少对于DMLC

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM