spring 集成ActiveMQ发送消息Hang住的问题

来源:互联网 发布:苹果mac看视频软件推荐 编辑:程序博客网 时间:2024/04/28 17:46

spring提供了一个简化JMS API的框架,利用spring可以不用关心connection和session的管理,并且提供了JmsTemplate模板使开发者可以专注于业务上的收发消息

为开发者提供了便利。在使用spring框架集成activeMQ5.12版本时遇到一个问题,activeMQ5.12独立部署于10.137.100.54机器上本地开发

使用配置

<bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">     
     <property name="connectionFactory">     
       <bean class="org.apache.activemq.ActiveMQConnectionFactory">        
        <property name="brokerURL">          
         <value>failover:(tcp://10.137.100.54:61616)?timeout=500</value>        
          </property>
          <property name="sendTimeout"  value="1000"/>
          <property name="clientID" value="useTimeout2000"/>
         </bean>     
         </property>   
         </bean>

<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">  

     <property name="connectionFactory" ref="jmsFactory"/>

     <property name="defaultDestination" ref="topicDestination"/>

     </bean>

如果54机器上的MQ Broker正常运行Spring能够稳定的管理JMS并且 平稳的收发消息,如果这时停掉Broker的服务

发送消息的线程将会发生阻塞,虽然设置了timeout超时但是丝毫不起作用。

如果发生这种情况将会对整个应用造成性能影响甚至宕机。

在这个问题的分析过程中尝试把activeMQ的客户端版本降为5.11.1结果还是会发生阻塞,最后版本降为5.10.0竟然可以正常抛出异常了。

5.10.0和5.12.0在版本升级的过程中究竟发生了什么变更导致前后出现如此的差异,下面是两个版本的代码

5.10版本代码

org.apache.activemq.transport.failover.FailoverTransport类里面有个方法

@Override

    public void oneway(Object o) throws IOException {

        Command command = (Command) o;

        Exception error = null;

        try {

            synchronized (reconnectMutex) {

                if (command != null && connectedTransport.get() ==null) {

                    if (command.isShutdownInfo()) {

                        // Skipping send of ShutdownInfo command when not connected.

                        return;

                    } else if (command instanceof RemoveInfo || command.isMessageAck()) {

                        // Simulate response to RemoveInfo command or MessageAck (as it will be stale)

                        stateTracker.track(command);

                        if (command.isResponseRequired()) {

                            Response response = new Response();

                            response.setCorrelationId(command.getCommandId());

                            myTransportListener.onCommand(response);

                        }

                        return;

                    } else if (command instanceof MessagePull) {

                        // Simulate response to MessagePull if timed as we can't honor that now.

                        MessagePull pullRequest = (MessagePull) command;

                        if (pullRequest.getTimeout() != 0) {

                            MessageDispatch dispatch = new MessageDispatch();

                            dispatch.setConsumerId(pullRequest.getConsumerId());

                            dispatch.setDestination(pullRequest.getDestination());

                            myTransportListener.onCommand(dispatch);

                        }

                        return;

                    }

                }

 

                // Keep trying until the message is sent.

                for (int i = 0; !disposed; i++) {

                    try {

 

                        // Wait for transport to be connected.

                        Transport transport = connectedTransport.get();

                        long start = System.currentTimeMillis();

                        boolean timedout =false;

                        while (transport ==null && !disposed && connectionFailure == null

                                && !Thread.currentThread().isInterrupted()) {

                            if (LOG.isTraceEnabled()) {

                                LOG.trace("Waiting for transport to reconnect..: " + command);

                            }

                            long end = System.currentTimeMillis();

                            if (timeout > 0 && (end - start > timeout)) {

                                timedout =true;

                         //5.10.0版本只要超时就退出

                                if (LOG.isInfoEnabled()) {

                                    LOG.info("Failover timed out after " + (end - start) + "ms");

                                }

                                break;

                            }

                            try {

                                reconnectMutex.wait(100);

                            } catch (InterruptedException e) {

                                Thread.currentThread().interrupt();

                                if (LOG.isDebugEnabled()) {

                                    LOG.debug("Interupted: " + e, e);

                                }

                            }

                            transport = connectedTransport.get();

                        }

 

                        if (transport == null) {

                            // Previous loop may have exited due to use being

                            // disposed.

                            if (disposed) {

                                error = new IOException("Transport disposed.");

                            } else if (connectionFailure !=null) {

                                error = connectionFailure;

                            } else if (timedout ==true) {

                                error =new IOException("Failover timeout of " + timeout + " ms reached.");

                            } else {

                                error = new IOException("Unexpected failure.");

                            }

                            break;

                        }

 

                        Tracked tracked = null;

                        try {

                            tracked = stateTracker.track(command);

                        } catch (IOException ioe) {

                            LOG.debug("Cannot track the command " + command, ioe);

                        }

                        // If it was a request and it was not being tracked by

                        // the state tracker,

                        // then hold it in the requestMap so that we can replay

                        // it later.

                        synchronized (requestMap) {

                            if (tracked != null && tracked.isWaitingForResponse()) {

                                requestMap.put(Integer.valueOf(command.getCommandId()), tracked);

                            } else if (tracked ==null && command.isResponseRequired()) {

                                requestMap.put(Integer.valueOf(command.getCommandId()), command);

                            }

                        }

 

                        // Send the message.

                        try {

                            transport.oneway(command);

                            stateTracker.trackBack(command);

                        } catch (IOException e) {

 

                            // If the command was not tracked.. we will retry in

                            // this method

                            if (tracked == null) {

 

                                // since we will retry in this method.. take it

                                // out of the request

                                // map so that it is not sent 2 times on

                                // recovery

                                if (command.isResponseRequired()) {

                                    requestMap.remove(Integer.valueOf(command.getCommandId()));

                                }

 

                                // Rethrow the exception so it will handled by

                                // the outer catch

                                throw e;

                            } else {

                                // Handle the error but allow the method to return since the

                                // tracked commands are replayed on reconnect.

                                if (LOG.isDebugEnabled()) {

                                    LOG.debug("Send oneway attempt: " + i + " failed for command:" + command);

                                }

                                handleTransportFailure(e);

                            }

                        }

 

                        return;

 

                    } catch (IOException e) {

                        if (LOG.isDebugEnabled()) {

                            LOG.debug("Send oneway attempt: " + i + " failed for command:" + command);

                        }

                        handleTransportFailure(e);

                    }

                }

            }

        } catch (InterruptedException e) {

            // Some one may be trying to stop our thread.

            Thread.currentThread().interrupt();

            throw new InterruptedIOException();

        }

 

        if (!disposed) {

            if (error != null) {

                if (error instanceof IOException) {

                    throw (IOException) error;

                }

                throw IOExceptionSupport.create(error);

            }

        }

    }

 

5.12版本代码

@Override

    public void oneway(Object o) throws IOException {

 

        Command command = (Command) o;

        Exception error = null;

        try {

 

            synchronized (reconnectMutex) {

 

                if (command != null && connectedTransport.get() ==null) {

                    if (command.isShutdownInfo()) {

                        // Skipping send of ShutdownInfo command when not connected.

                        return;

                    } else if (command instanceof RemoveInfo || command.isMessageAck()) {

                        // Simulate response to RemoveInfo command or MessageAck (as it will be stale)

                        stateTracker.track(command);

                        if (command.isResponseRequired()) {

                            Response response = new Response();

                            response.setCorrelationId(command.getCommandId());

                            myTransportListener.onCommand(response);

                        }

                        return;

                    } else if (command instanceof MessagePull) {

                        // Simulate response to MessagePull if timed as we can't honor that now.

                        MessagePull pullRequest = (MessagePull) command;

                        if (pullRequest.getTimeout() != 0) {

                            MessageDispatch dispatch = new MessageDispatch();

                            dispatch.setConsumerId(pullRequest.getConsumerId());

                            dispatch.setDestination(pullRequest.getDestination());

                            myTransportListener.onCommand(dispatch);

                        }

                        return;

                    }

                }

 

                // Keep trying until the message is sent.

                for (int i = 0; !disposed; i++) {

                    try {

 

                        // Wait for transport to be connected.

                        Transport transport = connectedTransport.get();

                        long start = System.currentTimeMillis();

                        boolean timedout =false;

                        while (transport ==null && !disposed && connectionFailure == null

                                && !Thread.currentThread().isInterrupted()) {

                            if (LOG.isTraceEnabled()) {

                                LOG.trace("Waiting for transport to reconnect..: " + command);

                            }

                            long end = System.currentTimeMillis();

                            if (command.isMessage() && timeout > 0 && (end - start > timeout)) {

                          //只有发送消息时的超时才会退出,导致最终抛出timeout的异常

                                timedout =true;

                                if (LOG.isInfoEnabled()) {

                                    LOG.info("Failover timed out after " + (end - start) + "ms");

                                }

                                break;

                            }

                            try {

                                reconnectMutex.wait(100);

                            } catch (InterruptedException e) {

                                Thread.currentThread().interrupt();

                                if (LOG.isDebugEnabled()) {

                                    LOG.debug("Interupted: " + e, e);

                                }

                            }

                            transport = connectedTransport.get();

                        }

 

                        if (transport == null) {

                            // Previous loop may have exited due to use being

                            // disposed.

                            if (disposed) {

                                error = new IOException("Transport disposed.");

                            } else if (connectionFailure !=null) {

                                error = connectionFailure;

                            } elseif (timedout == true) {

                                error =new IOException("Failover timeout of " + timeout + " ms reached.");

                            } else {

                                error = new IOException("Unexpected failure.");

                            }

                            break;

                        }

 

                        Tracked tracked = null;

                        try {

                            tracked = stateTracker.track(command);

                        } catch (IOException ioe) {

                            LOG.debug("Cannot track the command " + command, ioe);

                        }

                        // If it was a request and it was not being tracked by

                        // the state tracker,

                        // then hold it in the requestMap so that we can replay

                        // it later.

                        synchronized (requestMap) {

                            if (tracked != null && tracked.isWaitingForResponse()) {

                                requestMap.put(Integer.valueOf(command.getCommandId()), tracked);

                            } else if (tracked ==null && command.isResponseRequired()) {

                                requestMap.put(Integer.valueOf(command.getCommandId()), command);

                            }

                        }

 

                        // Send the message.

                        try {

                            transport.oneway(command);

                            stateTracker.trackBack(command);

                            if (command.isShutdownInfo()) {

                                shuttingDown = true;

                            }

                        } catch (IOException e) {

 

                            // If the command was not tracked.. we will retry in

                            // this method

                            if (tracked == null) {

 

                                // since we will retry in this method.. take it

                                // out of the request

                                // map so that it is not sent 2 times on

                                // recovery

                                if (command.isResponseRequired()) {

                                    requestMap.remove(Integer.valueOf(command.getCommandId()));

                                }

 

                                // Rethrow the exception so it will handled by

                                // the outer catch

                                throw e;

                            } else {

                                // Handle the error but allow the method to return since the

                                // tracked commands are replayed on reconnect.

                                if (LOG.isDebugEnabled()) {

                                    LOG.debug("Send oneway attempt: " + i + " failed for command:" + command);

                                }

                                handleTransportFailure(e);

                            }

                        }

 

                        return;

 

                    } catch (IOException e) {

                        if (LOG.isDebugEnabled()) {

                            LOG.debug("Send oneway attempt: " + i + " failed for command:" + command);

                        }

                        handleTransportFailure(e);

                    }

                }

            }

        } catch (InterruptedException e) {

            // Some one may be trying to stop our thread.

            Thread.currentThread().interrupt();

            throw new InterruptedIOException();

        }

 

        if (!disposed) {

            if (error != null) {

                if (error instanceof IOException) {

                    throw (IOException) error;

                }

                throw IOExceptionSupport.create(error);

            }

        }

    }

可以看出一个显著的区别5.12.0版本多了一个command.isMessage()的判断,也就是说只有是消息类型的操作时才会发生timeout的异常

当创建连接,建立session的过程中5.12.0版本不会抛出异常,而实际上创建connection和session都会执行oneway(Object o)的操作。

再来看看spring JmsTemplate发送消息的代码

public <T> T execute(SessionCallback<T> action, boolean startConnection) throws JmsException {

        Assert.notNull(action, "Callback object must not be null");

        Connection conToClose = null;

        Session sessionToClose = null;

        try {

            Session sessionToUse = ConnectionFactoryUtils.doGetTransactionalSession(

                    getConnectionFactory(),this.transactionalResourceFactory, startConnection);

            if (sessionToUse ==null) {

                conToClose = createConnection();

                sessionToClose = createSession(conToClose);

                if (startConnection) {

                    conToClose.start();

                }

                sessionToUse = sessionToClose;

            }

            if (logger.isDebugEnabled()) {

                logger.debug("Executing callback on JMS Session: " + sessionToUse);

            }

            return action.doInJms(sessionToUse);

        }

        catch (JMSException ex) {

            throw convertJmsAccessException(ex);

        }

        finally {

            JmsUtils.closeSession(sessionToClose);

            ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), startConnection);

        }

可以看出在发送消息时JmsTemplate会不断地获取获取connection和session这就造成了阻塞的问题。

这个问题应该是伴随着ActiveMQ从5.10.0向上升级的过程中产生的很可能是升级过程中产生的一个新BUG。

 

0 0
原创粉丝点击