XA Transactions between 2 JMS Brokers (ActiveMQ)

随声附和 提交于 2019-12-25 05:10:04

问题


I am trying to move jms messages between 2 different, remote, activeMQ brokers and after a lot of reading

I am using Atomikos, as I am writing a standalone application, and I am also using spring to get the whole thing working.

I have the following bean javaconfig setup

@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.

I then setup a simple messageListener (which is assigned to the dmlc before it is started), with the following method:

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).

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.

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. 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. If this is the case, would a SessionAwareMessageListener be better instead? Should I set @Transacted in the onMessage method?

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. 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. The solution posted below helped with other issues.


回答1:


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. Simply rehtrow, also to log the exception stacktrace properly remove the second {} in your log-statement.

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.

For a JMSException do something like this, converting it to a 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. 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. Spring will not call it due to the JTA transaction. Try switching it to SESSION_TRANSACTED instead. At least for the DMLC.



来源:https://stackoverflow.com/questions/22072100/xa-transactions-between-2-jms-brokers-activemq

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!