<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; ActiveMQ</title>
	<atom:link href="https://blog.gmem.cc/tag/activemq/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Fri, 03 Apr 2026 04:13:36 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>Spring对JMS的支持</title>
		<link>https://blog.gmem.cc/jms-support-of-spring</link>
		<comments>https://blog.gmem.cc/jms-support-of-spring#comments</comments>
		<pubDate>Fri, 07 Aug 2015 09:28:05 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[ActiveMQ]]></category>
		<category><![CDATA[JMS]]></category>
		<category><![CDATA[Spring]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=15268</guid>
		<description><![CDATA[<p>简介 Spring 提供了JMS的集成，简化JMS的使用，提供的API封装类似于Spring的JDBC集成。 JMS的功能大体上分为两类——接收、发送消息。Spring提供了： JmsTemplate来完成消息的发送、同步接收 消息监听器容器（message listener containers）来异步的接收消息（事件驱动） 使用Spring JMS JmsTemplate JmsTemplate是Spring的jms-core包的核心类，它封装了资源的创建、释放部分，简化收发消息的代码。 JmsTemplate类是线程安全的，除非需要不同的收发配置（QoS），整个系统可以仅仅使用单例的JmsTemplaet。 基本API 发送消息时，你可以提供一个MessageCreator回调来实现消息发送： [crayon-69d26efd6ca2f716075417/] 如果需要使用更加复杂的JMS API，可以调用下面的方法： [crayon-69d26efd6ca35870387097/] JMS API中有很多QoS设置项，例如优先级、TTL，这些都暴露为JmsTemplate的属性。你可以根据业务需要创建多个JmsTemplate。某些JMS实现在ConnectionFactory级别管理QoS设置，要明确指定使用JmsTemplate上的QoS选项，需要调用[crayon-69d26efd6ca38228498520-i/]  JmsTemplate集成了简单请求/应答模式的支持： <a class="read-more" href="https://blog.gmem.cc/jms-support-of-spring">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/jms-support-of-spring">Spring对JMS的支持</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>Spring 提供了JMS的集成，简化JMS的使用，提供的API封装类似于Spring的JDBC集成。</p>
<p>JMS的功能大体上分为两类——接收、发送消息。Spring提供了：</p>
<ol>
<li>JmsTemplate来完成消息的发送、同步接收</li>
<li>消息监听器容器（message listener containers）来异步的接收消息（事件驱动）</li>
</ol>
<div class="blog_h1"><span class="graybg">使用Spring JMS</span></div>
<div class="blog_h2"><span class="graybg">JmsTemplate</span></div>
<p>JmsTemplate是Spring的jms-core包的核心类，它封装了资源的创建、释放部分，简化收发消息的代码。</p>
<p>JmsTemplate类是线程安全的，除非需要不同的收发配置（QoS），整个系统可以仅仅使用单例的JmsTemplaet。</p>
<div class="blog_h3"><span class="graybg">基本API</span></div>
<p>发送消息时，你可以提供一个MessageCreator回调来实现消息发送：</p>
<pre class="crayon-plain-tag">jmsTemplate.send( queueName, new MessageCreator() {
    @Override
    public Message createMessage( Session session ) throws JMSException {
        return session.createTextMessage( ... );
    }
} );</pre>
<p>如果需要使用更加复杂的JMS API，可以调用下面的方法：</p>
<pre class="crayon-plain-tag">jmsTemplate.execute( new SessionCallback&lt;Object&gt;() {
    public Object doInJms( Session session ) throws JMSException {
        return null;
    }
} );
jmsTemplate.execute( new ProducerCallback&lt;Object&gt;() {
    public Object doInJms( Session session, MessageProducer producer ) throws JMSException {
        return null;
    }
} );</pre>
<p>JMS API中有很多QoS设置项，例如优先级、TTL，这些都暴露为JmsTemplate的属性。你可以根据业务需要创建多个JmsTemplate。某些JMS实现在ConnectionFactory级别管理QoS设置，要明确指定使用JmsTemplate上的QoS选项，需要调用<pre class="crayon-plain-tag">jmsTemplate.setExplicitQosEnabled( true )</pre> </p>
<p>JmsTemplate集成了简单请求/应答模式的支持：</p>
<pre class="crayon-plain-tag">Message response = jmsTemplate.sendAndReceive( session -&gt; {
    Message msg = session.createTextMessage();
    msg.setJMSReplyTo( "临时答复队列名称" );
    return msg;
} );</pre>
<div class="blog_h2"><span class="graybg">连接</span></div>
<p>你需要为JmsTemplate提供一个ConnectionFactory的引用，后者属于JMS规范的一部分，任何JMS提供商都需要实现。ConnectionFactory是创建到MOM中间件连接的工厂。</p>
<div class="blog_h3"><span class="graybg">资源缓存</span></div>
<p>标准的JMS API，在收发消息时需要使用很多中间对象：ConnectionFactory ⇨ Connection ⇨ Session ⇨ MessageProducer ⇨ send()。这些中间对象如果反复创建、销毁，会影响性能。Spring提供了一些具有缓存能力的ConnectionFactory实现：</p>
<table class="full-width fixed-word-wrap">
<tbody>
<tr>
<td><em><strong>SingleConnectionFactory</strong></em><br />在每次被调用createConnection()时，总是返回同一个连接，并且忽略对close()的调用</td>
</tr>
<tr>
<td>
<p><strong><em>CachingConnectionFactory</em></strong></p>
<p>在SingleConnectionFactory的基础上，增加了Session、MessageProducer、MessageConsumer缓存的能力。默认缓存大小1</p>
<p>设置sessionCacheSize可以增加Session的缓存数量。由于会话是基于不同签收模式（acknowledgment mode）来缓存的，因此实际缓存的Session数量可能多于声明的数量，sessionCacheSize设置为1时最多缓存4个（对应4种签收模式）</p>
<p>MessageProducer、MessageConsumer连同它们所属的Session一起缓存。在缓存时考虑其特别设置的属性。MessageProducer基于destination缓存。MessageConsumer基于destination、selector、noLocal递送标记、持久化订阅名称来缓存</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">消息监听容器</span></div>
<p>Spring支持类似于EJB的消息驱动Bean的功能，该功能由消息监听容器实现，你可以用XML配置或者注解驱动的方式，指定POJO的方法会自动被调用以完成消息的处理。</p>
<p>消息监听容器负责从JMS队列/主题监听消息，并负责管理多线程的消息消费、事务管理、资源获取/是否。</p>
<p>消息监听容器的实现类主要有：</p>
<table class="full-width fixed-word-wrap">
<tbody>
<tr>
<td>
<p><strong><em>SimpleMessageListenerContainer</em></strong></p>
<p>在启动时创建固定数量的Session、Consumer，使用JMS标准API MessageConsumer.setMessageListener()来注册监听器，由JMS实现来执行回调</p>
<p>支持JMS原生事务 —— 切换sessionTransacted标记或者设置acknowledge为acknowledge，你的回调抛出的异常会导致回滚</p>
<p>不支持外部管理的事务，兼容性较好</p>
</td>
</tr>
<tr>
<td>
<p><em><strong>DefaultMessageListenerContainer</strong></em> </p>
<p>基于轮询方式实现。支持外部管理事务，当使用JtaTransactionManager时，每个接收到的消息都注册到XA事务，与JEE环境兼容</p>
<p>容器的缓存级别可以被定制，如果不启用缓存，每当接收一个连接时都会创建Collection、Session。不启用缓存 + 非持久化订阅可能导致在高负载时丢失消息</p>
<p>该容器还能够支持消息代理宕机重启后，自动恢复自己的功能。默认的，它使用一个简单的BackOff实现，每5秒重试连接到代理，你可以指定自己的BackOff实现</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">XML配置示例</span></div>
<pre class="crayon-plain-tag">&lt;!--
  container-type = default表示DefaultMessageListenerContainer
  concurrency = 1 表示每一个jms:listener对应的Session/Consumer个数（也就是线程数？）
  destination-type = durableTopic 表示目标是持久化订阅，这种情况下 client-id + subscription是必须的
  cache为缓存方式：
      none：不缓存
      connection：为每个监听器线程缓存Connection对象
      session：为每个监听器线程缓存Connection、Session对象
      consumer：为每个监听器线程缓存Connection、Session、Consumer对象
      auto：默认值，通常取值consumer，但是指定了外部事务管理器时，取值none
      transaction-manager：事务管理器
--&gt;
&lt;jms:listener-container container-type="default" connection-factory="cf" acknowledge="auto" concurrency="1"
                        destination-type="durableTopic" client-id="APP" cache="auto"&gt;
    &lt;jms:listener destination="监听的主题" ref="你提供的回调Bean" method="你提供的回调方法" subscription="持久化订阅名称"/&gt;
&lt;/jms:listener-container&gt;

&lt;!-- 不使用JMS名字空间时的类似配置 --&gt;
&lt;bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"&gt;
    &lt;property name="messageListener" ref="messageListener" /&gt;
&lt;/bean&gt;
&lt;bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter"&gt;
    &lt;constructor-arg&gt;
        &lt;bean class="你提供的回调Bean"/&gt;
    &lt;/constructor-arg&gt;
    &lt;property name="defaultListenerMethod" value="你提供的回调方法"/&gt;
    &lt;!-- 禁止消息类型转换 --&gt;
    &lt;property name="messageConverter"&gt;
        &lt;null/&gt;
    &lt;/property&gt;
&lt;/bean&gt;</pre>
<div class="blog_h3"><span class="graybg">注解配置示例</span></div>
<p>编程式配置DMLC：</p>
<pre class="crayon-plain-tag">@Configuration
@EnableJms
public class AppConfig {

    @Bean
    public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
        DefaultJmsListenerContainerFactory factory =
                new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory());
        factory.setDestinationResolver(destinationResolver());
        factory.setConcurrency("3-10");
        return factory;
    }
}</pre>
<p>监听器配置：</p>
<pre class="crayon-plain-tag">@Component
public class MyService {
    // 使用@Header注入单个消息头、使用@Headers注入所有消息头，接收参数必须为Map
    // 使用@Payload可以明确指定接收载荷的参数
    // Message、Session可以被注入
    @JmsListener(destination = "queue")
    public void processOrder(String data, @Header("ordertype") String orderType) {
    }
    
    
    @JmsListener(destination = "myDestination")
    @SendTo("status") // 可以指定应答队列
    public OrderStatus processOrder(Order order) {
        return status;
    }
} </pre>
<div class="blog_h3"><span class="graybg">MessageListenerAdapter</span></div>
<p>这个组件允许你把任意POJO作为MDP（消息驱动POJO）使用，而不需要实现MessageListener接口：</p>
<pre class="crayon-plain-tag">public class MessageListenerAdapter implements MessageListener, SessionAwareMessageListener&lt;Message&gt;{
    public MessageListenerAdapter(Object delegate) {
         setDelegate(delegate);
    }
}</pre>
<p>构造方法参数delegate可以是任何对象， MessageListenerAdapter假设它实现以下接口：</p>
<pre class="crayon-plain-tag">public interface MessageDelegate {
    // 根据消息类型的不同，自动选择调用的方法
    void handleMessage(String message);
    void handleMessage(Map message);
    void handleMessage(byte[] message);
    void handleMessage(Serializable message);
}</pre>
<p>delegate也可以提供其它形式的方法，但是需要指定defaultListenerMethod配置。 </p>
<div class="blog_h3"><span class="graybg">回调方法</span></div>
<p>回调方法可以具有参数，对应接收到的消息，也<span style="background-color: #c0c0c0;">可以具有返回值</span>，对应发送到原始消息Reply-To字段指定的，或者listener默认配置的应答队列的应答消息。</p>
<p>参数类型可以是各种各样的，但是需要messageConverter的配合。</p>
<div class="blog_h2"><span class="graybg">事务管理</span></div>
<p>Spring提供了针对单个ConnectionFactory的JmsTransactionManager，事务资源是绑定到线程的。JmsTransactionManager会自动检测到事务资源并打开之。</p>
<p>在JEE环境下，ConnectionFactory可能对Connection、Session进行缓存，资源因而是跨事务重用的。在独立运行环境下，使用Spring的SingleConnectionFactory会导致Connection共享，而每个事务使用独立的Session。</p>
<p>JmsTemplate也可以使用JtaTransactionManager + 支持XA的ConnectionFactory，以支持分布式事务。</p>
<p>在独立运行环境下，你需要设置JmsTemplate的sessionAcknowledgeMode、sessionTransacted来告知Spring是否使用Jms事务。当联用PlatformTransactionManager、JmsTemplate时，JmsTemplate总是提供事务性的Session对象</p>
<div class="blog_h2"><span class="graybg">消息转换</span></div>
<p>JmsTemplate的<pre class="crayon-plain-tag">convertAndSend()</pre>、 <pre class="crayon-plain-tag">receiveAndConvert()</pre>可以在收发消息时完成数据类型转换。 转换工作代理给了MessageConverter类，缺省实现支持String  ⇨ TextMessage、byte[] ⇨ BytesMesssage、java.util.Map ⇨ MapMessage之间的转换。</p>
<p>如果要对转换后，发送前对JMS消息对象进行处理，可以使用MessagePostProcessor：</p>
<pre class="crayon-plain-tag">jmsTemplate.convertAndSend( queue, map, new MessagePostProcessor() {
    public Message postProcessMessage( Message message ) throws JMSException {
        message.setIntProperty( "AccountID", 1000 );
        message.setJMSCorrelationID( "123-00001" );
        return message;
    }
} );</pre>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">DMLC无法接收消息</span></div>
<p>通过JMX查看ActiveMQ队列状态，发现消息已经被消费。打开WARN级别的Spring日志，发现报方法找不到。配置的回调方法：</p>
<pre class="crayon-plain-tag">public void onP2PMessage( Message message ) throws Exception {
}</pre>
<p>此问题的原因是，回调方法的参数必须是JMS消息的载荷。因此，对于TextMessage，回调参数类型应该是String。 </p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/jms-support-of-spring">Spring对JMS的支持</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/jms-support-of-spring/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ActiveMQ学习笔记</title>
		<link>https://blog.gmem.cc/activemq-study-note</link>
		<comments>https://blog.gmem.cc/activemq-study-note#comments</comments>
		<pubDate>Sat, 07 Mar 2015 02:42:00 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Work]]></category>
		<category><![CDATA[ActiveMQ]]></category>
		<category><![CDATA[JMS]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=14881</guid>
		<description><![CDATA[<p>MOM简介 MOM简介 中间件是一类连接软件组件和应用的计算机软件，它包括一组服务。 以便于运行在一台或多台机器上的多个软件通过网络进行交互。该技术所提供 的互操作性，推动了一致分布式体系架构的演进，通常用于支持并简化 那些复杂的分布式应用程序。 所谓面向消息的中间件（Message-orientedMiddleware，MOM） ，它的基本功能是：将信息以消息的形式，从一个应用程序传送到另一个或多个应用程序。其主要特征包括： 消息以异步的方式发送和接收，消息发送者不需要等待消息接受者的响应 可靠传输，数据不能丢失，有的时候，也会要求不能重复传输 事务性以及分布式事务(XA)支持，可以和其他MOM、数据库等资源进行事 务性支持，确保操作的原子性 主流MOM产品 产品名称 开发者 授权 特点 IBM WebSphereMQ IBM 商业 稳定性高，市场占有率高，与 <a class="read-more" href="https://blog.gmem.cc/activemq-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/activemq-study-note">ActiveMQ学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">MOM简介</span></div>
<div class="blog_h2"><span class="graybg">MOM简介</span></div>
<p>中间件是一类<span style="background-color: #c0c0c0;">连接软件组件和应用的</span>计算机软件，它包括一组服务。 以便于运行在一台或多台机器上的多个软件通过网络进行交互。该技术所提供 的互操作性，推动了一致分布式体系架构的演进，通常用于支持并简化 那些复杂的分布式应用程序。</p>
<p>所谓面向消息的中间件（Message-orientedMiddleware，MOM） ，它的基本功能是：将信息以消息的形式，从一个应用程序传送到另一个或多个应用程序。其主要特征包括：</p>
<ol>
<li>消息以异步的方式发送和接收，消息发送者不需要等待消息接受者的响应</li>
<li>可靠传输，数据不能丢失，有的时候，也会要求不能重复传输</li>
<li>事务性以及分布式事务(XA)支持，可以和其他MOM、数据库等资源进行事 务性支持，确保操作的原子性</li>
</ol>
<div class="blog_h3"><span class="graybg">主流MOM产品</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">产品名称</td>
<td style="width: 10%; text-align: center;">开发者</td>
<td style="width: 10%; text-align: center;">授权</td>
<td style="text-align: center;">特点</td>
</tr>
</thead>
<tbody>
<tr>
<td>IBM WebSphereMQ</td>
<td>IBM</td>
<td>商业</td>
<td>稳定性高，市场占有率高，与 WebSphereMessageBroker紧密结合， 基于Eclipse的管理界面，多语言客户 端</td>
</tr>
<tr>
<td>ActiveMQ</td>
<td>Apache</td>
<td>开源</td>
<td>使用最多的开源MOM，可拔插的持久化 方案，多种传输机制(虚拟机内、TCP、 UDP、HTTP、SSL等)多种传输协议 (Openwire、AMQP、REST、STOMP、 XMPP等)，多语言客户端，简单的集群 和故障转移配置，嵌入JVM</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">JMS简介</span></div>
<p>Java消息服务（Java Message Service，JMS）定义了Java中访问消息中间件的 接口。JMS只是接口，并没有给予实现，实现JMS接口的消息中间件称为 JMS Provider，例如ActiveMQ。</p>
<div class="blog_h2"><span class="graybg">JMS基本概念</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">概念</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>JMSProvider</td>
<td>实现JMS接口的消息中间件</td>
</tr>
<tr>
<td>PTP</td>
<td>点对点的消息模型</td>
</tr>
<tr>
<td>Pub/Sub</td>
<td>发布/订阅的消息模型</td>
</tr>
<tr>
<td>Destination</td>
<td>目标，消息的目的地</td>
</tr>
<tr>
<td>Queue</td>
<td>队列目标</td>
</tr>
<tr>
<td>Topic</td>
<td>主题目标</td>
</tr>
<tr>
<td>ConnectionFactory</td>
<td>连接工厂，JMS用它创建连接</td>
</tr>
<tr>
<td>Connection</td>
<td>JMS客户端到JMSProvider的连接</td>
</tr>
<tr>
<td>Session</td>
<td>会话，一个发送或接收消息的线程持有</td>
</tr>
<tr>
<td>MessageProducer</td>
<td>由Session对象创建的用来发送消息的对象</td>
</tr>
<tr>
<td>MessageConsumer</td>
<td>由Session对象创建的用来接收消息的对象</td>
</tr>
<tr>
<td>Acknowledge</td>
<td>签收，即MessageConsumer确认消息收妥的操作</td>
</tr>
<tr>
<td>Transaction</td>
<td>事务</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">JMS编程模型</span></div>
<p>在JMS编程模型中，JMS客户端(组件或应用程序)通过JMS消息服务交换消息。 消息生产者将消息发送至消息服务，消息消费者则从消息服务接收这些消息。</p>
<p>这些消息的传送操作是使用一组实现JMS应用编程接口(API)的对象(由 JMSProvide提供)来执行的。</p>
<p>在JMS编程模型中JMS客户端使用 ConnectionFactory对象创建一个连接，向消息服务发送消息以及从消息服务接收消息均是通过此连接来进行。</p>
<p>Connection是客户端与消息服务的活动连接。 创建连接时，将分配通信资源以及验证客户端。这是一个相当重要的对象，大多数客户端均使用一个连接来进行所有的消息传送。连接用于创建会话。</p>
<p>Session是一个用于生成和使用消息的单线程上下文。它用于创建发送的生产 者和接收消息的消费者，并为所发送的消息定义发送顺序。会话通过大量确认选项或通过事务来支持可靠传送。</p>
<p>客户端使用MessageProducer向指定的物理目标发送消息。生产者可指定一个默认传送模式(持久性消息与非持久性消息)、优先级和有效期值，以控制生产者向物 理目标发送的所有消息。</p>
<p>同样，客户端使用MessageConsumer对象从指定的物 理目标(在API中表示为目标对象)接收消息。消费者可使用消息选择器，借助它，消息服务可以只向消费者发送与选择标准匹配的那些消息。消费者可以支持同步或异步消息接收。异步使用可通过向消费者注册MessageListener来 实现。当会话线程调用MessageListener对象的onMessage方法时，客户端将使用消息。</p>
<div class="blog_h3"><span class="graybg">P2P模型</span></div>
<p>消息从一个生产者传送至一个消费者。在此传送模型中，目标是一个队列。消息首先被传送至队列目标，然后根据队列传送策略，从该队列将消息传送至向 <span style="background-color: #c0c0c0;">此队列进行注册的某一个消费者</span>，一次只传送一条消息。可以向队列目标发送 消息的<span style="background-color: #c0c0c0;">生产者的数量没有限制</span>，但每条消息只能<span style="background-color: #c0c0c0;">发送至、并由一个消费者成功 使用</span>。如果没有已经向队列目标注册的消费者，队列将保留它收到的消息，并 在<span style="background-color: #c0c0c0;">某个消费者向该队列进行注册时将消息传送给该消费者</span>。</p>
<div class="blog_h3"><span class="graybg">Pub/Sub模型</span></div>
<p>消息从一个生产者传送至任意数量的消费者。在此传送模型中，目标是一个主 题。消息首先被传送至主题目标，然后传送至<span style="background-color: #c0c0c0;">所有已订阅此主题的活动消费 者</span>。可以向主题目标发送消息的生产者的数量没有限制，并且每个消息可以发 送至任意数量的订阅消费者。主题目标也<span style="background-color: #c0c0c0;">支持持久订阅的概念</span>。持久订阅表示 消费者已向主题目标进行注册，但在消息传送时此消费者可以处于非活动状态。 当此<span style="background-color: #c0c0c0;">消费者再次处于活动状态时，它将接收此信息</span>。</p>
<div class="blog_h3"><span class="graybg">消息生产步骤</span></div>
<ol>
<li>创建连接使用的工厂类JMS ConnectionFactory</li>
<li>使用管理对象 JMS ConnectionFactory 建立连接 Connection</li>
<li>使用连接Connection建立会话Session</li>
<li>使用会话Session和管理对象Destination创建消息生产者 MessageSender</li>
<li>使用消息生产者MessageSender发送消息</li>
</ol>
<div class="blog_h3"><span class="graybg">消息消费步骤</span></div>
<ol>
<li>创建连接使用的工厂类JMS ConnectionFactory</li>
<li>使用管理对象 JMS ConnectionFactory 建立连接 Connection</li>
<li>使用连接Connection建立会话Session</li>
<li>使用会话Session和管理对象Destination创建消息消费者MessageReceiver</li>
<li>使用消息消费者MessageReceiver接受消息setMessageListener 将 MessageListener 接口绑定到 MessageReceiver</li>
</ol>
<div class="blog_h3"><span class="graybg">消息的签收</span></div>
<p>JMS消息只有在被确认之后，才认为已经被成功地消费了。消息的成功消费通常包含三个阶段：</p>
<ol>
<li>客户接收消息</li>
<li>客户处理消息</li>
<li>消息被确认</li>
</ol>
<p>在事务性会话中，当一个<span style="background-color: #c0c0c0;">事务被提交的时候，确认自动发生</span>。在非事务性会话 中，消息何时被确认取决于创建会话时的签收模式(acknowledgement mode)。 该参数有以下三个可选值：</p>
<ol>
<li>Session.AUTO_ACKNOWLEDGE 当客户成功的从receive方法返回的时候或者从MessageListener.onMessage方法成功返回的时候，会话自动确认</li>
<li>Sessiion.TRANSACTION 用session.commit()进行签收</li>
<li>Session.CLIENT_ACKNOWLEDGE 客户通过消息的acknowledge方法确认消息</li>
</ol>
<div class="blog_h3"><span class="graybg">JMS编程接口列表</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">基本类型</td>
<td style="text-align: center;">点对点模式</td>
<td style="text-align: center;">发布/订阅模式</td>
</tr>
</thead>
<tbody>
<tr>
<td>ConnectionFactory</td>
<td>QueueConnectionFactory</td>
<td> TopicConnectionFactory</td>
</tr>
<tr>
<td>Connection</td>
<td>QueueConnection</td>
<td> TopicConnection</td>
</tr>
<tr>
<td>Session </td>
<td> QueueSession</td>
<td> TopicPublisher</td>
</tr>
<tr>
<td>Destination</td>
<td> QueueSession</td>
<td> Topic</td>
</tr>
<tr>
<td>MessageProducer</td>
<td> QueueSender</td>
<td> </td>
</tr>
<tr>
<td>MessageConsumer</td>
<td> QueueReceiver,QueueBrowseer</td>
<td> TopicSubscriber</td>
</tr>
</tbody>
</table>
<div class="page" title="Page 15">
<div class="section">
<div class="layoutArea">
<div class="column">
<div class="page" title="Page 50">
<div class="section">
<div class="layoutArea">
<div class="column">
<div class="blog_h3"><span class="graybg">消息头</span></div>
</div>
</div>
</div>
</div>
</div>
<div class="column">
<div class="page" title="Page 50">
<div class="section">
<div class="layoutArea">
<div class="column">
<p>生产者在发送消息前，可以设置一系列的JMS消息头：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">消息头</td>
<td style="width: 10%; text-align: center;">自动</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>JMSDestination</td>
<td>是</td>
<td>消息发送的目的地:主要是指Queue和Topic</td>
</tr>
<tr>
<td>JMSDeliveryMode</td>
<td>是</td>
<td>传送模式有两种模式 :持久模式和非持久模式</td>
</tr>
<tr>
<td>JMSExpiration</td>
<td>是</td>
<td>消息过期时间，等于 Destination 的send 方法中的timeToLive值加 上发送时刻的GMT 时间值。如果timeToLive 值等于零，则 JMSExpiration 被设为零，表示该消息永不过期。如果发送后，在消 息过期时间之后消息还没有被发送到目的地，则该消息被清除</td>
</tr>
<tr>
<td>JMSPriority</td>
<td>是</td>
<td>消息优先级，从 0-9 十个级别，0-4 是普通消息，5-9 是加急消息。 JMS 不要求JMS Provider 严格按照这十个优先级发送消息，但必须 保证加急消息要先于普通消息到达。默认是4级</td>
</tr>
<tr>
<td>JMSMessageID</td>
<td>是</td>
<td>唯一识别每个消息的标识，由JMS Provider 产生</td>
</tr>
<tr>
<td>JMSTimestamp</td>
<td>是</td>
<td>消息时间戳</td>
</tr>
<tr>
<td>JMSCorrelationID</td>
<td>否</td>
<td>用来连接到另外一个消息，典型的应用是在回复消息中连接到原消息 在大多数情况下，JMSCorrelationID用于将一条消息标记为对 JMSMessageID标示的上一条消息的应答，不过，JMSCorrelationID可 以是任何值，不仅仅是JMSMessageID</td>
</tr>
<tr>
<td>JMSReplyTo</td>
<td>否</td>
<td>提供本消息回复消息的目标地址，目标通常是一个临时队列</td>
</tr>
<tr>
<td>JMSType</td>
<td>否</td>
<td>消息类型的识别符</td>
</tr>
<tr>
<td>JMSRedelivered</td>
<td>是</td>
<td>如果一个客户端收到一个设置了JMSRedelivered属性的消息，则表示 可能客户端曾经在早些时候收到过该消息，但并没有签收 (acknowledged)</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="blog_h3"><span class="graybg">消息体类型</span></div>
<div class="column">JMS API 定义了5 种消息体格式，也叫消息类型：</div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">消息类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>TextMessage</td>
<td>java.lang.String 对象，如xml 文件内容</td>
</tr>
<tr>
<td>MapMessage</td>
<td>名/值对的集合，名是String 对象，值类型可以是Java 任何基本类型</td>
</tr>
<tr>
<td>BytesMessage</td>
<td>字节流</td>
</tr>
<tr>
<td>StreamMessage</td>
<td>Java 中的输入输出流</td>
</tr>
<tr>
<td>ObjectMessage</td>
<td>串行化Java对象并传输，注意不兼容其它编程语言</td>
</tr>
<tr>
<td>Message</td>
<td>没有消息体，只有消息头和属性</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">消息属性</span></div>
<div class="column">JMS设置2类消息属性：</div>
<div class="column">
<ol>
<li><span style="font-family: Ubuntu, Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif;"><span style="font-family: Ubuntu, Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif;">应用程序特定的属性。例如：</span></span><br />
<pre class="crayon-plain-tag">TextMessage msg = session.createTextMessage(); 
msg.setStringProperty( "username", username );</pre>
</li>
<li>JMS定义的属性：包含一系列JMSX开头的属性，具体请参考JMS规范</li>
</ol>
<div class="blog_h2"><span class="graybg">JMS代码示例</span></div>
<div class="blog_h3"><span class="graybg">发送消息到队列</span></div>
<pre class="crayon-plain-tag">ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory( "tcp://brokerHost:brokerPort" );

Connection connection = null;
try {
    //如果代理启用身份验证，这里需要指定用户、密码参数
    connection = factory.createConnection();
    connection.start();
    //创建会话时有事务和确认选项
    Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE ); 
   //如果代理上没有队列，则创建，否则，直接使用
    Destination dest = session.createQueue( queueName );
    //创建消息生产者
    MessageProducer producer = session.createProducer( dest );
    //设置消息是否持久化
    producer.setDeliveryMode( DeliveryMode.NON_PERSISTENT ); //创建一个文本消息
    TextMessage message = session.createTextMessage( msg ); //设置消息属性
    message.setIntProperty( "id", 1 );
    producer.send( message );
} finally {
    connection.close();
}</pre>
<div class="blog_h3"><span class="graybg">发送消息到主题</span></div>
<pre class="crayon-plain-tag">//如果代理上没有主题，则创建，否则，直接使用
Destination dest = session.createTopic( topicName );
//创建消息生产者
MessageProducer producer = session.createProducer( dest );
//设置消息是否持久化
producer.setDeliveryMode( DeliveryMode.NON_PERSISTENT ); //创建一个文本消息
TextMessage message = session.createTextMessage(msg); producer.send( message );
session.commit();</pre>
<div class="blog_h3"><span class="graybg">从队列消费消息</span></div>
<pre class="crayon-plain-tag">ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(); 
Properties props = new Properties();
// 设置预读取属性
props.setProperty( "prefetchPolicy.queuePrefetch", "1000" ); 
props.setProperty( "prefetchPolicy.queueBrowserPrefetch", "500" ); 
props.setProperty( "prefetchPolicy.durableTopicPrefetch", "100" ); 
props.setProperty( "prefetchPolicy.topicPrefetch", "32766" ); 
cf.setProperties( props );

Connection connection = null;

try {

    connection = cf.createConnection();
    connection.start(); //创建会话时有事务和确认选项，如果确认选项为:SESSION_TRANSACTED，则必须启用事务
    Session session = connection.createSession( true, Session.SESSION_TRANSACTED ); //如果代理上没有队列，则创建，否则，直接使用
    Destination dest = session.createQueue( queueName );
    //创建消息生产者
    MessageConsumer consumer = session.createConsumer( dest );
    //等待队列里面有消息可消费，最多1秒，超时返回NULL
    long timeout = 1000;
    TextMessage msg = (TextMessage) consumer.receive( timeout );
    String recMsg = msg == null ? null : msg.getText(); //如果设置:Session.CLIENT_ACKNOWLEDGE，则必须手工确认:msg.acknowledge() 
    session.commit();//如果设置:Session.SESSION_TRANSACTED，则必须提交或者回滚 return recMsg;
} finally {
    connection.close();
}</pre>
<div class="blog_h3"><span class="graybg">订阅一个主题</span></div>
<pre class="crayon-plain-tag">public void subscribeTo1pic( String topicName, final Callback&lt;Void, String&gt; cb ) throws JMSException {
    Connection connection = null;
    try {
        connection = factory.createConnection();
        connection.start();
        Session session = connection.createSession( false, Session.CLIENT_ACKNOWLEDGE );
        Destination dest = session.createTopic( topicName );
        MessageConsumer consumer = session.createConsumer( dest ); 
        //注意:MessageListener亦适用于队列
        consumer.setMessageListener( new MessageListener() {
            @Override
            public void onMessage( Message message ) {
                TextMessage msg = (TextMessage) message;
                try {
                    cb.callback( msg.getText() );
                    msg.acknowledge();
                } catch ( Throwable t ) {
                    LOGGER.error( "Failed to Process message: " + t.getMessage(), t );
                }
            }
        } );
    } finally {
        connection.close();
    }
}</pre>
<div class="blog_h1"><span class="graybg">ActiveMQ简介</span></div>
</div>
</div>
</div>
</div>
<p>ActiveMQ是一个完全支持JMS1.1和J2EE1.4规范的JMS Provider实现。</p>
<div class="blog_h2"><span class="graybg">特性</span></div>
<ol>
<li>多种语言编写客户端:Java, C, C++, C#，Ruby, Perl, Python, PHP</li>
<li>支持多种应用协议:OpenWire、Stomp REST、WS Notification、XMPP、AMQP</li>
<li>完全支持JMS1.1和J2EE1.4规范(持久化，XA消息，事务)。支持分布式事务</li>
<li>对Spring的支持,ActiveMQ可以很容易内嵌到使用Spring的系统里</li>
<li>一支持多种传送协议:in-VM、TCP、SSL、NIO、UDP、JGroups、JXTA</li>
<li>支持通过JDBC和journal提供高速的消息持久化</li>
<li>支持多种集群特征:故障转移(主/从结构，HA)、ActiveMQ网络集群</li>
<li>支持自动发现:基于组播或者ZeroConf技术，可以自动化的建立ActiveMQ 集群（Network of Brokers，代理网络），或者(客户端)连接到ActiveMQ</li>
<li>与开源ESB产品例如Camel紧密集成</li>
<li>安全性保证:ActiveMQ支持基于SSL的传输，在广域网上保证加密的传输，结 合数字证书，可以保证双向身份验证</li>
</ol>
<div class="blog_h2"><span class="graybg">基本概念</span></div>
<div class="blog_h3"><span class="graybg">代理(Broker)</span></div>
<p>即一个运行中的ActiveMQ实例，该实例可以独立运行，也可以嵌入在已有的 JVM中运行。从这个名字也可以看出基于ActiveMQ的消息系统的运行方式。</p>
<div class="blog_h3"><span class="graybg">传输(Transport)</span></div>
<p>表示ActiveMQ使用的消息传递机制，这是一个混合的概念，不是简单的指网络 协议或者应用协议。用于将ActiveMQ实例暴露给客户端，或者其它代理以构建代理网络。常用 的传输包括：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">传输名称</td>
<td style="width: 12%; text-align: center;">URI前缀</td>
<td style="width: 10%; text-align: center;">端口</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>TCP</td>
<td>tcp</td>
<td>61616</td>
<td>传统的TCP套接字</td>
</tr>
<tr>
<td>NIO</td>
<td>nio</td>
<td>61618</td>
<td>需要更少的线程，提供更好的性能</td>
</tr>
<tr>
<td>UDP</td>
<td>udp</td>
<td> </td>
<td>防火墙，低延迟</td>
</tr>
<tr>
<td>SSL</td>
<td>ssl</td>
<td> </td>
<td>提供加密传输</td>
</tr>
<tr>
<td>HTTP/HTTPS</td>
<td>http(s)</td>
<td> </td>
<td>可以供基于浏览器的客户端使用</td>
</tr>
<tr>
<td>VM Protocol</td>
<td>vm</td>
<td> </td>
<td>
<p>允许在JVM内部通信，从而避免了网络传输的开销</p>
<p>同一JVM中的其它代码可以直接使用该传输，无需额外配置</p>
</td>
</tr>
</tbody>
</table>
<p>在配置传输的同时，可选的可以指定在其上使用的线路协议。Openwire协议最常用，高效、可靠。</p>
<div class="blog_h3"><span class="graybg">发现(Discovery)</span></div>
<p>ActiveMQ使用一个叫发现代理(Discovery Agent)的机制，来用于发现远程 服务，例如远程代理。使用此机制，可以：</p>
<ol>
<li>让客户端自动连接到代理</li>
<li>构建 代理的网络。</li>
</ol>
<p>目前，有两种类型的发现代理：</p>
<ol>
<li>Multicast：ActiveMQ内置的一种发现代理，可以定位代理的URI列表。参考本文稿后续的 Discovery传输</li>
<li>Zeroconf：Zeroconf是一种标准，基于UDP/multicast，ActiveMQ使用的是jmDNS库，该库是Zeroconf标准的实现</li>
</ol>
<div class="blog_h2"><span class="graybg">集群特性</span></div>
<div class="blog_h3"><span class="graybg">队列消费者集群</span></div>
<p>支持消费者的高可用性、负载均衡：</p>
<ol>
<li>如果一个Consumer死掉，该消息会转发到其它的Consumer消费的Queue上</li>
<li>如果一个Consumer获得消息比其 它Consumer快，那么他将获得更多的消息</li>
<li>如果一个Conseumer消费缓慢，则 其它Consumer会替换它</li>
</ol>
<div class="blog_h3"><span class="graybg">代理集群</span></div>
<p>如果一个Broker死掉了，Client可以自动链接到其它Broker 上（可以使用故障转移传输failover:// 来配置客户端）。</p>
<p>构建代理集群时，可以使用：</p>
<ol>
<li>静态发现，静态指定其它代理的列表</li>
<li>动态发现，使用动态发现（组播、零配置）机制自动获取其它代理</li>
</ol>
<div class="blog_h3"><span class="graybg">主从代理</span></div>
<p>可以配置两个Broker的主从关系，并保证二者的状态完全一致。这种方式主要 用于提供高可用性。</p>
<div class="blog_h3"><span class="graybg">代理网络</span></div>
<p>ActiveMQ实例（代理）到实例的通信，是跨地域、跨网络的消息可靠传输的关键。通常的部署方式是，在<span style="background-color: #c0c0c0;">每个局域网设置一个代理，代理与代理在广域网 之间进行交互</span>，<span style="background-color: #c0c0c0;">客户端与局域网内的代理交互</span>——这样的方式尽量减少广域网 上的流量和连接数。 </p>
<p>代理网络是以通道的形式将一个Broker和其他的Broker链接起来通信。 代理网络默认是单向的:一个Broker在一端发送消息，另一Broker在另一端接 收消息，这就是所谓的“桥接”。</p>
<p>在ActiveMQ 5.x，也可以创建一个双向(duplex)的通道对于两个Broker。他将不仅发送消息而且也能从相同的通道来接收消息。 双向通道的配置，会自动传递到对方代理上。</p>
<div class="blog_h2"><span class="graybg">高级特性</span></div>
<div class="blog_h3"><span class="graybg">复合目标(Composite Destinations) </span></div>
<p>允许用一个虚拟的destination 代表多个destinations。例如可以通过 composite destinations在一个操作中同时向12个queue发送消息。</p>
<div class="blog_h3"><span class="graybg">通配符目标(Wildcards Desitination)</span></div>
<p>用来支持联合的名字分层体系(federated name hierarchies)。ActiveMQ支持以下三种通配符：</p>
<ol>
<li><pre class="crayon-plain-tag">.</pre>用作路径上名字间的分隔符</li>
<li><pre class="crayon-plain-tag">*</pre>用于匹配任意字符，但是不能跨越点号</li>
<li><pre class="crayon-plain-tag">&gt;</pre>用于匹配任意字符，可以跨越点号</li>
</ol>
<p>考虑一个报价应用的主题命名，需要区分商品类别、市场名称、商品名称：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">主题</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>PRICE.&gt;</td>
<td>任何商品在任何交易市场的价格</td>
</tr>
<tr>
<td>PRICE.STOCK.&gt;</td>
<td>任何市场的股票价格</td>
</tr>
<tr>
<td>PRICE.STOCK.NASDAQ.*</td>
<td>纳斯达克市场任何股票的价格</td>
</tr>
<tr>
<td>PRICE.STOCK.*.IBM</td>
<td>IBM的股票在任何市场的价格</td>
</tr>
</tbody>
</table>
<div class="page" title="Page 22">
<div class="section">
<div class="layoutArea">
<div class="column">
<div class="blog_h3"><span class="graybg">异步发送(Async Sends) </span></div>
</div>
</div>
</div>
</div>
<p>默认选项，消息Producer不需要等待Consumer确认签收即完成发送操作。 </p>
<div class="blog_h3"><span class="graybg">批量确认(Optimized Acknowledgement)</span></div>
<p>ActiveMQ缺省支持批量确认消息。由于批量确认会提高性能，因此这是缺省的 确认方式。 </p>
<div class="blog_h3"><span class="graybg">严格顺序分发策略(Strict Order Dispatch Policy) </span></div>
<p>ActiveMQ可以保证<span style="background-color: #c0c0c0;">主题的所有消费者以相同的顺序接收消息</span>。</p>
<p>默认的ActiveMQ会保证所有消费者以相同顺序接收来自<span style="background-color: #c0c0c0;">同一生产者的消息</span>。然而，由于多线程和异步处理，不同生产者产生的消息的接受顺序无法保证。此行为可以通过destinationPolicy实现。</p>
<div class="blog_h3"><span class="graybg">通知消息(Advisory Message)</span></div>
<p>这类消息由ActiveMQ系统内部使用，用于收发系统事件，例如新的消费者的注册。代理网络很大程度上依赖通知消息工作。</p>
<div class="blog_h3"><span class="graybg">消息转换</span></div>
<p>可以在内部进行消息格式的转换，提供MessageTransformer接口。 在ActiveMQConnectionFactory、ActiveMQConnection、ActiveMQSession、 ActiveMQMessageConsumer、ActiveMQMessageProducer等对象上调用 setTransformer方法可以设置消息消息转换器。</p>
<div class="blog_h3"><span class="graybg">独占消费者/独占队列</span></div>
<p>Queue中的消息是按照顺序被分发到consumers的。然而，当你有多个 consumers同时从相同的queue中提取消息时，你将失去这个保证。因为这些消 息是被多个线程并发的处理。有的时候，保证消息按照顺序处理是很重要的。 例如，你可能不希望在插入订单操作结束之前执行更新这个订单的操作。 ActiveMQ从4.x版本起开始支持Exclusive Consumer (或者说Exclusive Queues)。 Broker会从多个consumers中挑选一个consumer来处理queue中所 有的消息，从而保证了消息的有序处理。如果这个consumer失效，那么broker 会自动切换到其它的consumer。</p>
<div class="blog_h3"><span class="graybg">消息分组(Message Group)</span></div>
<p>设置属性JMSXGroupID，可以保证同一个组的数据，仅被发送给一个Consumer。 可以在大消息分割的场景下使用。</p>
<div class="blog_h3"><span class="graybg">消息选择器(Message Selector)</span></div>
<p>JMS Selectors用于在订阅中，基于消息属性和Xpath语法对进行消息的过滤。JMS Selectors由SQL92语义定义。JMS Selectors用于在订阅中，基于消息属性和Xpath语 法对进行消息的过滤。</p>
<div class="blog_h3"><span class="graybg">死信队列</span></div>
<p>默认的死信队列名称为:ActivemMQ.DLQ。所有不能消费的消息被传递到该死队列中。 过期消息的处理：</p>
<ol>
<li>持久化消息默认是存入死信队列，设置<pre class="crayon-plain-tag">processExpired=false</pre>，则直接删除</li>
<li>非持久化消息，必须设置<pre class="crayon-plain-tag">processNonPersistent=true</pre>才能放入死信队列</li>
</ol>
<div class="blog_h3"><span class="graybg">分布式队列与主题</span></div>
<p>要创建分布式队列或主题，需要进行代理之间的通信，ActiveMQ支持两种类型的代理通信方式：</p>
<ol>
<li>主/从代理：用于提供高可用性，<span style="background-color: #c0c0c0;">消息在主从代理之间保持复制</span>。当主代理宕机，则从 代理自动接管并提供服务</li>
<li>存储和转发—代理网络：消息从一个<span style="background-color: #c0c0c0;">在代理之间进行传递，直到其到达消费者那里</span>， 每个消息在一个时刻，只会属于单个代理(即时它宕掉)</li>
</ol>
<p>分布式队列：Producer发布一个消息到代理，代理将其存储，如果该代理配置了 store/forward到其它代理，消息将会按照一定的算法转发给其它代理。<span style="background-color: #c0c0c0;">注意:只有目 标代理上有该消息的消费者，消息才会被转发</span></p>
<p>分布式主题：与分布式队列类似，不同的是，每个队主题感兴趣的客户端，均会 收到一份消息的拷贝，ActiveMQ内置算法保证不会在环形网络中出现无限循环。</p>
<div class="blog_h1"><span class="graybg">管理和配置</span></div>
<p>本章内容主要讨论ActiveMQ实例（服务器端）的管理和配置。</p>
<div class="page" title="Page 26">
<div class="section">
<div class="layoutArea">
<div class="column">
<div class="blog_h2"><span class="graybg">运行方式</span></div>
</div>
</div>
</div>
</div>
<div class="blog_h3"><span class="graybg">独立运行</span></div>
<p>解压ActiveMQ二进制包到任意目录，把它的bin目录添加到PATH环境变量中。执行InstallService.bat可以安装为Windows服务。</p>
<p>在命令行中执行<pre class="crayon-plain-tag">activemq start</pre>即可启动ActiveMQ实例。运行<pre class="crayon-plain-tag">activemq stop</pre>则停止实例。</p>
<p>默认的Web管理控制台位于:http://localhost:8161/admin 默认用户密码均为admin。</p>
<p>默认的运行日志位于data/activemq.log。</p>
<p>默认的消息持久化数据库位于data/kahadb。</p>
<div class="blog_h3"><span class="graybg">嵌入已有JVM运行</span></div>
<p>ActiveMQ独立运行时，内部维护了一个Spring IoC容器的实例，其相关服务均是以 Spring Bean的形式存在的，因此，嵌入已有JVM运行非常简单，只需要提供Spring 支持即可。 此外，ActiveMQ提供了一系列的API，方便使用纯编程的方式启动、配置实例。</p>
<p><span style="background-color: #c0c0c0;"> 只有在嵌入模式下，才可以使用VM Transport</span>。</p>
<div class="blog_h2"><span class="graybg">基于编码配置</span></div>
<p>ActiveMQ服务器可以已硬编码的方式进行配置，而不使用XML配置文件。通常情况下不需要 使用硬编码方式，除非需要进行运行时动态的控制。 </p>
<div class="blog_h3"><span class="graybg">简单示例</span></div>
<pre class="crayon-plain-tag">public void start() throws Exception {
    //代理服务对象
    broker = new BrokerService();


    //基于AMQ日志的持久化适配器
    JournalPersistenceAdapterFactory jpaf = new JournalPersistenceAdapterFactory();
    //日志数量
    jpaf.setJournalLogFiles( 5 );
    //日志文件大小
    jpaf.setJournalLogFileSize( 1024 * 1024 * 128 );
    broker.setPersistenceFactory( jpaf ); //设置持久化机制
    
    // 基于KahaDB的持久化适配器
    KahaDBPersistenceAdapter pa = new KahaDBPersistenceAdapter();
    pa.setCheckForCorruptJournalFiles( true );
    File userHome = new File( System.getProperty( "user.home" ) );
    File amqDataDir = new File( userHome, "ActiveMQ/data" );
    amqDataDir.mkdirs();
    pa.setDirectory( amqDataDir );
    broker.setPersistenceAdapter( pa );

    broker.setBrokerName( "UniqueName" ); //设置代理的名称，每个代理应该具有独特的名称 
    broker.addConnector( "tcp://localhost:61616" ); //设置传输连接器 
    broker.setPersistent( false ); //如果写这一句，那么仅使用内存来存储消息 
    broker.start();
}

public void stop() throws Exception {
    broker.stop();
}</pre>
<div class="blog_h3"><span class="graybg">基于发现传输的配置</span></div>
<p>下面的代码示例了两个基于发现传输连接在一起的代理协同工作的场景：</p>
<pre class="crayon-plain-tag">public class DiscoveryBroker {

    public static final String QUEUE_NAME = "AMQ.BrokerB";

    public static BrokerService create( String name ) throws Exception {
        BrokerService broker = new BrokerService();

        broker.setBrokerName( name );
        broker.setPersistent( false );
        broker.setUseJmx( false );

        // 异步启动网络连接器
        broker.setStartAsync( true );

        // 基于发现机制的网络连接器：发现其它代理
        List&lt;NetworkConnector&gt; ncs = new ArrayList&lt;&gt;();
        URI discoveryURI = new URI( "multicast://default?group=pems" );
        // DiscoveryNetworkConnector是默认的网络连接器实现，不管是否基于动态发现
        NetworkConnector nc = new DiscoveryNetworkConnector( discoveryURI );
        // 允许双向通信
        nc.setDuplex( true );
        nc.setName( "NC_" + name );
        ncs.add( nc );

        // 如果使用静态URI，类似：broker.addNetworkConnector("static://tcp://host:port");
        // broker.addNetworkConnector( nc );
        
        broker.setNetworkConnectors( ncs );

        // 基于发现机制的传输连接器：让别人连接到当前代理
        List&lt;TransportConnector&gt; tcs = new ArrayList&lt;&gt;();
        TransportConnector tc = new TransportConnector();
        tc.setUri( new URI( "tcp://0.0.0.0:0" ) );
        tc.setDiscoveryUri( discoveryURI );
        tcs.add( tc );
        broker.setTransportConnectors( tcs );

        return broker;
    }
}

public class DiscoveryBrokerA extends DiscoveryBroker {

    private static final Logger LOGGER = LoggerFactory.getLogger( DiscoveryBrokerA.class );

    public static void main( String[] args ) throws Exception {
        BrokerService brokerA = DiscoveryBroker.create( "BrokerA" );
        brokerA.start();
        new Thread( new Runnable() {
            @Override
            public void run() {
                ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory( "vm://BrokerA" );
                Connection conn;
                try {
                    conn = factory.createConnection();
                    // 连接必须启动
                    conn.start();
                    Session session = conn.createSession( false, Session.AUTO_ACKNOWLEDGE );
                    Destination dest = session.createQueue( QUEUE_NAME );
                    MessageProducer producer = session.createProducer( dest );
                    producer.setDeliveryMode( DeliveryMode.NON_PERSISTENT );
                    List&lt;String&gt; prevChildren = new ArrayList&lt;&gt;();
                    int count = 0;
                    for ( ; ; ) {
                        LOGGER.debug( "Sending message to queue {}", QUEUE_NAME );
                        MapMessage msg = new ActiveMQMapMessage();
                        msg.setString( "name", "Alex" );
                        msg.setInt( "age", 30 );
                        msg.setBoolean( "married", true );
                        Map&lt;String, Object&gt; meng = new LinkedHashMap&lt;&gt;();
                        meng.put( "name", "Meng" );
                        meng.put( "age", 27 );
                        msg.setObject( "spouse", meng );
                        List&lt;String&gt; children = new ArrayList&lt;&gt;( prevChildren );
                        msg.setObject( "children", children );
                        children.add( "Child" + ++count );
                        producer.send( msg );
                        prevChildren = children;

                        TimeUnit.SECONDS.sleep( 10 );
                    }
                } catch ( Exception e ) {
                    LOGGER.error( e.getMessage(), e );
                }
            }
        } ).start();
    }
}

public class DiscoveryBrokerB extends DiscoveryBroker {

    private static final Logger LOGGER = LoggerFactory.getLogger( DiscoveryBrokerB.class );

    public static void main( String[] args ) throws Exception {
        BrokerService brokerB = DiscoveryBroker.create( "BrokerB" );
        brokerB.start();
        new Thread( new Runnable() {
            @Override
            public void run() {
                ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory( "vm://BrokerB" );
                Connection conn;
                try {
                    conn = factory.createConnection();
                    conn.start();
                    Session session = conn.createSession( false, Session.AUTO_ACKNOWLEDGE );
                    Destination dest = session.createQueue( QUEUE_NAME );
                    MessageConsumer consumer = session.createConsumer( dest );
                    consumer.setMessageListener( new MessageListener() {
                        @Override
                        public void onMessage( Message message ) {
                            MapMessage msg = (MapMessage) message;
                            Object children = null;
                            try {
                                children = msg.getObject( "children" );
                            } catch ( JMSException e ) {}
                            LOGGER.debug( "Received message from {}, children property: {}", QUEUE_NAME, children );
                        }
                    } );
                } catch ( JMSException e ) {
                    LOGGER.error( e.getMessage(), e );
                }
            }
        } ).start();
        TimeUnit.SECONDS.sleep( 600 );
    }
}</pre>
<div class="blog_h2"><span class="graybg">基于XML配置</span></div>
<p>无论是独立运行还是嵌入已有VM运行，ActiveMQ的配置均是以 Springframework为基础的，整体配置框架如下：</p>
<pre class="crayon-plain-tag">&lt;beans xmlns="http://www.springframework.org/schema/beans" xmlns:amq="http://activemq.apache.org/schema/core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd"&gt;
    &lt;broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="data/kahadb"&gt;
        &lt;networkConnectors&gt;网络连接器，用于构建代理网络&lt;/networkConnectors&gt;
        &lt;transportConnectors&gt;传输连接器，代理在此连接器上等待客户端、其它代理的连接&lt;/transportConnectors&gt; 
        &lt;persistenceAdapter&gt;配置持久化适配器，实现消息持久化存储&lt;/persistenceAdapter&gt; 
        &lt;destinationPolicy&gt;为目标设置各种控制策略&lt;/destinationPolicy&gt; 
        &lt;managementContext&gt;&lt;/managementContext&gt; 
        &lt;systemUsage&gt;限制使用系统资源的量&lt;/systemUsage&gt; 
        &lt;plugins&gt;&lt;/plugins&gt;
    &lt;/broker&gt; 
&lt;/beans&gt;</pre>
<div class="blog_h2"><span class="graybg">传输配置</span></div>
<p>transportConnectors子元素用于配置ActiveMQ使用的传输，其每个子元素 transportConnector定义一种传输方式，包含name、uri等元素，可以配置多个传输方式 —— 也就是说一个ActiveMQ实例可以支持很多底层通信机制。</p>
<p>传输连接器可以用于：</p>
<ol>
<li>虚拟机外部的客户端连接到代理</li>
<li>其它代理连接到当前代理</li>
</ol>
<p>基本配置格式如下：</p>
<pre class="crayon-plain-tag">&lt;transportConnectors&gt;
    &lt;transportConnector name="" uri=""/&gt;
&lt;/transportConnectors&gt;</pre>
<div class="blog_h3"><span class="graybg">通用选项</span></div>
<p>transportConnector包含以下服务器端选项：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>name</td>
<td>默认null，此传输连接器的名称</td>
</tr>
<tr>
<td>uri</td>
<td>
<p>默认null，URI的格式为：<pre class="crayon-plain-tag">protocol://host:port?key=val&amp;key=val</pre>，问号后面的是参数列表。URI的示例：tcp://0.0.0.0:61616、amqp://0.0.0.0:5672、ssl://localhost:61616。</p>
<p>这些URI包含了两方面的含义，一方面是声明代理支持的连接方式，系统会做相应的处理以便支持该连接;另一方面，客户端也使用此格式的URI来获取到代理的连接</p>
<p>注意，URI会构建为java.net.URI对象，因此任何空白符都是不允许的</p>
</td>
</tr>
<tr>
<td>discoveryURI</td>
<td>默认null，可以设置一个组播发现地址，以便客户端、其它代理自动发现当前代理</td>
</tr>
<tr>
<td>enableStatusMonitor</td>
<td>默认false，可以监控连接状态，判断其是否被阻塞（Blocked） </td>
</tr>
<tr>
<td>updateClusterClients</td>
<td>默认false，当代理集群发生改变后，是否更新客户端连接（如果客户端使用failover:// transport的话）</td>
</tr>
<tr>
<td>rebalanceClusterClients</td>
<td>默认false，当代理网络的拓扑改变后，是否在集群内部重新平衡客户端（负载均衡） </td>
</tr>
<tr>
<td>updateClusterClientsOnRemove</td>
<td>默认false，当代理从集群中移除后，是否更新客户端 </td>
</tr>
<tr>
<td>updateClusterFilter</td>
<td>默认null，逗号分隔的正则式。代理名称匹配其一的代理，可以引发客户端更新</td>
</tr>
<tr>
<td>allowLinkStealing</td>
<td>
<p>默认false，对于MQTT则默认启用</p>
<p>所谓连接偷取，是指最后两个/多个具有相同ID（JMS的clientID）的连接，被认为是合法的，并且比较老的连接会被代理自动丢弃</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">客户端可用选项</span></div>
<p>本节介绍客户端也支持的选项，发起连接的时候可以使用。注意这些选项：</p>
<ol>
<li>可以在传输URI中作为参数。示例<pre class="crayon-plain-tag">tcp://localhost:61616?jms.useAsyncSend=true</pre></li>
<li>也可以通过编程方式设置ActiveMQConnectionFactory、ActiveMQConnection对象的属性</li>
<li>当在<span style="background-color: #c0c0c0;">brokerURL</span>或者<span style="background-color: #c0c0c0;">代理的传输连接器</span>中使用这些选项时，必须加上<pre class="crayon-plain-tag">jms.</pre>前缀</li>
</ol>
<p>选项列表：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>alwaysSessionAsync</td>
<td>
<p>默认true，表示为Connection中的每个Session分配独立的线程，用于消息的派发</p>
<p>当存在多个会话，或者会话不处于Session.AUTO_ACKNOWLEDGE或Session.DUPS_OK_ACKNOWLEDGE模式，则总是使用独立线程</p>
</td>
</tr>
<tr>
<td>alwaysSyncSend</td>
<td>
<p>默认false，设置为true则MessageProducer总是同步的发送消息，甚至是递送模式（Delivery Mode）不需要同步的情况下</p>
</td>
</tr>
<tr>
<td>auditDepth</td>
<td>默认2048，审计重复、乱序消息时，消息窗口的大小（即审计探测的深度）</td>
</tr>
<tr>
<td>auditMaximumProducerNumber</td>
<td>默认64，最多被审计的生产者的数量</td>
</tr>
<tr>
<td>checkForDuplicates</td>
<td>默认true，消费者是否检查重复消息，并且进行适当处理，避免同一消息被意外的处理两次</td>
</tr>
<tr>
<td>clientID</td>
<td>默认null，为连接设置JMS的clientID</td>
</tr>
<tr>
<td>closeTimeout</td>
<td>
<p>默认15000，单位ms。默认情况下，在连接上执行close()操作需要等待代理的确认，此超时防止代理不可用时客户端的无限等待</p>
</td>
</tr>
<tr>
<td>consumerExpiryCheckEnabled</td>
<td>默认true，是否让消费者检查消息是否过期，禁用后可能消费已经过期的消息</td>
</tr>
<tr>
<td>copyMessageOnSend</td>
<td>
<p>默认true，当基于JMS的send()发送消息时，是否把消息拷贝到一个新的JMS Message对象中。默认设置为true 以便和JMS规范兼容</p>
<p>如果你再send()后不会再修改消息，则可以设置为false，这样<span style="background-color: #c0c0c0;">可以提升性能</span></p>
</td>
</tr>
<tr>
<td>disableTimeStampsByDefault</td>
<td>默认false，是否禁用消息的时间戳，禁用后可以获得<span style="background-color: #c0c0c0;">较小的性能提升</span></td>
</tr>
<tr>
<td>dispatchAsync</td>
<td>
<p>默认false，代理是否应该异步的分发消息给消费者</p>
<p>如果消费者比较慢，则异步递送消息更加有意义。当消费者很快的时候，同步递送可以避免synchronization和上下文切换的开销</p>
<p>当基于同步递送时，生产者可能因为消费者缓慢而被阻塞</p>
</td>
</tr>
<tr>
<td>nestedMapAndListEnabled</td>
<td>
<p>默认true，是否支持结构化消息属性、MapMessage，并且支持内嵌的Map或List对象</p>
<p>JMS扩展特性允许你附带Map、List属性到JMS消息对象。或者，直接在MapMessage中使用内嵌的Map或者List对象</p>
<p>这一特性高效的发送类型安全的结构信息，而避免串行化/反串行化的开销</p>
<p>相关方法：</p>
<pre class="crayon-plain-tag">Message.setObjectProperty(key, value)
MapMessage.setObject(key, value)</pre>
<p>其中value参数可以是Map、List、数字、字符串类型及其嵌套结构</p>
</td>
</tr>
<tr>
<td>objectMessageSerializationDefered</td>
<td>
<p>默认false，当通过设置对象到ObjectMessage上时，JMS规范要求对象被set()方法立即串行化</p>
<p>设置为true，则不兼容JMS规范，仅仅在需要通过套接字发送消息时，才进行串行化 </p>
</td>
</tr>
<tr>
<td>optimizeAcknowledge</td>
<td>
<p>默认false。是否启用优化的消息签收模式 —— 批量的签收</p>
<p>作为备选，你可以在消费者上设置Session.DUPS_OK_ACKNOWLEDGE签收模式，通常会更快一些</p>
<p>警告：启用后，可能导致重新连接后与自动签收有关的问题 </p>
</td>
</tr>
<tr>
<td>optimizeAcknowledgeTimeOut</td>
<td>默认300，单位ms。两次批量签收行为的最大间隔</td>
</tr>
<tr>
<td>optimizedAckScheduledAckInterval</td>
<td>
<p>默认0，单位ms。如果大于0则经过指定的间隔，所有未签收的消息都被批量签收</p>
<p>可以防止长时间运行的消费者，不再接收消息后，未签收的消息有机会被签收 </p>
</td>
</tr>
<tr>
<td>optimizedMessageDispatch</td>
<td>默认true。仅对于持久化订阅有意义，设置为true则具有更大的预读取（prefetch）限制 </td>
</tr>
<tr>
<td>useAsyncSend</td>
<td>
<p>默认false。是否使用异步发送</p>
<p>强制使用异步发送可以<span style="background-color: #c0c0c0;">获得很大的性能提升</span>。但是这意味着send()会立即返回，而不管消息是否被递送，因而可能导致消息丢失</p>
</td>
</tr>
<tr>
<td>useCompression</td>
<td>默认false。是否启用消息体压缩</td>
</tr>
<tr>
<td>useRetroactiveConsumer</td>
<td>
<p>默认false。是否启用可追溯消费者</p>
<p>可追溯消费者允许非持久化的主题订阅者能够接收到老旧（在非持久化订阅者启动之前发布）的消息</p>
</td>
</tr>
<tr>
<td>warnAboutUnstartedConnectionTimeout</td>
<td>默认500，单位ms。创建JMS Connection后，必须start()才能接受消息。这个选项可以在你忘记start()时给予提示</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">AUTO传输</span></div>
<p>在计算机网络领域，线路协议（wire protocol）这一术语通常和传输协议（transport protocols）—— 例如 TCP或者UDP ——进行区分，它用于在应用程序级别表示信息的方式。这些协议可以是基于文本的，也可以是二进制的。</p>
<p>从5.13.0开始，ActiveMQ支持在TCP、SSL、NIO、NIO SSL之上进行自动的<span style="background-color: #c0c0c0;">线路协议检测</span>，支持的线路协议包括：OpenWire、STOMP、AMQP、MQTT。</p>
<p>配置示例：</p>
<pre class="crayon-plain-tag">&lt;!-- 在TCP之上的自动线路协议检测 --&gt;
&lt;transportConnector name="auto" uri="auto://localhost:5671"/&gt;
&lt;!-- 在启用SSL的TCP之上的自动线路协议检测 --&gt;
&lt;transportConnector name="auto+ssl" uri="auto+ssl://localhost:5671"/&gt;
&lt;!-- 在NIO TCP之上的自动线路协议检测 --&gt;
&lt;transportConnector name="auto+nio" uri="auto+nio://localhost:5671"/&gt;
&lt;!-- 在NIO SSL TCP之上的自动线路协议检测 --&gt;
&lt;transportConnector name="auto+nio+ssl" uri="auto+nio+ssl://localhost:5671"/&gt;</pre>
<div class="blog_h3"><span class="graybg">VM传输 </span></div>
<p>用于支持在JVM内部进行客户端之间的连接，避免网络占用。第一个启用VM连接的 客户端，会启动一个内嵌的Broker实例。最后一个客户端断开，则会停止实例。</p>
<p>URI示例：</p>
<pre class="crayon-plain-tag">vm://brokerName?transportOptions</pre>
<p>更复杂的URI格式：</p>
<pre class="crayon-plain-tag">vm:(broker:(tcp://localhost)?brokerOptions)?transportOptions</pre>
<p> VM传输不是基于网络套接字，而是直接的Java方法调用，因此速度很快。</p>
<p>此传输专有选项： </p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">选项</td>
<td style="width: 15%; text-align: center;">默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>marshal</td>
<td>false</td>
<td>如果设置为true，则基于此传输发送的任何命令，均使用某种WireFormat进行分解（unmarshall）和编组（marshall）</td>
</tr>
<tr>
<td>wireFormat</td>
<td>default</td>
<td>使用的WireFormat工厂的名称</td>
</tr>
<tr>
<td>wireFormat.*</td>
<td> </td>
<td>这些属性用来配置WireFormat</td>
</tr>
<tr>
<td>create</td>
<td>true</td>
<td>如果代理不存在，是否按需创建</td>
</tr>
<tr>
<td>waitForStart</td>
<td>-1</td>
<td>如果大于0，表示等待代理启动的毫秒数</td>
</tr>
<tr>
<td>broker.*</td>
<td> </td>
<td>这些属性用来配置代理</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">AMQP传输</span></div>
<p>高级消息队列协议（Advanced Message Queuing Protocol，AMQP），是提供统一消息服务的应用层标准高级消息队列协议，是一个开放标准。ActiveMQ支持该协议的1.0版本。</p>
<p>注意使用AMQP时，目标的地址前缀以queue://或者topic://，前者为默认，可以省略。</p>
<p>配置示例：</p>
<pre class="crayon-plain-tag">&lt;transportConnectors&gt;
   &lt;transportConnector name="amqp" uri="amqp://0.0.0.0:5672"/&gt;
&lt;/transportConnectors&gt;

&lt;!-- AMQP over NIO --&gt;
&lt;transportConnector name="amqp+nio" uri="amqp+nio://localhost:5672"/&gt;
&lt;!-- AMQP over SSL --&gt;
&lt;transportConnector name="amqp+ssl" uri="amqp+ssl://localhost:5671"/&gt;</pre>
<p>关于此传输的更多信息，参考<a href="http://activemq.apache.org/amqp.html">ActiveMQ文档</a>。</p>
<div class="blog_h3"><span class="graybg">TCP传输</span></div>
<p>允许客户端基于TCP套接字连接到远程的ActiveMQ代理。示例：</p>
<pre class="crayon-plain-tag"># 服务器端，在传输连接器的属性中配置
tcp://localhost:61616?transport.threadName&amp;transport.trace=false&amp;transport.soTimeout=60000
# 客户端，在代理URI中配置
tcp://localhost:61616?threadName&amp;trace=false&amp;soTimeout=60000</pre>
<p>此传输专有选项： </p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">选项</td>
<td style="width: 15%; text-align: center;">默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>backlog</td>
<td>5000</td>
<td>此传输的服务端，等待被接受的连接的队列的大小</td>
</tr>
<tr>
<td>closeAsync</td>
<td>true</td>
<td>
<p>是否异步的关闭套接字，对于STOMP之类的线路协议，此选项应设置为false</p>
<p>STOMP之类的协议，通常为每个读写操作打开一个套接字，同步的关闭可以防止代理没有可用的套接字（当套接字回收极快的情况下）</p>
</td>
</tr>
<tr>
<td>connectionTimeout</td>
<td>30000</td>
<td>连接超时，单位ms</td>
</tr>
<tr>
<td>daemon</td>
<td>false</td>
<td>传输的线程是否工作在守护模式下，当<span style="background-color: #c0c0c0;">嵌入到其它JVM或者Web应用中运行ActiveMQ代理时应该设置为true</span>，这样容器才能正确的关闭</td>
</tr>
<tr>
<td>diffServ</td>
<td>0</td>
<td>
<p>仅客户端使用。设置出站流量的差异化服务流量（ Differentiated Services traffic）的类别</p>
</td>
</tr>
<tr>
<td>dynamicManagement</td>
<td>false</td>
<td>设置为true则TransportLogger可以被JMX管理</td>
</tr>
<tr>
<td>ioBufferSize</td>
<td>8 * 1024</td>
<td>TCP层和OpenWire层之间的缓冲，此缓冲在基于wireFormat进行编组时使用</td>
</tr>
<tr>
<td>jmxPort</td>
<td>1099</td>
<td>JMX的端口</td>
</tr>
<tr>
<td>keepAlive</td>
<td>false</td>
<td>是否启用TCP保活，用于防止在TCP层出现连接超时</td>
</tr>
<tr>
<td>logWriterName</td>
<td>default</td>
<td>org.apache.activemq.transport.LogWriter实现的名字</td>
</tr>
<tr>
<td>maximumConnections</td>
<td>Integer.MAX_VALUE</td>
<td>此代理允许的最大连接数</td>
</tr>
<tr>
<td>minmumWireFormatVersion</td>
<td>0</td>
<td>最小支持的远程wireFormat版本，0表示不检查版本</td>
</tr>
<tr>
<td>socketBufferSize</td>
<td>64 * 1024</td>
<td>套接字读写缓冲的大小</td>
</tr>
<tr>
<td>soLinger</td>
<td>Integer.MIN_VALUE</td>
<td>设置TCP的soLinger选项，设置为-1则禁用此选项。当套接字被关闭时，此选项影响缓冲中残留的尚未发送的流量的处理方式</td>
</tr>
<tr>
<td>soTimeout</td>
<td>0</td>
<td>套接字读超时时间，如果操作没有在超时前完成会导致套接字被关闭</td>
</tr>
<tr>
<td>soWriteTimeout</td>
<td>0</td>
<td>套接字写超时时间，如果操作没有在超时前完成会导致套接字被关闭</td>
</tr>
<tr>
<td>stackSize</td>
<td>0</td>
<td>此传输的后台读线程的栈大小，必须设置为128K的倍数</td>
</tr>
<tr>
<td>tcpNoDelay</td>
<td>false</td>
<td>是否启用TCP_NODELAY选项</td>
</tr>
<tr>
<td>threadName</td>
<td> </td>
<td>设置传输的后台线程的名称</td>
</tr>
<tr>
<td>trace</td>
<td>false</td>
<td>
<p>设置为true，则所有通过此传输发送的命令都被记录</p>
<p>通过Log4J查看这些日志的方式：</p>
<p>log4j.logger.org.apache.activemq.transport.TransportLogger=DEBUG</p>
</td>
</tr>
<tr>
<td>trafficClass</td>
<td>0</td>
<td>在套接字上设置的Traffic类</td>
</tr>
<tr>
<td>typeOfService</td>
<td>0</td>
<td>仅客户端。偏好的服务类型（Type of Service），在出站数据包上设置</td>
</tr>
<tr>
<td>useInactivityMonitor</td>
<td>true</td>
<td>设置为false 则禁用InactivityMonitor，连接永远不会超时</td>
</tr>
<tr>
<td>useKeepAlive</td>
<td>true</td>
<td>设置为true则在空闲连接上发布KeepAliveInfo消息，防止其超时</td>
</tr>
<tr>
<td>useLocalHost</td>
<td>false</td>
<td>
<p>设置为true则使用localhost而非实际的本地主机名来连接到本机。Mac OS X不支持使用本地主机名连接到本机，因此只能使用localhost</p>
</td>
</tr>
<tr>
<td>useQueueForAccept</td>
<td>true</td>
<td>如果设置为true，则被接受的套接字排队，由额外线程异步的处理</td>
</tr>
<tr>
<td>wireFormat</td>
<td>default</td>
<td>使用的WireFormat工厂的名称</td>
</tr>
<tr>
<td>wireFormat.*</td>
<td> </td>
<td>这些属性用来配置WireFormat</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">NIO传输</span></div>
<p>此传输和普通的TCP传输很类似，区别仅仅是此传输基于Java NIO API实现，提高了性能和可扩容性。</p>
<p>NIO是仅服务器端的传输配置，如果在客户端使用此传输，会初始化一个常规的TCP传输。注意，最初的NIO传输是TCP + Openwire的替代，其它线路协议 —— AMQP, MQTT, Stomp ——均有自己的NIO实现，配置时协议前缀是xxx+nio，例如：</p>
<pre class="crayon-plain-tag"># MQTT + NIO
mqtt+nio://localhost:1883
# Openwire + NIO
nio://hostname:port?key=value</pre>
<p>配置选项和TCP传输相同。此外，你可以设置一些JVM系统属性，来微调NIO传输的线程使用：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">JVM属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>org.apache.activemq.transport.nio.SelectorManager.corePoolSize</td>
<td>默认10。连接池中维持的最小线程数量</td>
</tr>
<tr>
<td>org.apache.activemq.transport.nio.SelectorManager.maximumPoolSize</td>
<td>默认1024。连接池中允许的最大线程数量</td>
</tr>
<tr>
<td>org.apache.activemq.transport.nio.SelectorManager.workQueueCapacity</td>
<td>增加线程池大小时，工作队列积压的至少深度</td>
</tr>
<tr>
<td>org.apache.activemq.transport.nio.SelectorManager.rejectWork</td>
<td>默认false。为了保证既有的QoS，允许拒绝工作</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">SSL传输</span></div>
<p>允许客户端使用SSL over TCP连接到代理。从5.4开始，任何SSLServerSocket类的选项可以通过?transport.xxx来设置：</p>
<pre class="crayon-plain-tag">ssl://localhost:61616?transport.enabledCipherSuites=SSL_RSA_WITH_RC4_128_SHA,SSL_DH_anon_WITH_3DES_EDE_CBC_SHA
ssl://localhost:61616?transport.needClientAuth=true</pre>
<p>基于Spring的JMS客户端配置示例：</p>
<pre class="crayon-plain-tag">&lt;bean id="AMQJMSConnectionFactory" class="org.apache.activemq.ActiveMQSslConnectionFactory"&gt;
    &lt;property name="trustStore" value="/path/to/truststore.ts" /&gt;
    &lt;property name="trustStorePassword" value="password" /&gt;
    &lt;property name="keyStore" value="/path/to/keystore.ks" /&gt;
    &lt;property name="keyStorePassword" value="password" /&gt;
    &lt;property name="brokerURL" value="ssl://localhost:61616" /&gt;
    &lt;property name="userName" value="admin" /&gt; 
    &lt;property name="password" value="admin" /&gt;
&lt;/bean&gt;</pre>
<p>除非<pre class="crayon-plain-tag">transport.needClientAuth=true</pre>  ，否则只需要单向身份认证，客户端不需要配置keystore，而仅需要truststore来验证服务器身份。</p>
<div class="blog_h3"><span class="graybg">NIO SSL传输</span></div>
<p>示例：</p>
<pre class="crayon-plain-tag">&lt;transportConnectors&gt;
    &lt;transportConnector name="nio+ssl" uri="nio+ssl://0.0.0.0:61616"/&gt;  
&lt;/&lt;transportConnectors&gt;</pre>
<p>在客户端使用该传输，会得到一个普通的SSL传输。 </p>
<div class="blog_h3"><span class="graybg">HTTP/HTTPS传输</span></div>
<p>此传输的实现位于activemq-optional包中。使用XML载荷穿过HTTP隧道，可以让ActiveMQ客户端和代理穿越某些网络环境下的防火墙。</p>
<p>对于客户端，除了JMS之外，可以考虑ActiveMQ的REST和Ajax支持。 </p>
<div class="blog_h3"><span class="graybg">WebSocket传输</span></div>
<p>从5.4开始，ActiveMQ支持基于HTML5 WebSocket，在浏览器端直接和代理进行交互。</p>
<p>由于JavaScript处理JSON格式很简单，因此Stomp是WebSocket传输下很好的线路协议。从5.9开始，高效的二进制协议MQTT也被支持。</p>
<p>配置示例：</p>
<pre class="crayon-plain-tag">&lt;transportConnectors&gt;
    &lt;transportConnector name="websocket" uri="ws://0.0.0.0:61614"/&gt;
&lt;/transportConnectors&gt;
&lt;!-- 启用安全WebSocket，5.7开始支持 --&gt;
&lt;transportConnectors&gt;
    &lt;transportConnector name="secure_websocket" uri="wss://0.0.0.0:61614"/&gt;
&lt;/transportConnectors&gt;

&lt;!-- 为代理配置SSL上下文 --&gt;
&lt;sslContext keyStore="file:${activemq.conf}/broker.ks"
    keyStorePassword="password" trustStore="file:${activemq.conf}/broker.ts"
    trustStorePassword="password"
/&gt;</pre>
<div class="blog_h2"><span class="graybg">高级传输配置</span></div>
<p>ActiveMQ提供了一种高级别的传输，这些传输总是在上面那些基础传输的更上一层工作。</p>
<div class="blog_h3"><span class="graybg">Failover传输</span></div>
<p>该传输是一种逻辑传输，在其他传输的上层工作，在ActiveMQ3中，称Reliable传输。 它的价值是保障高可用性，维护一组URI的列表，从中随机(默认)寻找一个进行连接 尝试，如果失败，则会尝试其他的URI。该传输的URI格式：</p>
<pre class="crayon-plain-tag">failover:(uri1,...,uriN)?transportOptions&amp;nestedURIOptions
# 或者
failover:uri1,...,uriN

# 示例
failover:(tcp://localhost:61616,tcp://remotehost:61616)?initialReconnectDelay=100</pre>
<p>该传输的选项包括：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">选项</td>
<td style="width: 15%; text-align: center;">默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>backup</td>
<td>false</td>
<td>是否初始化并持有针对备选传输的连接，这样故障转移更快</td>
</tr>
<tr>
<td>initialReconnectDelay</td>
<td>10</td>
<td>第一次重连尝试之前等待的毫秒数</td>
</tr>
<tr>
<td>maxCacheSize</td>
<td>131072</td>
<td>
<p>被跟踪消息的缓存的大小，字节</p>
<p>仅仅trackMessages设置为true时才有意义</p>
</td>
</tr>
<tr>
<td>maxReconnectAttempts</td>
<td> </td>
<td>
<p>从5.6开始，默认值-1表示永远尝试，0表示禁止重连</p>
<p>5.6之前，默认0，表示永远尝试</p>
<p>大于0的值表示最大重连尝试次数，超过此次数后，错误被发送给客户端代码</p>
</td>
</tr>
<tr>
<td>maxReconnectDelay</td>
<td>30000</td>
<td>第二次以及后续重连尝试，最大的延迟时间</td>
</tr>
<tr>
<td>nested.*</td>
<td> </td>
<td>从5.9开始，应用到下层URI列表中每一项的配置</td>
</tr>
<tr>
<td>randomize</td>
<td>true</td>
<td>随机的选择一个URI进行重连</td>
</tr>
<tr>
<td>reconnectDelayExponent</td>
<td>2.0</td>
<td>重连时延迟会逐步增加，这个指数说明增加的幅度</td>
</tr>
<tr>
<td>reconnectSupported</td>
<td>true</td>
<td>客户端是否应该向代理报告ConnectionControl事件，和rebalanceClusterClients有关</td>
</tr>
<tr>
<td>startupMaxReconnectAttempts</td>
<td>-1</td>
<td>
<p>-1表示启动时重连的次数不限制</p>
<p>大于0表示启动时最大重连次数，超过此次数后向客户端代码报告错误</p>
</td>
</tr>
<tr>
<td>timeout</td>
<td>-1</td>
<td>在没有重连操作干扰的情况下，send操作超时时间，ms</td>
</tr>
<tr>
<td>trackMessages</td>
<td>false</td>
<td>保留尚未发送的消息，在重连成功后刷出到代理</td>
</tr>
<tr>
<td>updateURIsSupported</td>
<td>true</td>
<td>5.4+，客户端是否允许代理推送故障转移备选URI列表</td>
</tr>
<tr>
<td>useExponentialBackOff</td>
<td>true</td>
<td>连接重试时，是否使用指数级的延迟增长</td>
</tr>
<tr>
<td>warnAfterReconnectAttempts</td>
<td>10</td>
<td>在多少次重连尝试后，记录警告</td>
</tr>
</tbody>
</table>
<p>故障转移的服务器端配置：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">选项</td>
<td style="width: 15%; text-align: center;">默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>updateClusterClients</td>
<td>false</td>
<td>如果true，推送变更后的代理集群信息给客户端</td>
</tr>
<tr>
<td>rebalanceClusterClients</td>
<td>false</td>
<td>
<p>如果true，新的代理连接到集群中后，发送信息给客户端，要求再平衡 —— 均衡分布客户端</p>
<p>priorityBackup=true 覆盖此选项</p>
</td>
</tr>
<tr>
<td>updateClusterClientsOnRemove</td>
<td>false</td>
<td>如果true，当代理离开集群后，更新客户端</td>
</tr>
<tr>
<td>updateClusterFilter</td>
<td>null</td>
<td>逗号分隔的正则式，匹配的代理名称将作为故障转移的成员</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Fanout传输</span></div>
<p>在任何其它传输之上工作，应用重连、复制逻辑。它使用发现传输来检测代理，并复制命令到这些代理。该传输的URI格式：</p>
<pre class="crayon-plain-tag">fanout:(discoveryURI)?transportOptions
# 或
fanout:discoveryURI</pre>
<p> 该传输的选项包括：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">选项</td>
<td style="width: 15%; text-align: center;">默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>initialReconnectDelay</td>
<td>10</td>
<td>第一次重连（首次连接失败后）尝试前，等待的毫秒数</td>
</tr>
<tr>
<td>maxReconnectDelay</td>
<td>30000</td>
<td>两次重连最大的间隔</td>
</tr>
<tr>
<td>useExponentialBackOff</td>
<td>true</td>
<td>连接重试时，是否使用指数级的延迟增长</td>
</tr>
<tr>
<td>backOffMultiplier</td>
<td>2</td>
<td>延迟增长的倍数</td>
</tr>
<tr>
<td>maxReconnectAttempts</td>
<td>0</td>
<td>最大重连次数，超过此次数后错误信息发送给客户端</td>
</tr>
<tr>
<td>fanOutQueues</td>
<td>false</td>
<td>
<p>如果设置为true，则命令被复制到主题的同，也复制到队列</p>
<p>默认的该传输仅仅复制命令到主题，因此，假设你想发送命令到多个代理的多个队列上，设置为true</p>
</td>
</tr>
<tr>
<td>minAckCount</td>
<td>2</td>
<td>最少需要连接上的代理数量</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">发现类传输</span></div>
<div class="blog_h3"><span class="graybg">发现传输</span></div>
<p>此传输的工作方式和Failover传输类似，但是它基于所谓发现代理（<span style="color: #000000;">discovery agent）来定位可以连接的URI的列表。Fanout传输会使用到该传输，以便发现需要复制命令的目标代理。URI格式：</span></p>
<pre class="crayon-plain-tag">discovery:(discoveryAgentURI)?transportOptions
# 或
discovery:discoveryAgentURI</pre>
<p> 想要基于此传输发现代理，<span style="background-color: #c0c0c0;">目标代理必须启用了组播发现代理</span>。服务器配置示例：</p>
<pre class="crayon-plain-tag">&lt;transportConnectors&gt;
    &lt;!-- 端口0表示随机选择TCP传输端口 ，discoveryUri说明发现代理使用的组播地址，默认 --&gt;
    &lt;!-- URI为multicast://default，即multicast://239.255.2.3:6155 --&gt;
    &lt;transportConnector uri="tcp://localhost:0" discoveryUri="multicast://default"/&gt;
&lt;/transportConnectors&gt;</pre>
<p>该传输的选项包括：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">选项</td>
<td style="width: 15%; text-align: center;">默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>reconnectDelay</td>
<td>10</td>
<td>等待发现的延迟</td>
</tr>
<tr>
<td>initialReconnectDelay</td>
<td>10</td>
<td>第一次尝试重连（首次连接失败后）到一个发现的URI前，等待的毫秒数</td>
</tr>
<tr>
<td>maxReconnectDelay</td>
<td>30000</td>
<td>两次重连最大的间隔</td>
</tr>
<tr>
<td>useExponentialBackOff</td>
<td>true</td>
<td>连接重试时，是否使用指数级的延迟增长</td>
</tr>
<tr>
<td>backOffMultiplier</td>
<td>2</td>
<td>延迟增长的倍数</td>
</tr>
<tr>
<td>maxReconnectAttempts</td>
<td>0</td>
<td>最大重连次数，超过此次数后错误信息发送给客户端</td>
</tr>
<tr>
<td>group</td>
<td>default</td>
<td>组播分组标识符</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">零配置传输</span></div>
<p>类似于发现传输，只是它基于ZeroConf协议。URI格式：</p>
<pre class="crayon-plain-tag">zeroconf:serviceName?transportOptions
# 或
zeroconf:serviceName</pre>
<p> 服务器端配置示例：</p>
<pre class="crayon-plain-tag">&lt;transportConnectors&gt;
    &lt;transportConnector name="openwire" uri="tcp://0.0.0.0:61616" 
        discoveryUri="zeroconf:_activemq_development" /&gt;
&lt;/transportConnectors&gt;</pre>
<div class="blog_h2"><span class="graybg">线路协议配置</span></div>
<p>在进行传输配置时，你可以指定wireFormat说明要使用何种线路协议，可以指定wireFormat.*来为线路协议指定配置。</p>
<div class="blog_h3"><span class="graybg">OpenWire</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">选项</td>
<td style="width: 15%; text-align: center;">默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>stackTraceEnabled</td>
<td>true</td>
<td>代理发生的异常堆栈是否发送到客户端</td>
</tr>
<tr>
<td>tcpNoDelayEnabled</td>
<td>true</td>
<td>对数据格式没有影响，提示Soecket启用TCP_NO_DELAY</td>
</tr>
<tr>
<td>cacheEnabled</td>
<td>true</td>
<td>是否进行缓存，避免重复内容的数据转换开销</td>
</tr>
<tr>
<td>tightEncodingEnabled</td>
<td>true</td>
<td>是否使用紧凑的格式，压缩大小</td>
</tr>
<tr>
<td>prefixPacketSize</td>
<td>true</td>
<td>数据包的前缀包含其大小</td>
</tr>
<tr>
<td>maxInactivityDuration</td>
<td>30000</td>
<td>认为Socket已经断开的超时时间</td>
</tr>
<tr>
<td>maxInactivityDuration InitalDelay</td>
<td>10000</td>
<td>认为Socket已经断开的超时，初始判断的延迟</td>
</tr>
<tr>
<td>cacheSize</td>
<td>1024</td>
<td>最大缓存数量</td>
</tr>
<tr>
<td>maxFrameSize</td>
<td>MAX_LONG</td>
<td>最大可以发送的帧大小</td>
</tr>
</tbody>
</table>
<p> Spring客户端配置示例：</p>
<pre class="crayon-plain-tag">&lt;bean class="org.apache.activemq.ActiveMQConnectionFactory"&gt;
    &lt;property name="brokerURL" 
        value="failover:(tcp://localhost:61616?jms.optimizeAcknowledge=false&amp;wireFormat.maxInactivityDuration=30000)"/&gt;
&lt;/bean&gt;</pre>
<div class="blog_h2"><span class="graybg">代理网络</span></div>
<p>为提供无限制可扩展性(scalability )，往往需要把若干ActiveMQ代理连接到一 起，进而让分布在各地的客户端逻辑的连接在一起进行消息交互。代理网络提供了 <span style="background-color: #c0c0c0;">分布式队列与主题(distributed queues and topics)</span>的支持。</p>
<p>当使用客户端/服务器模型或者星状模型时，代理可能会引起单点故障。启用代理网络后此问题不再存在。</p>
<p>客户端可以连接到代理网络中的任意一个代理，并在此代理出现故障时，转移到其它代理。</p>
<p>默认情况下，代理网络中节点之间的连接是单向的 —— 建立连接通道的代理可以向目标代理发送消息，反过来则不行。从5.x开始，通道可以配置为双向的，这样对于星状结构中位于防火墙背后的Hub可以受益。</p>
<div class="blog_h3"><span class="graybg">配置示例</span></div>
<p>配置代理网络最简单的方式是基于Spring的XML配置。支持两种发现代理的机制：</p>
<ol>
<li>硬编码参与网络的其它代理的URI</li>
<li>使用发现传输</li>
</ol>
<p>硬编码的示例：</p>
<pre class="crayon-plain-tag">&lt;broker brokerName="receiver" persistent="false" useJmx="false"&gt;
    &lt;networkConnectors&gt;
        &lt;!-- 这里指向其它代理通过传输连接器暴露的URI  --&gt;
        &lt;networkConnector uri="static:(tcp://localhost:62001,tcp://host2:62000)"/&gt;
    &lt;/networkConnectors&gt;
    &lt;transportConnectors&gt;
        &lt;!-- 本代理暴露的URI --&gt;
        &lt;transportConnector uri="tcp://localhost:62002"/&gt;
    &lt;/transportConnectors&gt;
&lt;/broker&gt;</pre>
<p>基于组播发现的示例：</p>
<pre class="crayon-plain-tag">&lt;broker name="sender" persistent="false" useJmx="false"&gt;
    &lt;networkConnectors&gt;
        &lt;!-- 使用组播发现其它代理  --&gt;
        &lt;networkConnector uri="multicast://default"/&gt;
    &lt;/networkConnectors&gt;
    &lt;transportConnectors&gt;
        &lt;!-- 所有参与网络的代理都需要这样配置 --&gt;
        &lt;transportConnector uri="tcp://localhost:0" discoveryUri="multicast://default"/&gt;
    &lt;/transportConnectors&gt;
&lt;/broker&gt;</pre>
<div class="blog_h3"><span class="graybg">启动网络连接器</span></div>
<p>默认情况下，网络连接器（ network connector）作为代理启动流程的一部分、串行的初始化。如果某些网络很缓慢，会导致整个集群中代理启动都缓慢。从5.5开始你可以设置代理属性<pre class="crayon-plain-tag">networkConnectorStartAsync="true"</pre>  这会导致代理使用异步线程来启动网络连接器。</p>
<div class="blog_h3"><span class="graybg">网络连接器属性</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="width: 15%; text-align: center;">默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>name</td>
<td>bridge</td>
<td>网络的名称，如果同一对代理之间有多重网络，必须制定不一样的名称</td>
</tr>
<tr>
<td>dynamicOnly</td>
<td>false</td>
<td>如果设置为true，则仅仅本地持久化订阅者重新激活后，才激活相应的网络化的持久订阅</td>
</tr>
<tr>
<td>decreaseNetworkConsumerPriority</td>
<td>false</td>
<td>
<p>如果设置为true，从-5开始，离消息生产者越远（跨越代理的数量）的消费者，其优先级越低</p>
<p>默认的，所有消费者具有优先级0，和本地消费者一样优先</p>
</td>
</tr>
<tr>
<td>networkTTL</td>
<td>1</td>
<td>消息、订阅能够穿透的代理数量</td>
</tr>
<tr>
<td>messageTTL</td>
<td>1</td>
<td>从生产者角度来看，消息能够穿透的代理数量</td>
</tr>
<tr>
<td>consumerTTL</td>
<td>1</td>
<td>从消费者角度来看，订阅（主题或者队列）能够穿透的代理数量</td>
</tr>
<tr>
<td>conduitSubscriptions</td>
<td>true</td>
<td>订阅同一目标的多个消费者，是否被网络当做单一消费者看待</td>
</tr>
<tr>
<td>excludedDestinations</td>
<td> </td>
<td>匹配此列表的那些目标，不会通过代理网络转发（仅应用到dynamicallyIncludedDestinations）</td>
</tr>
<tr>
<td>dynamicallyIncludedDestinations</td>
<td> </td>
<td>
<p>匹配此列表的那些目标，会通过代理网络转发</p>
<p>注意：如果此列表为空，则不在excludedDestinations中的所有目标都被转发</p>
</td>
</tr>
<tr>
<td>useVirtualDestSubs</td>
<td>false</td>
<td>如果为true，则网络连接会监听通知消息（advisory messages）以获得虚拟目标消费者</td>
</tr>
<tr>
<td>staticallyIncludedDestinations</td>
<td> </td>
<td>匹配此列表的目标总是跨越代理网络被转发，甚至没有消费者关心此目标</td>
</tr>
<tr>
<td>duplex</td>
<td>false</td>
<td>
<p>设置为true，则网络连接通道是双向的。当前代理可以基于此通道进行生产、消费，而不仅仅是生产</p>
<p>尝试通过单向通道接收消息，会收到如下错误：</p>
<p>Could not start network bridge between: vm://BrokerB?async=false&amp;network=true and: tcp://192.168.0.89:61616 due to: Connection refused (Connection refused)</p>
</td>
</tr>
<tr>
<td>prefetchSize</td>
<td>1000</td>
<td>在网络连接器的消费者上设置预读取大小。必须大于0，因为网络消费者不执行轮询操作</td>
</tr>
<tr>
<td>suppressDuplicateQueueSubscriptions</td>
<td>false</td>
<td>
<p>如果为true，则代理网络中中继过来的重复订阅被忽略</p>
<p>例如通过组播连接到一起的代理A、B、C。A上的本地消费者会传递并映射为B、C的网络消费者。进一步C上的网络消费者（源自A）会传递并映射到B、B也传递并映射到C。当此选项为true时，B和C之间的网络桥会并抑制 </p>
<p>通过此设置来减少可选的路由路径，可以为跨越网络迁移的生产者/消费者提供一种确定性 —— 潜在的死路由被消除了</p>
<p>触发此抑制行为，需要networkTTL到达或者超过代理数量</p>
</td>
</tr>
<tr>
<td>bridgeTempDestinations</td>
<td>true</td>
<td>
<p>是否在代理网络中通过通知消息来广播新创建的临时目标</p>
<p>临时目标通常用于执行请求/应答模式，该选项默认true，这样可以让执行请求应答模式通信的消费者位于代理网络中的其它节点上，并可以设置JMSReplyTo然后把应答消息返回给最初的生产者</p>
<p>如果应用程序中大量使用请求/应答模型，该选项为true会导致在代理网络中产生额外的流量。原因是，通常JMSReplyTo总是填写一个唯一地址，则就意味着总是会创建新的临时目标，进而广播到整个代理网络</p>
<p>如果设置为false，则基于请求应答模型通信的生产者/消费者必须连接到同一个代理上。否则你会收到temp destination does not exist错误</p>
</td>
</tr>
<tr>
<td>alwaysSyncSend</td>
<td>false</td>
<td>设置为true时，非持久化消息基于请求/应答的方式，而不是单向的方式发送给远程代理 —— 和持久化消息的处理方式一样</td>
</tr>
<tr>
<td>staticBridge</td>
<td>false</td>
<td>如果设置为true，代理不会因为新的消费者的出现而做出响应，它只会使用staticallyIncludedDestinations来创建demand subscriptions</td>
</tr>
<tr>
<td>userName</td>
<td>null</td>
<td>针对远程代理进行身份验证时的用户名</td>
</tr>
<tr>
<td>password</td>
<td>null</td>
<td>针对远程代理进行身份验证时的密码</td>
</tr>
</tbody>
</table>
<p>使用硬编码URI时，你可以设置initialReconnectDelay、maxReconnectDelay、useExponentialBackOff、backOffMultiplier这些选项，来控制重连操作：</p>
<pre class="crayon-plain-tag">uri="static:(tcp://host1:61616,tcp://host2:61616)?maxReconnectDelay=5000&amp;useExponentialBackOff=false"</pre>
<div class="blog_h3"><span class="graybg">MasterSlave网络</span></div>
<p>代理网络的一个常见用途是，创建一个Master和多个Slave之间的连接：</p>
<pre class="crayon-plain-tag">&lt;networkConnectors&gt;
    &lt;networkConnector uri="masterslave:(tcp://host1:61616,tcp://host2:61616,tcp://..)"/&gt;
&lt;/networkConnectors&gt;</pre>
<p>这个通常和failover传输一起使用。 </p>
<div class="blog_h3"><span class="graybg">可靠性</span></div>
<p>代理网络执行可靠的消息存储、转发。如果源（消息）是持久的 —— 队列上的持久消息、持久化主题订阅 —— 则代理网络仍然保证其持久性。</p>
<p>但是，如果源是非持久性的，则代理网络不会为其增加持久性特征。当非持久化源 —— 非持久化主题订阅、临时目标 —— 位于代理网络中时，出现故障时，正在传递中的消息可能丢失。</p>
<div class="blog_h3"><span class="graybg">消息顺序</span></div>
<p>在代理网络中，整体的消息顺序（Total ordering）不被保留。代理网络引入了额外的消费者 —— 网络桥消费者通过producer.send(..)实现消息转发，这样，消息从其本地的队列头中取出，又放到目标代理对应队列尾部。</p>
<div class="blog_h3"><span class="graybg">Conduit subscriptions</span></div>
<p>ActiveMQ依赖于活动消费者（订阅者，subscriptions）的信息，来决定是否在代理网络上传递消息。代理把来自远程代理的订阅、和本地的订阅同等看待，并且路由相关消息的拷贝到所有消费者。</p>
<p>对于主题订阅，远程代理B会把所有消息拷贝看做是合法的，因此它把这些（来自代理A的）消息路由给它的N个本地订阅（消费者）时，就会发生消息重复  —— 每个拷贝都被转发给每个本地订阅者。</p>
<p>ActiveMQ的默认行为是，把远程代理B上的N个匹配的订阅看成是单个订阅，避免重复消息的发生。</p>
<div class="blog_h3"><span class="graybg">双向连接</span></div>
<p>设置duplex=true后，连接双方使用同一连接进行通信，构成双向网桥。此双向配置会自动传递到对方，因此对方不需要任何配置。</p>
<div class="blog_h3"><span class="graybg">代理网络与通知</span></div>
<p>代理网络的工作机制依赖于通知消息(advisory messages)——代理使用这些消息来 感知远程代理上的新消费者。在代理启动时，它会创建<pre class="crayon-plain-tag">ActiveMQ.Advisory.Consumer.&gt;</pre> 的消费者，这样，当远程代理连接/断开了Consumer时，本地代理会获得通知。</p>
<p>在小型网络、少量目标、消费者的情况下没有问题，反之就会导致大量网络开销，所有，在代理网络的配置中，包含若干种过滤代理共享目标的方法：</p>
<ol>
<li>dynamicallyIncludedDestinations：这意味着只有远程代理上有匹配目标的消费者时，才进行转发：<br />
<pre class="crayon-plain-tag">&lt;networkConnector uri="static:(tcp://host)"&gt;
    &lt;dynamicallyIncludedDestinations&gt;
        &lt;!-- 仅当远程代理上存在如下目标的消费者时，才转发这些目标上的消息 --&gt;
        &lt;queue physicalName="include.test.foo"/&gt;
        &lt;topic physicalName="include.test.bar"/&gt;
    &lt;/dynamicallyIncludedDestinations&gt;
&lt;/networkConnector&gt;</pre>
</li>
<li>staticallyIncludedDestinations：这意味着不管远程代理上有没有匹配目标的消费者，消息均被转发：<br />
<pre class="crayon-plain-tag">&lt;networkConnector uri="static:(tcp://host)" staticBridge="true"&gt;
     &lt;staticallyIncludedDestinations&gt;
          &lt;queue physicalName="always.include.queue"/&gt;
     &lt;/staticallyIncludedDestinations&gt;
&lt;/networkConnector&gt;</pre>
</li>
</ol>
<div class="blog_h2"><span class="graybg">目标策略配置</span></div>
<p>可以通过队列/主题名称或者通配符应用一系列的策略，在destinationPolicy 元素下指定。示例：</p>
<pre class="crayon-plain-tag">&lt;destinationPolicy&gt;
    &lt;policyMap&gt;
        &lt;policyEntries&gt;
            &lt;policyEntry queue="&gt;" producerFlowControl="true" memoryLimit="20mb"&gt;
                &lt;deadLetterStrategy&gt;
                    &lt;individualDeadLetterStrategy queuePrefix="DLQ." useQueueForQueueMessages="true"/&gt;
                &lt;/deadLetterStrategy&gt;
            &lt;/policyEntry&gt;
            &lt;policyEntry topic="&gt;" producerFlowControl="true" memoryLimit="20mb"&gt;
            &lt;/policyEntry&gt;
        &lt;/policyEntries&gt;
    &lt;/policyMap&gt;
&lt;/destinationPolicy&gt;</pre>
<p>支持的策略包括：</p>
<p>expireMessagesPeriodÈ</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="width: 15%; text-align: center;">默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>producerFlowControl</td>
<td>true</td>
<td>如果代理没有足够的资源(例如内存)，那么生产者将减速并最终被 阻塞</td>
</tr>
<tr>
<td>enableAudit</td>
<td>true</td>
<td>跟踪重复消息(重复消息可能出现在非持久化消息的failover)</td>
</tr>
<tr>
<td>useCache</td>
<td>true</td>
<td>持久化消息可以被缓存，以便后续的快速存取</td>
</tr>
<tr>
<td>maxPageSize</td>
<td>200</td>
<td>最大单次可以从持久化存储获取消息的页数</td>
</tr>
<tr>
<td>maxBrowsePageSize</td>
<td>400</td>
<td>在浏览时，最大单次可以从持久化存储获取消息的页数</td>
</tr>
<tr>
<td>memoryLimit</td>
<td> </td>
<td>指定目标的内存限制</td>
</tr>
<tr>
<td>minimumMessageSize</td>
<td>1024</td>
<td>用于嵌入式代理:假设的消息最小内存使用量</td>
</tr>
<tr>
<td>cursorMemoryHighWaterMark</td>
<td>70%</td>
<td>触发写入磁盘的内存用量高水位</td>
</tr>
<tr>
<td>prioritizedMessages</td>
<td>false</td>
<td>让存储根据消息优先级进行安排</td>
</tr>
<tr>
<td>advisoryForConsumed</td>
<td>false</td>
<td>当消息被消费，发送一个通知消息</td>
</tr>
<tr>
<td>advisoryForDelivery</td>
<td>false</td>
<td>当消息被发送到客户端，发送一个通知消息</td>
</tr>
<tr>
<td>advisoryForSlowConsumers</td>
<td>false</td>
<td>当某个消费者运行缓慢时，发送一个通知消息</td>
</tr>
<tr>
<td>advsioryForFastProducers</td>
<td>false</td>
<td>当某个消费者运行迅速时，发送一个通知消息</td>
</tr>
<tr>
<td>advisoryWhenFull</td>
<td>false</td>
<td>在资源耗尽(内存、存储、临时存储)耗尽时，发送一个通知消息</td>
</tr>
<tr>
<td>gcInactiveDestinations</td>
<td>false</td>
<td>删除不活动的目标</td>
</tr>
<tr>
<td>inactiveTimoutBeforeGC</td>
<td>5000</td>
<td>目标在多长时间ms内不活动，被认为是可删除的</td>
</tr>
<tr>
<td>usePrefetchExtension</td>
<td>true</td>
<td>预读取扩展在消息被递送，但是没有确认时使用</td>
</tr>
<tr>
<td>slowConsumerStrategy</td>
<td>null</td>
<td>处理缓慢消费者的策略</td>
</tr>
<tr>
<td colspan="3"><em>用于队列目标的额外策略</em></td>
</tr>
<tr>
<td>useConsumerPriority</td>
<td>true</td>
<td>分发消息时，根据消费者优先级计算分发目标</td>
</tr>
<tr>
<td>strictOrderDispatch</td>
<td>false</td>
<td>如果启用，队列将一直供单个消费者使用，直到预读取缓冲满了</td>
</tr>
<tr>
<td>optimizedDispatch</td>
<td>false</td>
<td>不使用单独的线程来发送队列中的消息</td>
</tr>
<tr>
<td>lazyDispatch</td>
<td>false</td>
<td> </td>
</tr>
<tr>
<td>consumersBeforeDispatchStarts</td>
<td>0</td>
<td>第一个消费者连接后，等待N个消费者连接，才进行消息分发</td>
</tr>
<tr>
<td>timeBeforeDispatchStarts</td>
<td>0</td>
<td>当第一个消费者连接，等待消息分发的延迟</td>
</tr>
<tr>
<td>queuePrefetch</td>
<td> </td>
<td>预读取的默认数量</td>
</tr>
<tr>
<td>expireMessagesPeriod</td>
<td>30000</td>
<td>检查消息过期的周期，0表示不检查，单位ms</td>
</tr>
<tr>
<td>persistJMSRedelivered</td>
<td>false</td>
<td>如果为真，在持久消息第一次被分发前，消息被重写以反映可能的传 递，确保JMSRedelivered头是一个可靠的值</td>
</tr>
<tr>
<td colspan="3"><em>用于主题目标的额外策略</em></td>
</tr>
<tr>
<td>topicPrefetch</td>
<td> </td>
<td> 预读取的默认数量</td>
</tr>
<tr>
<td>durableTopicPrefetch</td>
<td> </td>
<td> 持久订阅者预读取的默认数量</td>
</tr>
<tr>
<td>advisoryForDiscardingMess ages</td>
<td> false</td>
<td> 当消息被从非持久订阅中废弃时，发送一个通知消息</td>
</tr>
<tr>
<td>expireMessagesPeriod</td>
<td> 30000</td>
<td> 为不活动的持久化订阅检查消息过期的周期，0表示不检查，单位ms</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">持久化配置</span></div>
<p>persistenceAdapter用于配置ActiveMQ持久化消息的方式，目前支持以下几种存 储机制：</p>
<ol>
<li>AMQ消息存储:默认的消息存储。消息被存储到数据日志中，日志文件的默认 大小为32mb，如果单个消息可以超过此大小，则需要调整。如果日志文件中 的所有消息被成功消费，ActiveMQ会做标记，以便清理和归档。示例：<br />
<pre class="crayon-plain-tag">&lt;journalPersistenceAdapter directory="${activemq.base}/data" maxFileLength="32mb" /&gt;</pre>
</li>
<li>KahaDB消息存储:提供了容量的提升和恢复能力(5.3以上采用)。示例：<br />
<pre class="crayon-plain-tag">&lt;kahaDB directory="${activemq.data}/kahadb" /&gt;</pre>
</li>
<li>JDBC消息存储:消息使用JDBC接口，存放于数据库中，指定数据源即可。示例：<br />
<pre class="crayon-plain-tag">&lt;jdbcPersistenceAdapter dataSource="#mysql-ds"/&gt; </pre>
</li>
<li>Memory消息存储:基于内存的消息存储。指定broker的属性 <pre class="crayon-plain-tag">persistent="false"</pre>即可</li>
</ol>
<div class="blog_h2"><span class="graybg">安全性配置</span></div>
<p>ActiveMQ 4.x以上的版本，提供了可拔插的安全机制，主要包括：</p>
<ol>
<li>JAAS——Java认证与授权服务</li>
<li>一个内置的基于XML配置的简单认证(simpleAuthentication插件)</li>
</ol>
<p>simpleAuthentication的例子(注意启用了匿名访问)：</p>
<pre class="crayon-plain-tag">&lt;simpleAuthenticationPlugin anonymousAccessAllowed="true"&gt;
    &lt;users&gt;
        &lt;authenticationUser username="system" password="manager" groups="users,admins"/&gt;
        &lt;authenticationUser username="user" password="password" groups="users"/&gt;
        &lt;authenticationUser username="guest" password="password" groups="guests"/&gt;
    &lt;/users&gt;
&lt;/simpleAuthenticationPlugin&gt;</pre>
<div class="blog_h3"><span class="graybg">代理网络的身份验证</span></div>
<p>如果代理网络的一方启用了身份验证，那么，连接到它时，必须提供身份验证信 息：</p>
<pre class="crayon-plain-tag">&lt;networkConnectors&gt;
    &lt;networkConnector name="brokerAbridge" userName="user" password="password"
                      uri="static://(tcp://brokerA:61616)"/&gt;
&lt;/networkConnectors&gt;</pre>
<div class="blog_h3"><span class="graybg">用户授权</span></div>
<p>支持对队列、主题的3种访问权限:读(Read)、写(Write)、管理(Admin)。 其中管理权限用于动态延迟创建队列。授权配置的例子如下：</p>
<pre class="crayon-plain-tag">&lt;plugins&gt;
    &lt;authorizationPlugin&gt;
        &lt;map&gt;
            &lt;authorizationMap&gt;
                &lt;authorizationEntries&gt;
                    &lt;authorizationEntry queue="&gt;" read="admins" write="admins" admin="admins"/&gt;
                    &lt;authorizationEntry queue="USERS.&gt;" read="users" write="users" admin="users"/&gt;
                    &lt;authorizationEntry queue="GUEST.&gt;" read="guests" write="guests,users" admin="guests,users"/&gt;
                    &lt;authorizationEntry topic="&gt;" read="admins" write="admins" admin="admins"/&gt;
                    &lt;authorizationEntry topic="USERS.&gt;" read="users" write="users" admin="users"/&gt;
                    &lt;authorizationEntry topic="GUEST.&gt;" read="guests" write="guests,users" admin="guests,users"/&gt;
                    &lt;authorizationEntry topic="ActiveMQ.Advisory.&gt;" read="guests,users" write="guests,users"
                                        admin="guests,users"/&gt;
                &lt;/authorizationEntries&gt;
                &lt;tempDestinationAuthorizationEntry&gt;
                    &lt;tempDestinationAuthorizationEntry read="tempDestinationAdmins"
                                                       write="tempDestinationAdmins" admin="tempDestinationAdmins"/&gt;
                &lt;/tempDestinationAuthorizationEntry&gt;
            &lt;/authorizationMap&gt;
        &lt;/map&gt;
    &lt;/authorizationPlugin&gt;
&lt;/plugins&gt;</pre>
<div class="blog_h2"><span class="graybg">资源配额</span></div>
<p> 在某些情况下，特别是嵌入式代理，可以限制ActiveMQ使用的内存、硬盘资源的数量：</p>
<pre class="crayon-plain-tag">&lt;systemUsage&gt;
    &lt;systemUsage&gt;
        &lt;!-- 内存用量，用于跟踪目的地、进行消息缓存 --&gt;
        &lt;memoryUsage&gt;
            &lt;memoryUsage limit="20 mb"/&gt;
        &lt;/memoryUsage&gt;
        &lt;!-- 下面是磁盘中用于持久化消息的用量 -&gt;
        &lt;storeUsage&gt;
            &lt;storeUsage limit="1 gb" name="foo"/&gt;
        &lt;/storeUsage&gt;
        &lt;!-- 下面是磁盘中用于非持久化消息的用量 --&gt;
        &lt;tempUsage&gt;
            &lt;tempUsage limit="100 mb"/&gt;
        &lt;/tempUsage&gt;
    &lt;/systemUsage&gt;
&lt;/systemUsage&gt;</pre>
<div class="blog_h3"><span class="graybg">基于代码的配置</span></div>
<pre class="crayon-plain-tag">SystemUsage usage = new SystemUsage();
Runtime runtime = Runtime.getRuntime();
MemoryUsage mu = new MemoryUsage();
mu.setPercentOfJvmHeap( 20 );
usage.setMemoryUsage( mu );

StoreUsage su = new StoreUsage();
su.setStore( pa );
su.setLimit( DISK_USAGE_LIMIT_DEFAULT );
usage.setStoreUsage( su );

TempUsage tu = new TempUsage();
su.setStore( pa );
su.setLimit( DISK_USAGE_LIMIT_DEFAULT );
usage.setTempUsage( tu );

broker.setSystemUsage( usage );</pre>
<div class="blog_h1"><span class="graybg">AMQ客户端与Spring的集成</span></div>
<div class="blog_h2"><span class="graybg">使用amq:connectionFactory </span></div>
<pre class="crayon-plain-tag">&lt;amq:connectionFactory id="jmsFactory" brokerURL="vm://localhost"/&gt;</pre>
<div class="blog_h2"><span class="graybg">使用ActiveMQ连接池</span></div>
<p>使用PooledConnectionFactory可以提供连接池功能：<span style="background-color: #c0c0c0;">连接、会话、消息生产者的实例</span>可以被缓存，以便和Spring JmsTemplate、MessageListeners以及Camel等框架集成使用。</p>
<p>注意：该连接池<span style="background-color: #c0c0c0;">不会缓存消息消费者</span>，通常消息消费者在启动后即保持活动，直到必要时手工关闭。 另外，Spring自带的CachingConnectionFactory也可以作为连接池使用。</p>
<pre class="crayon-plain-tag">&lt;bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop"&gt;
    &lt;property name="connectionFactory"&gt;
        &lt;bean class="org.apache.activemq.ActiveMQConnectionFactory"&gt;
            &lt;property name="brokerURL"&gt;
                &lt;value&gt;vm://dataSyncBroker-${appCfg.currentMonitorCenterId}&lt;/value&gt;
            &lt;/property&gt;
        &lt;/bean&gt;
    &lt;/property&gt;
&lt;/bean&gt;

&lt;bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"&gt;
    &lt;property name="connectionFactory" ref="jmsFactory" /&gt;
    &lt;!-- 下面的属性必须为true，才能设置Qos相关的消息头，例如deliveryMode, priority, timeToLive --&gt;
    &lt;property name="explicitQosEnabled" value="true" /&gt;
&lt;/bean&gt;</pre>
<div class="blog_h2"><span class="graybg">使用JmsTemplate发送消息</span></div>
<p> 通常使用JmsTemplate来同步的生产、消费消息：</p>
<pre class="crayon-plain-tag">jmsTemplate.send( getDataSyncQueueName( mc ), new MessageCreator() {
    @Override
    public Message createMessage( Session session ) throws JMSException {
        TextMessage message = session.createTextMessage( msg.toJSON() );
        return message;
    }
} );</pre>
<div class="blog_h2"><span class="graybg">使用DMLC监听消息</span></div>
<p>DefaultMessageListenerContainer组件不是单个类，而是一个良好抽象的、用于接收JMS消息的抽象层。其概念类似 于J2EE的消息驱动Bean(Message Driven Beans，MDB)。其包含的特性有：</p>
<ol>
<li>多层次的JMS资源缓存(连接、会话)、消费者缓存</li>
<li>根据负载，动态增减同时处理消息的消费者数量</li>
<li>如果代理不可用，自动重新建立连接</li>
<li>基于Spring TaskExecutor的异步消息监听</li>
<li>在消息接收、监听执行时，支持本地JMS事务，以及外部分布式事务管理器</li>
<li>支持多种消息确认模式</li>
</ol>
<p>配置示例：</p>
<pre class="crayon-plain-tag">&lt;jms:listener-container container-type="default" connection-factory="jmsFactory" acknowledge="auto"&gt;
    &lt;jms:listener destination="DATA.SYNC.1000" ref="msgTransceiver" method="onMessage" /&gt;
&lt;/jms:listener-container&gt;</pre>
<p>容器类型这里选择的是default，还可以选择simple，后者不支持事务处理。msgTransceiver.onMessage的方法为监听到消息后的回调：</p>
<pre class="crayon-plain-tag">public class MsgTransceiverImpl implements MsgTransceiver, MessageListener {

    public void onMessage( Message message ) {
        try {
            TextMessage msg = (TextMessage) message;
            String text = msg.getText();
            LOGGER.debug( "Received Message: \n" + text );
            onMsgReceived( Msg.create( text ) );
        } catch ( JMSException e ) {
            LOGGER.error( e.getMessage(), e );
        }
    }
}</pre>
<p>除了实现JMS标准的MessageListener以外，还可以使用：</p>
<ol>
<li><pre class="crayon-plain-tag">SessionAwareMessageListener</pre>，接口提供对Session的访问，在实现请求/应答模式 时非常有用注意:你需要自行覆盖handleListenerException方法进行异常处理</li>
<li><pre class="crayon-plain-tag">MessageListenerAdapter</pre>，使用此接口，可以避免代码与JMS有关联，可以处理类型 具体化的消息。 </li>
</ol>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/activemq-study-note">ActiveMQ学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/activemq-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ActiveMQ知识集锦</title>
		<link>https://blog.gmem.cc/activemq-faq</link>
		<comments>https://blog.gmem.cc/activemq-faq#comments</comments>
		<pubDate>Tue, 23 Sep 2014 03:31:53 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[ActiveMQ]]></category>
		<category><![CDATA[FAQ]]></category>
		<category><![CDATA[JMS]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=861</guid>
		<description><![CDATA[<p>常见问题 Setting clientID on a used Connection is not allowed 与Spring DMLC集成，进行持久化订阅时，会报此错误，报错的根源是： [crayon-69d26efd6e35f849696512/] 可以看到，状态isConnectionInfoSentToBroker变为true后，就不能再设置ClientID，修改此字段值的，只有： [crayon-69d26efd6e363359328148/] 断点跟踪，发现调用栈片断： [crayon-69d26efd6e365890351363/] 也就是说，业务代码调用JmsTemplate发送JMS消息，导致isConnectionInfoSentToBroker为true且ClientID被设置。进一步分析发现： ConnectionPool只包含一个实际连接ActiveMQConnection，由多个Session共享 使用的ConnectionFactory：PooledConnectionFactory，仅仅持有一个ConnectionPool，也就是仅仅一个ActiveMQConnection AMQ的ClientId是在Collection级别设置的，只能在连接第一次使用前设置一次，这里由业务代码发起的JmsTemplate调用设置 ConnectionPool自动生成的ClientID，具有随机性，无法用于持久化订阅。因为持久化订阅者的识别方式是ClientID <a class="read-more" href="https://blog.gmem.cc/activemq-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/activemq-faq">ActiveMQ知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">常见问题</span></div>
<div class="blog_h3"><span class="graybg">Setting clientID on a used Connection is not allowed</span></div>
<p>与Spring DMLC集成，进行持久化订阅时，会报此错误，报错的根源是：</p>
<pre class="crayon-plain-tag">@Override
public void setClientID(String newClientID) throws JMSException {
    checkClosedOrFailed();
    // false
    if (this.clientIDSet) {
        throw new IllegalStateException("The clientID has already been set");
    }
    // true
    if (this.isConnectionInfoSentToBroker) {
        // 不允许在“已经使用”的连接上执行设置clientID的操作
        throw new IllegalStateException("Setting clientID on a used Connection is not allowed");
    }

    this.info.setClientId(newClientID);
    this.userSpecifiedClientID = true;
    ensureConnectionInfoSent();
}</pre>
<p>可以看到，状态isConnectionInfoSentToBroker变为true后，就不能再设置ClientID，修改此字段值的，只有：</p>
<pre class="crayon-plain-tag">protected void ensureConnectionInfoSent() throws JMSException {
    synchronized(this.ensureConnectionInfoSentMutex) {
        // Can we skip sending the ConnectionInfo packet??
        if (isConnectionInfoSentToBroker || closed.get()) {
            return;
        }
        //TODO shouldn't this check be on userSpecifiedClientID rather than the value of clientID?
        if (info.getClientId() == null || info.getClientId().trim().length() == 0) {
            // 此生成器的结果具有随机性
            info.setClientId(clientIdGenerator.generateId());
        }
        syncSendPacket(info.copy());
        // 这里修改了字段值
        this.isConnectionInfoSentToBroker = true;
        ...
    }
}</pre>
<p>断点跟踪，发现调用栈片断：</p>
<pre class="crayon-plain-tag">// ActiveMQConnection.java
void ensureConnectionInfoSent();
Session createSession(boolean transacted, int acknowledgeMode);
// ConnectionPool.java 这是对单个ActiveMQConnection的封装，允许多个Session共享之
Session makeSession(SessionKey key);
ConnectionPool (Connection connection);
// JmsTemplate.java
send(final Destination destination, final MessageCreator messageCreator);
// 业务代码略</pre>
<p>也就是说，业务代码调用JmsTemplate发送JMS消息，导致isConnectionInfoSentToBroker为true且ClientID被设置。进一步分析发现：</p>
<ol>
<li>ConnectionPool只包含一个实际连接ActiveMQConnection，由多个Session共享</li>
<li>使用的ConnectionFactory：PooledConnectionFactory，仅仅持有一个ConnectionPool，也就是仅仅一个ActiveMQConnection</li>
<li>AMQ的ClientId是在Collection级别设置的，只能在连接第一次使用前设置一次，这里由业务代码发起的JmsTemplate调用设置</li>
<li>ConnectionPool自动生成的ClientID，具有随机性，<span style="background-color: #c0c0c0;">无法用于持久化订阅。因为持久化订阅者的识别方式是ClientID + subscription name</span></li>
</ol>
<p>因此，使用AMQ的情况下，要进行持久化订阅，应当为DMLC提供一个可控的ConnectionFactory，比如单独分配ConnectionFactory。</p>
<div class="blog_h3"><span class="graybg">javax.jms.InvalidClientIDException</span></div>
<p>报错信息示例：</p>
<p>[WARN ] [triggerStartAsyncNetworkBridgeCreation: remoteBroker=tcp://Zircon/127.0.1.1:38592@47262, localBroker= vm://BrokerA#19200] 2017-08-08 12:21:21 org.apache.activemq.broker.TransportConnection.processAddConnection(TransportConnection.java:770)<br />Failed to add Connection BrokerA-&gt;BrokerB-32884-1502096377514-3780:1<br />javax.jms.InvalidClientIDException: Broker: BrokerA - Client: NC_BrokerA_BrokerB_inbound_BrokerA already connected from vm://BrokerA#8</p>
<p>这里的情况是，BrokerA 到BrokerB之间的通道对应了一个客户端，其ID已经被占用。原因可能是BrokerB代理意外终止导致。</p>
<p>解决办法：设置<pre class="crayon-plain-tag">TransportConnector</pre>的属性：<pre class="crayon-plain-tag">tc.setAllowLinkStealing( true )</pre></p>
<div class="blog_h3"><span class="graybg">Consumer无法监听到消息</span></div>
<p>发现设置了异步启动<pre class="crayon-plain-tag">broker.setStartAsync( true )</pre>的代理，在构成代理网络上，容易出现偶发性的网络相关错误，并发生代理宕机重启后，某些Consumer不再收到消息的现象，关闭异步启动后收不到消息的现象消失。</p>
<p>可能相关的报错信息：</p>
<p>javax.jms.JMSException: peer (vm://BrokerB#3) stopped.<br />Caused by: org.apache.activemq.transport.TransportDisposedIOException: peer (vm://BrokerA#1) stopped.</p>
<p>javax.jms.IllegalStateException: The Consumer is closed</p>
<div class="blog_h3"><span class="graybg">无法启动：Caused by: java.io.IOException: Invalid location: 5:3807632:</span></div>
<p>java.lang.NegativeArraySizeException<br /> at org.apache.activemq.store.kahadb.disk.journal.DataFileAccessor.readRecord(DataFileAccessor.java:92)</p>
<p>服务非正常重启导致kahadb日志损坏，可以设置属性解决：</p>
<pre class="crayon-plain-tag">&lt;persistenceAdapter&gt;
    &lt;kahaDB directory="D:/amq/datasync/kahadb" checkForCorruptJournalFiles="true" /&gt;
&lt;/persistenceAdapter&gt;</pre>
<div class="blog_h3"><span class="graybg">ActiveMQ代理实例无法连入网络</span></div>
<p>检查以下项目：</p>
<ol>
<li>如果使用多播自动发现，检查有没有使用正确的网卡，参考：<a href="/activemq-network-connetcor-problem-caused-by-multihomed-host">我的另一篇文章</a></li>
<li>保证代理名称的唯一性</li>
</ol>
<div class="blog_h3"><span class="graybg">Linux（CentOS 6.3）下无法收发任何消息</span></div>
<p>现象：不能接收到任何组播（Multicast）消息</p>
<p>原因：可能是因为生产者操作系统内核支持IPv6，但是网络本身、消费者操作系统内核不支持IPv6。</p>
<p>解决：当操作系统内核支持IPv6时，JRE会默认使用IPv6，要改变此行为，可以设置JVM系统属性：</p>
<pre class="crayon-plain-tag">-Djava.net.preferIPv4Stack=true</pre>
<div class="blog_h3"><span class="graybg">端口0是什么意思</span></div>
<p>如下配置：</p>
<pre class="crayon-plain-tag">&lt;transportConnector uri="tcp://localhost:0" discoveryUri="multicast://default"/&gt;</pre>
<p>uri中的端口指定为0，表示由ActiveMQ选择一个可用的端口。由于使用发现机制，限定端口没有必要。 </p>
<div class="blog_h3"><span class="graybg">命名规则</span></div>
<p>代理、队列、网络连接器、链接器的名称，都可以通过API自由设定。</p>
<p>假设代理名称为BrokerA，当：</p>
<ol>
<li>基于双向自动发现的网络连接器进行连接时，代理BrokerA通过NC_BrokerA连接，代理BrokerB通过NC_BrokerB连接，则BrokerB在BrokerA队列上的远程消费者的ClientID命名为：
<ol>
<li>NC_BrokerA_BrokerB_inbound_BrokerA，这是由BrokerA双向网络连接（反向）产生的远程消费者</li>
<li>NC_BrokerB_BrokerB_inbound_BrokerA，这是由BrokerB双向网络连接（正向）产生的远程消费者</li>
</ol>
</li>
<li>基于单向自动发现的网络连接器进行连接时，代理BrokerA通过NC_BrokerA连接，代理BrokerB通过NC_BrokerB连接，则BrokerB在BrokerA队列上的远程消费者的ClientID命名为：
<ol>
<li>NC_BrokerA_BrokerB_inbound_BrokerA，这是由BrokerA单向网络连接产生的远程消费者</li>
</ol>
</li>
<li>基于双向静态网络连接器进行连接，代理BrokerB通过NC_BrokerB连接，则BrokerB在BrokerA队列上的远程消费者的ClientID命名为：
<ol>
<li>NC_BrokerB_BrokerB_inbound_BrokerA</li>
</ol>
</li>
<li>基于单向静态网络连接器进行连接，代理BrokerB通过NC_BrokerB连接，则BrokerB在BrokerA队列上没有消费者</li>
</ol>
<p>单向网络连接，仅能用来发送消息，不能接收网络代理发来的消息。</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/activemq-faq">ActiveMQ知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/activemq-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ActiveMQ代理网络无法连接的问题一例</title>
		<link>https://blog.gmem.cc/activemq-network-connetcor-problem-caused-by-multihomed-host</link>
		<comments>https://blog.gmem.cc/activemq-network-connetcor-problem-caused-by-multihomed-host#comments</comments>
		<pubDate>Mon, 15 Sep 2014 09:23:20 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[ActiveMQ]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=632</guid>
		<description><![CDATA[<p>环境说明：ActiveMQ 5.10.0，Windows Server 2008 R2。 最近开发人员在做基于ActiveMQ的数据同步测试时，其中的一台AMQ服务器一直无法连接到代理网络中，久久找不到原因。 我检查了一下配置，各ActiveMQ服务器使用多播地址：multicast://default进行自动发现，配置文件很简单，看不出什么问题。 通过jConsole连接： [crayon-69d26efd6e68b815171642/] 发现问题服务器上networkConnectors里面没有任何内容。 开启DEBUG级别的ActiveMQ日志，没有发现和建立代理网络连接有关的任何日志，估计问题服务器的网络存在问题，使用iperf监测网络上的多播数据报： [crayon-69d26efd6e68f174062068/] 在问题服务器上，上述脚本没有任何输出，其它服务器上则不断收到多播数据报。并且，问题服务器上的绑定地址不是我们内网的192.168.0网段。打开网络连接查看，该IP是Vmware虚拟网卡的地址，尝试禁用虚拟网卡，重启后，一切恢复正常。考虑问题和多网卡有关——ActiveMQ在进行多播时，使用了错误的网卡。启用Vmware虚拟网卡并重启，然后打开MulticastDiscoveryAgent的TRACE日志： [crayon-69d26efd6e691288331703/] 监测到以下日志： [crayon-69d26efd6e693033018550/] 可以看到，多播的地址、端口、组都是正确的，后面还有一些interface、network interface、join network interface，看着好像和网卡设置有关，查看MulticastDiscoveryAgent的源代码： [crayon-69d26efd6e696500460467/] 可以看到，ActiveMQ对JDK标准类java.net.MulticastSocket的实例mcast进行了网卡相关的设置，相关方法的用途如下： <a class="read-more" href="https://blog.gmem.cc/activemq-network-connetcor-problem-caused-by-multihomed-host">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/activemq-network-connetcor-problem-caused-by-multihomed-host">ActiveMQ代理网络无法连接的问题一例</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both">环境说明：ActiveMQ 5.10.0，Windows Server 2008 R2。<br />
最近开发人员在做基于ActiveMQ的数据同步测试时，其中的一台AMQ服务器一直无法连接到代理网络中，久久找不到原因。<br />
我检查了一下配置，各ActiveMQ服务器使用多播地址：multicast://default进行自动发现，配置文件很简单，看不出什么问题。<br />
通过jConsole连接：<br />
<pre class="crayon-plain-tag">service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi</pre><br />
发现问题服务器上networkConnectors里面没有任何内容。<br />
开启DEBUG级别的ActiveMQ日志，没有发现和建立代理网络连接有关的任何日志，估计问题服务器的网络存在问题，使用iperf监测网络上的多播数据报：<br />
<pre class="crayon-plain-tag">iperf -s  -u  -B 239.255.2.3 -p 6155  -i 1
------------------------------------------------------------
Server listening on UDP port 6155
Binding to local address 192.168.7.130
Receiving 1470 byte datagrams
UDP buffer size: 8.00 KByte (default)
------------------------------------------------------------</pre><br />
在问题服务器上，上述脚本没有任何输出，其它服务器上则不断收到多播数据报。并且，问题服务器上的绑定地址不是我们内网的192.168.0网段。打开网络连接查看，该IP是Vmware虚拟网卡的地址，尝试禁用虚拟网卡，重启后，一切恢复正常。考虑问题和多网卡有关——ActiveMQ在进行多播时，使用了错误的网卡。启用Vmware虚拟网卡并重启，然后打开MulticastDiscoveryAgent的TRACE日志：<br />
<pre class="crayon-plain-tag">log4j.logger.org.apache.activemq.transport.discovery.multicast.MulticastDiscoveryAgent=TRACE,DataSync</pre><br />
监测到以下日志：<br />
<pre class="crayon-plain-tag">[TRACE] [Thread-1] 2014-09-16 11:16:40 org.apache.activemq.transport.discovery.multicast.MulticastDiscoveryAgent.start(MulticastDiscoveryAgent.java:290)
start - discoveryURI = multicast://default
[TRACE] [Thread-1] 2014-09-16 11:16:40 org.apache.activemq.transport.discovery.multicast.MulticastDiscoveryAgent.start(MulticastDiscoveryAgent.java:302)
start - myHost = 239.255.2.3
[TRACE] [Thread-1] 2014-09-16 11:16:40 org.apache.activemq.transport.discovery.multicast.MulticastDiscoveryAgent.start(MulticastDiscoveryAgent.java:303)
start - myPort = 6155
[TRACE] [Thread-1] 2014-09-16 11:16:40 org.apache.activemq.transport.discovery.multicast.MulticastDiscoveryAgent.start(MulticastDiscoveryAgent.java:304)
start - group  = default
[TRACE] [Thread-1] 2014-09-16 11:16:40 org.apache.activemq.transport.discovery.multicast.MulticastDiscoveryAgent.start(MulticastDiscoveryAgent.java:305)
start - interface  = null
[TRACE] [Thread-1] 2014-09-16 11:16:40 org.apache.activemq.transport.discovery.multicast.MulticastDiscoveryAgent.start(MulticastDiscoveryAgent.java:306)
start - network interface  = null
[TRACE] [Thread-1] 2014-09-16 11:16:40 org.apache.activemq.transport.discovery.multicast.MulticastDiscoveryAgent.start(MulticastDiscoveryAgent.java:307)
start - join network interface  = null</pre><br />
可以看到，多播的地址、端口、组都是正确的，后面还有一些interface、network interface、join network interface，看着好像和网卡设置有关，查看MulticastDiscoveryAgent的源代码：<br />
<pre class="crayon-plain-tag">public void start() throws Exception {
        
    if (started.compareAndSet(false, true)) {            
                     
        if (group == null || group.length() == 0) {
            throw new IOException("You must specify a group to discover");
        }
        String type = getType();
        if (!type.endsWith(".")) {
            LOG.warn("The type '" + type + "' should end with '.' to be a valid Discovery type");
            type += ".";
        }
        
        if (discoveryURI == null) {
            discoveryURI = new URI(DEFAULT_DISCOVERY_URI_STRING);
        }
        
        if (LOG.isTraceEnabled()) 
              LOG.trace("start - discoveryURI = " + discoveryURI);                                
          
          String myHost = discoveryURI.getHost();
          int    myPort = discoveryURI.getPort(); 
             
          if( DEFAULT_HOST_STR.equals(myHost) ) 
              myHost = DEFAULT_HOST_IP;                       
          
          if(myPort &lt; 0 )
            myPort = DEFAULT_PORT;                
          
          if (LOG.isTraceEnabled()) {
              LOG.trace("start - myHost = " + myHost); 
              LOG.trace("start - myPort = " + myPort);       
              LOG.trace("start - group  = " + group );                         
              LOG.trace("start - interface  = " + mcInterface );
              LOG.trace("start - network interface  = " + mcNetworkInterface );
              LOG.trace("start - join network interface  = " + mcJoinNetworkInterface );
          }    
          
        this.inetAddress = InetAddress.getByName(myHost);
        this.sockAddress = new InetSocketAddress(this.inetAddress, myPort);
        mcast = new MulticastSocket(myPort);
        mcast.setLoopbackMode(loopBackMode);
        mcast.setTimeToLive(getTimeToLive());
        if (mcJoinNetworkInterface != null) {
            mcast.joinGroup(sockAddress, NetworkInterface.getByName(mcJoinNetworkInterface));
        }
        else {
            mcast.joinGroup(inetAddress);
        }
        mcast.setSoTimeout((int)keepAliveInterval);
        if (mcInterface != null) {
            mcast.setInterface(InetAddress.getByName(mcInterface));
        }
        if (mcNetworkInterface != null) {
            mcast.setNetworkInterface(NetworkInterface.getByName(mcNetworkInterface));
        }
        runner = new Thread(this);
        runner.setName(this.toString() + ":" + runner.getName());
        runner.setDaemon(true);
        runner.start();
        doAdvertizeSelf();
    }
}</pre><br />
可以看到，ActiveMQ对JDK标准类java.net.MulticastSocket的实例mcast进行了网卡相关的设置，相关方法的用途如下：<br />
<pre class="crayon-plain-tag">/**
 * 加入指定网络接口上的多播组。
 * @param mcastaddr 准备加入的多播地址
 * @param netIf 指定用于接收多播数据报的本地网络接口。
 * 如果不指定，将使用setInterface(InetAddress)、setNetworkInterface(NetworkInterface)所指定的接口
 */
public void joinGroup(SocketAddress mcastaddr, NetworkInterface netIf) throws IOException;
/**
 * 设置相关受影响方法使用的多播网络接口，对于多重网络的主机有意义
 * @param inf IP地址对象
 */
public void setInterface(InetAddress inf) throws SocketException;
/**
 * 设置发送多播数据报使用的网络接口
 * @param netIf 网络接口对象
 */
public void setNetworkInterface(NetworkInterface netIf) throws SocketException;</pre><br />
MulticastDiscoveryAgent根据自身的属性配置情况来调用上述的方法，但是在ActiveMQ官方文档中，并没有找到interface、networkInterface等属性如何在XML中配置的说明，跟踪MulticastDiscoveryAgent的创建过程：<br />
<pre class="crayon-plain-tag">protected DiscoveryAgent doCreateDiscoveryAgent(URI uri) throws IOException {
    try {
        
          if (LOG.isTraceEnabled()) {      
           LOG.trace("doCreateDiscoveryAgent: uri = " + uri.toString());               
        }
        
        MulticastDiscoveryAgent mda = new MulticastDiscoveryAgent();          
        
        mda.setDiscoveryURI(uri);            
                    
        // allow MDA's params to be set via query arguments  
        // (e.g., multicast://default?group=foo             
        Map options = URISupport.parseParameters(uri);         
        IntrospectionSupport.setProperties(mda, options);
        
        return mda;
        
    } catch (Throwable e) {
        throw IOExceptionSupport.create("Could not create discovery agent: " + uri, e);
    }
}</pre><br />
从第14行可以看到，ActiveMQ会解析URI中的所有参数，并利用反射机制设置到MulticastDiscoveryAgent对象中，因此，只需要将需要设置的属性附到URI参数中即可：<br />
<pre class="crayon-plain-tag">&lt;broker xmlns="http://activemq.apache.org/schema/core" brokerName="dataSyncBroker" dataDirectory="D:/amq/datasync/data"&gt;
    &lt;networkConnectors&gt;
        &lt;networkConnector uri="multicast://default?joinNetworkInterface=net13&amp;networkInterface=net13&amp;interface=192.168.0.89" dynamicOnly="true" networkTTL="3" prefetchSize="1"
            decreaseNetworkConsumerPriority="true" /&gt;
    &lt;/networkConnectors&gt;
    &lt;transportConnectors&gt; 
        &lt;transportConnector name="openwire" uri="tcp://0.0.0.0:61616" discoveryUri="multicast://default" /&gt;
    &lt;/transportConnectors&gt;</pre><br />
至此，问题解决。<br />
总结：ActiveMQ的自动代理发现机制比较方便、灵活，但是受客户环境影响较大（网络结构、防火墙等），如果AMQ代理的IP地址固定或基本不会增减，可以考虑使用静态的代理网络。
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/activemq-network-connetcor-problem-caused-by-multihomed-host">ActiveMQ代理网络无法连接的问题一例</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/activemq-network-connetcor-problem-caused-by-multihomed-host/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>基于CMS接口的ActiveMQ CPP客户端示例</title>
		<link>https://blog.gmem.cc/activemq-cpp-client-demo</link>
		<comments>https://blog.gmem.cc/activemq-cpp-client-demo#comments</comments>
		<pubDate>Tue, 19 Aug 2014 06:04:05 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[C++]]></category>
		<category><![CDATA[ActiveMQ]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=993</guid>
		<description><![CDATA[<p>[crayon-69d26efd6e9de384533060/] 生产者头文件： [crayon-69d26efd6e9e1598108171/] 生产者实现： [crayon-69d26efd6e9e4718815782/] 消费者头文件： [crayon-69d26efd6e9e6980661153/] 消费者实现，注意阻塞等待receive、监听器listen两种消费方式： [crayon-69d26efd6e9e9246512941/] 主函数代码： [crayon-69d26efd6e9ec492788598/] &#160;</p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/activemq-cpp-client-demo">基于CMS接口的ActiveMQ CPP客户端示例</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><pre class="crayon-plain-tag">#ifndef AMQUTILS_H_
#define AMQUTILS_H_
#include &lt;cms/Connection.h&gt;
#include &lt;cms/Session.h&gt;
#include &lt;decaf/lang/Exception.h&gt;
using namespace decaf::lang;
using namespace cms;

namespace amqutils
{
    inline void closeQuitely( Connection* conn, Session* session )
    {
        if ( session ) try
        {
            session-&gt;close();
        }
        catch ( Exception&amp; e )
        {

        }
        if ( conn ) try
        {
            conn-&gt;close();
        }
        catch ( Exception&amp; e )
        {

        }
    }
}

#endif</pre><br />
生产者头文件：<br />
<pre class="crayon-plain-tag">#ifndef PRODUCER_H_
#define PRODUCER_H_

#include &lt;activemq/core/ActiveMQConnectionFactory.h&gt;

#include &lt;boost/shared_ptr.hpp&gt;

#include &lt;stdlib.h&gt;
#include &lt;stdio.h&gt;

using namespace activemq;
using namespace activemq::core;
using namespace decaf;
using namespace decaf::lang;
using namespace decaf::util;
using namespace decaf::util::concurrent;
using namespace cms;
using namespace std;
using namespace boost;

class Producer
{
    private:
        boost::shared_ptr&lt;ActiveMQConnectionFactory&gt; amqf;
        Connection* connection;
        Session* session;
    public:
        Producer( string&amp; uri, string&amp; userName, string&amp; password );
        Producer( boost::shared_ptr&lt;ActiveMQConnectionFactory&gt; amqf );
        virtual ~Producer();
        virtual void init();
        virtual void send( string&amp; queueName, string&amp; msg );
        virtual void pub( string&amp; topicName, string&amp; msg );
};

#endif</pre><br />
生产者实现：<br />
<pre class="crayon-plain-tag">#include "Producer.h"
#include &lt;cms/Connection.h&gt;
#include &lt;boost/scoped_ptr.hpp&gt;
#include "amqutils.h"
#include &lt;slf4cmacros.h&gt;

Producer::Producer( string&amp; uri, string&amp; userName, string&amp; password ) :
        connection( NULL ), session( NULL )
{
    amqf = shared_ptr&lt;ActiveMQConnectionFactory&gt;( new ActiveMQConnectionFactory( uri, userName, password ) );
}
Producer::Producer( shared_ptr&lt;ActiveMQConnectionFactory&gt; amqf ) :
        amqf( amqf ), connection( NULL ), session( NULL )
{
}
Producer::~Producer()
{
    SLF_DEBUG( "准备销毁ActiveMQ连接、会话" );
    amqutils::closeQuitely( connection, session );
    delete session;
    delete connection;
}
void Producer::init()
{
    SLF_DEBUG( "准备创建ActiveMQ连接" );
    connection = amqf-&gt;createConnection();
    SLF_DEBUG( "准备创建ActiveMQ会话" );
    session = connection-&gt;createSession();
}

void Producer::send( string&amp; queueName, string&amp; msg )
{
    SLF_DEBUG( "准备发送ActiveMQ消息到队列" + queueName );
    scoped_ptr&lt;Destination&gt; dest( session-&gt;createQueue( queueName ) );
    scoped_ptr&lt;MessageProducer&gt; producer( session-&gt;createProducer( dest.get() ) );
    scoped_ptr&lt;TextMessage&gt; message( session-&gt;createTextMessage( msg ) );
    producer-&gt;send( message.get() );
}
void Producer::pub( string&amp; topicName, string&amp; msg )
{
    SLF_DEBUG( "准备发布ActiveMQ消息到主题" + topicName );
    scoped_ptr&lt;Destination&gt; dest( session-&gt;createTopic( topicName ) );
    scoped_ptr&lt;MessageProducer&gt; producer( session-&gt;createProducer( dest.get() ) );
    scoped_ptr&lt;TextMessage&gt; message( session-&gt;createTextMessage( msg ) );
    producer-&gt;send( message.get() );
}</pre><br />
消费者头文件：<br />
<pre class="crayon-plain-tag">#ifndef CONSUMER_H_
#define CONSUMER_H_

#include &lt;activemq/core/ActiveMQConnectionFactory.h&gt;

#include &lt;boost/shared_ptr.hpp&gt;

#include &lt;stdlib.h&gt;
#include &lt;stdio.h&gt;
#include &lt;boost/ptr_container/ptr_map.hpp&gt;
#include &lt;boost/ptr_container/ptr_vector.hpp&gt;

using namespace activemq;
using namespace activemq::core;
using namespace decaf;
using namespace decaf::lang;
using namespace decaf::util;
using namespace decaf::util::concurrent;
using namespace cms;
using namespace std;
using namespace boost;

class Consumer
{
    private:
        boost::shared_ptr&lt;ActiveMQConnectionFactory&gt; amqf;
        Connection* connection;
        Session* session;
        ptr_map&lt;string, MessageConsumer*&gt; consumers;
        ptr_vector&lt;MessageListener&gt; listeners;

    public:
        Consumer( string&amp; uri, string&amp; userName, string&amp; password );
        Consumer( boost::shared_ptr&lt;ActiveMQConnectionFactory&gt; amqf );
        virtual ~Consumer();
        virtual void init();
        string receive( string&amp; queueName, int timeout );
        void listen( string queueName, void (*listener)( TextMessage* ) );
};

#endif</pre><br />
消费者实现，注意阻塞等待receive、监听器listen两种消费方式：<br />
<pre class="crayon-plain-tag">#include "Consumer.h"
#include &lt;cms/Connection.h&gt;
#include &lt;boost/scoped_ptr.hpp&gt;
#include "amqutils.h"
#include &lt;slf4cmacros.h&gt;
#include &lt;boost/lexical_cast.hpp&gt;

Consumer::Consumer( string&amp; uri, string&amp; userName, string&amp; password ) :
        connection( NULL ), session( NULL )
{
    amqf = boost::shared_ptr&lt;ActiveMQConnectionFactory&gt;( new ActiveMQConnectionFactory( uri, userName, password ) );
}
Consumer::Consumer( boost::shared_ptr&lt;ActiveMQConnectionFactory&gt; amqf ) :
        amqf( amqf ), connection( NULL ), session( NULL )
{
}
Consumer::~Consumer()
{
    SLF_DEBUG( "准备销毁ActiveMQ连接、会话" );
    amqutils::closeQuitely( connection, session );
    delete session;
    delete connection;
}
void Consumer::init()
{
    SLF_DEBUG( "准备创建ActiveMQ连接" );
    connection = amqf-&gt;createConnection();
    connection-&gt;start(); //必须，否则不会触发监听
    SLF_DEBUG( "准备创建ActiveMQ会话" );
    session = connection-&gt;createSession();
}
string Consumer::receive( string&amp; queueName, int timeout )
{
    {
        string msg = "准备等待队列" + queueName + "并接受一条消息，超时为" + lexical_cast&lt;string, int&gt;( timeout ) + "秒";
        SLF_DEBUG( msg );
    }
    scoped_ptr&lt;Destination&gt; dest( session-&gt;createQueue( queueName ) );
    scoped_ptr&lt;MessageConsumer&gt; consumer( session-&gt;createConsumer( dest.get() ) );
    Message* message = consumer-&gt;receive( timeout * 1000 );
    if ( message )
    {
        TextMessage* tm = dynamic_cast&lt;TextMessage*&gt;( message );
        string content( tm-&gt;getText() );
        delete message;
        return content;
    }
    else
    {
        throw CMSException( "在超过时间限制之前没有获取到消息." );
    }
}
class PlainMessageListener : public MessageListener
{
    public:
        typedef void (*ListenerCallback)( TextMessage* );
        virtual void onMessage( const Message* message );
        PlainMessageListener( ListenerCallback callback ) :
                listenerCallback( callback )
        {
        }
        ~PlainMessageListener()
        {
            SLF_DEBUG( "准备销毁监听器..." );
        }
    private:
        ListenerCallback listenerCallback;
};
void PlainMessageListener::onMessage( const Message* message )
{
    const TextMessage* msg = dynamic_cast&lt;const TextMessage*&gt;( message );
    listenerCallback( const_cast&lt;TextMessage*&gt;( msg ) );
}
void Consumer::listen( string queueName, void (*listener)( TextMessage* ) )
{
    scoped_ptr&lt;Destination&gt; dest( session-&gt;createQueue( queueName ) );
    MessageConsumer* consumer( session-&gt;createConsumer( dest.get() ) );
    if ( consumers.count( queueName ) == 1 )
    {
        throw CMSException( "当前消费者已经在监听队列：" + queueName );
    }
    else
    {
        consumers[queueName] = consumer;
        SLF_DEBUG( "准备创建监听器..." );
        MessageListener* pml = new PlainMessageListener( listener );
        consumer-&gt;setMessageListener( pml );
        listeners.push_back( pml );
    }
}</pre><br />
主函数代码：<br />
<pre class="crayon-plain-tag">#include "Producer.h"
#include "Consumer.h"
#include &lt;activemq/library/ActiveMQCPP.h&gt;
#include &lt;boost/filesystem.hpp&gt;
#include &lt;boost/thread.hpp&gt;
#include &lt;boost/bind.hpp&gt;
#include &lt;boost/lexical_cast.hpp&gt;
#include &lt;boost/date_time/posix_time/posix_time_duration.hpp&gt;
#include &lt;slf4cmacros.h&gt;

const char* QUEUE_NAME = "DEMO.Q2";

using namespace std;

void producerRunner( boost::shared_ptr&lt;ActiveMQConnectionFactory&gt; amqf )
{
    Producer p( amqf );
    SLF_DEBUG( "准备初始化生产者" );
    p.init();
    string queneName( QUEUE_NAME );
    SLF_DEBUG( "准备向：" + queneName + "发送消息" );
    for ( int i = 0; i &lt; 10; i++ )
    {
        string content( "Hello C++, " + lexical_cast&lt;string, int&gt;( i ) );
        p.send( queneName, content );
        SLF_DEBUG( "已发送：" + content + "，当前线程将休眠" );
        this_thread::sleep( posix_time::seconds( 5 ) );
    }
}
void consumerListener( TextMessage* msg )
{
    SLF_DEBUG( "监听到消息，内容为：" + msg-&gt;getText() );
}
void consumerRunner( boost::shared_ptr&lt;ActiveMQConnectionFactory&gt; amqf )
{
    Consumer c( amqf );
    SLF_DEBUG( "准备初始化消费者" );
    c.init();
    string queneName( QUEUE_NAME );
    SLF_DEBUG( "准备监听队列：" + queneName );
    c.listen( queneName, &amp;consumerListener );
    SLF_DEBUG( "当前线程将休眠60秒，以等待消息" );
    this_thread::sleep( posix_time::seconds( 60 ) );
}
int main()
{
    SLF_DEBUG( "初始化ActiveMQ-CPP运行时库" );
    activemq::library::ActiveMQCPP::initializeLibrary();
    {
        boost::shared_ptr&lt;ActiveMQConnectionFactory&gt; amqf( new ActiveMQConnectionFactory( "tcp://localhost:61616" ) );
        thread_group g;
        SLF_DEBUG( "准备发动生产者线程" );
        g.create_thread( bind( &amp;producerRunner, amqf ) );
        SLF_DEBUG( "准备发动消费者线程" );
        g.create_thread( bind( &amp;consumerRunner, amqf ) );
        SLF_DEBUG( "等待线程子线程结束..." );
        g.join_all();
    }
    SLF_DEBUG( "销毁ActiveMQ-CPP运行时库" );
    activemq::library::ActiveMQCPP::shutdownLibrary();
}</pre><br />
&nbsp;
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/activemq-cpp-client-demo">基于CMS接口的ActiveMQ CPP客户端示例</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/activemq-cpp-client-demo/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>基于JMS的ActiveMQ Java客户端示例</title>
		<link>https://blog.gmem.cc/activemq-java-client-demo</link>
		<comments>https://blog.gmem.cc/activemq-java-client-demo#comments</comments>
		<pubDate>Tue, 12 Aug 2014 06:36:31 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[ActiveMQ]]></category>
		<category><![CDATA[JMS]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=1072</guid>
		<description><![CDATA[<p>JMS生产者： [crayon-69d26efd6ecfd027118344/] JMS消费者： [crayon-69d26efd6ed01556849652/] 结合Spring使用，配置文件示例 [crayon-69d26efd6ed04571785521/] 监听器实现类示例 [crayon-69d26efd6ed07222742027/]</p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/activemq-java-client-demo">基于JMS的ActiveMQ Java客户端示例</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both">JMS生产者：<br />
<pre class="crayon-plain-tag">package cc.gmem.demo.amq.client;

import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnectionFactory;



public class Producer
{

    private ActiveMQConnectionFactory factory;

    public Producer( String brokerHost, int brokerPort )
    {
        factory = new ActiveMQConnectionFactory( "tcp://" + brokerHost + ":" + brokerPort );
    }

    public void sendMsgToQueue( String queueName, String msg ) throws JMSException
    {
        Connection connection = null;
        try
        {
            //如果代理启用身份验证，这里需要指定用户、密码参数
            connection = factory.createConnection();
            connection.start();
            //创建会话时有事务和确认选项
            Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );
            //如果代理上没有队列，则创建，否则，直接使用
            Destination dest = session.createQueue( queueName );
            //创建消息生产者
            MessageProducer producer = session.createProducer( dest );
            //设置消息是否持久化
            producer.setDeliveryMode( DeliveryMode.NON_PERSISTENT );
            //创建一个文本消息
            TextMessage message = session.createTextMessage( msg );
            producer.send( message );
        }
        finally
        {
            connection.close();
        }
    }
}</pre><br />
JMS消费者：<br />
<pre class="crayon-plain-tag">package cc.gmem.demo.amq.client;

import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class Consumer
{

    private static final Logger       LOGGER = LoggerFactory.getLogger( Consumer.class );


    private ActiveMQConnectionFactory factory;

    public Consumer( String brokerHost, int brokerPort )
    {
        factory = new ActiveMQConnectionFactory( "tcp://" + brokerHost + ":" + brokerPort );
    }


    public String reveiveMsgFromQueue( String queueName ) throws JMSException
    {
        Connection connection = null;
        try
        {
            //如果代理启用身份验证，这里需要指定用户、密码参数
            connection = factory.createConnection();
            connection.start();
            //创建会话时有事务和确认选项，如果确认选项为：SESSION_TRANSACTED，则必须启用事务
            Session session = connection.createSession( true, Session.SESSION_TRANSACTED );
            //如果代理上没有队列，则创建，否则，直接使用
            Destination dest = session.createQueue( queueName );
            //创建消息生产者
            MessageConsumer consumer = session.createConsumer( dest );
            //等待队列里面有消息可消费，最多1秒，超时返回NULL
            long timeout = 1000;
            TextMessage msg = (TextMessage) consumer.receive( timeout );
            String recMsg = msg == null ? null : msg.getText();
            //如果设置：Session.CLIENT_ACKNOWLEDGE，则必须手工确认：msg.acknowledge()
            session.commit();//如果设置：Session.SESSION_TRANSACTED，则必须提交或者回滚
            return recMsg;
        }
        finally
        {
            connection.close();
        }
    }

}</pre><br />
结合Spring使用，配置文件示例<br />
<pre class="crayon-plain-tag">&lt;beans 
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:jms="http://www.springframework.org/schema/jms"
    xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.0.xsd
        http://www.springframework.org/schema/task  http://www.springframework.org/schema/task/spring-task-3.0.xsd
  "&gt;
    &lt;!-- 启用连接池支持的连接工厂 --&gt;
    &lt;bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop"&gt;
        &lt;property name="connectionFactory"&gt;
            &lt;bean class="org.apache.activemq.ActiveMQConnectionFactory"&gt;
                &lt;property name="brokerURL"&gt;
                    &lt;value&gt;vm://localBroker&lt;/value&gt;
                &lt;/property&gt;
            &lt;/bean&gt;
        &lt;/property&gt;
    &lt;/bean&gt;
    &lt;!-- 以事件驱动的方式接收消息 --&gt;
    &lt;!--  tipsMessageListener即为监听器对象，通常实现JMS的MessageListener接口 --&gt;
    &lt;jms:listener-container container-type="default" connection-factory="jmsFactory" acknowledge="auto"&gt;
        &lt;jms:listener destination="TIPS.10000.BATCH" ref="tipsMessageListener" method="onMessage" /&gt;
    &lt;/jms:listener-container&gt;
    &lt;!-- jmsTemplate通常用来发送消息 --&gt;
    &lt;bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"&gt;
        &lt;property name="connectionFactory" ref="jmsFactory" /&gt;
        &lt;property name="explicitQosEnabled" value="true" /&gt;
    &lt;/bean&gt;
&lt;/beans&gt;</pre><br />
监听器实现类示例<br />
<pre class="crayon-plain-tag">public void onMessage( Message message )
{
    try
    {
        TextMessage msg = (TextMessage) message;
        String text = msg.getText();
        LOGGER.debug( "Received Message: \n" + text );
        onMsgReceived( msgService.create( text ) );
    }
    catch ( JMSException e )
    {
        LOGGER.error( e.getMessage(), e );
    }
}</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/activemq-java-client-demo">基于JMS的ActiveMQ Java客户端示例</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/activemq-java-client-demo/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>常用C++库的编译步骤记录</title>
		<link>https://blog.gmem.cc/compile-step-of-frequently-used-cpp-lib</link>
		<comments>https://blog.gmem.cc/compile-step-of-frequently-used-cpp-lib#comments</comments>
		<pubDate>Sat, 22 Feb 2014 09:09:24 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[C++]]></category>
		<category><![CDATA[ActiveMQ]]></category>
		<category><![CDATA[BOOST]]></category>
		<category><![CDATA[wxWidgets]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=2013</guid>
		<description><![CDATA[<p>BOOST 1.55.0 MinGW [crayon-69d26efd6ef77451564027/] Cygwin [crayon-69d26efd6ef7b575132404/] wxWidgets 3.0.1 如果PATH环境变量中包含MSYS的bin目录，需要暂时禁用 MinGW [crayon-69d26efd6ef7d117641872/] ActiveMQ CMS Library 3.8.3 GCC依赖库 [crayon-69d26efd6ef7f913631580/]  GCC [crayon-69d26efd6ef82105051344/] MinGW 在MSYS的终端中执行： [crayon-69d26efd6ef84150691172/] <a class="read-more" href="https://blog.gmem.cc/compile-step-of-frequently-used-cpp-lib">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/compile-step-of-frequently-used-cpp-lib">常用C++库的编译步骤记录</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">BOOST 1.55.0</span></div>
<div><strong>MinGW</strong></div>
<pre class="crayon-plain-tag">CD D:\CPP\tools\boost-1.55.0\tools\build\v2
build.bat mingw
CD D:\CPP\tools\boost-1.55.0
REM 下面是静态链接的多线程支持版本，根据需要调整
bjam --build-type=complete toolset=gcc address-model=32 variant=debug,release threading=multi link=static</pre>
<div><strong>Cygwin</strong></div>
<pre class="crayon-plain-tag">#在Cygwin Terminal中执行
./bjam --layout=versioned --build-type=complete toolset=gcc address-model=32 variant=debug,release threading=multi link=static</pre>
<div class="blog_h2"><span class="graybg">wxWidgets 3.0.1</span></div>
<div>如果PATH环境变量中包含MSYS的bin目录，需要暂时禁用</div>
<div><strong>MinGW</strong></div>
<pre class="crayon-plain-tag">mingw32-make -f makefile.gcc UNICODE=1 SHARED=0 BUILD=release clean
mingw32-make -f makefile.gcc UNICODE=1 SHARED=0 BUILD=debug</pre>
<div class="blog_h2"><span class="graybg">ActiveMQ CMS Library 3.8.3</span></div>
<div><strong>GCC依赖库</strong></div>
<pre class="crayon-plain-tag">#CPPUnit，单元测试支持，该库会安装到：/usr/local/bin/cppunit-config
cd /usr/src/cppunit-1.12.0
./configure CFLAGS="-O2 -s -mms-bitfields -march=i686" CXXFLAGS="-O2 -s -mms-bitfields -march=i686" --disable-shared
make &amp;&amp; make install
#XML解析依赖
cd /usr/src/expat-2.0.0
./configure CFLAGS="-O2 -s -mms-bitfields -march=i686" CXXFLAGS="-O2 -s -mms-bitfields -march=i686"
make &amp;&amp; make install
#Apache Portable Runtime，注意：-O0 -g3 用于调试，生产环境的版本注意调整
cd /usr/src/apr-1.5.1
./buildconf
./configure CFLAGS="-O0 -g3 -s -mms-bitfields -march=i686" CXXFLAGS="-O0 -g3 -s -mms-bitfields -march=i686"
make &amp;&amp; make install

cd /usr/src/apr-util-1.5.3
./buildconf  --with-apr=../apr-1.5.1
./configure CFLAGS="-O0 -g3 -s -mms-bitfields -march=i686" CXXFLAGS="-O0 -g3 -s -mms-bitfields -march=i686"  --with-apr=/usr/local/apr/bin/apr-1-config
make &amp;&amp; make install

cd /usr/src/apr-iconv-1.2.1
./buildconf  --with-apr=../apr-1.5.1
./configure CFLAGS="-O0 -g3 -s -mms-bitfields -march=i686" CXXFLAGS="-O0 -g3 -s -mms-bitfields -march=i686"  --with-apr=/usr/local/apr/bin/apr-1-config
make &amp;&amp; make install</pre>
<div><strong> GCC</strong></div>
<pre class="crayon-plain-tag">cd /usr/src/activemq-cpp-library-3.8.3
./autogen.sh
./configure CFLAGS="-O0 -g3 -s -mms-bitfields -march=i686" CXXFLAGS="-O0 -g3 -s -mms-bitfields  -std=gnu++0x -march=i686" --with-apr=/usr/local/apr/bin/apr-1-config
make &amp;&amp; make install</pre>
<p><strong>MinGW</strong></p>
<div>在MSYS的终端中执行：</div>
<pre class="crayon-plain-tag">./configure CFLAGS="-O0 -g3 -s -mms-bitfields -march=i686 -DHAVE_WINSOCK2_H" CXXFLAGS="-O0 -g3 -s -mms-bitfields  -std=gnu++0x -march=i686 -DHAVE_WINSOCK2_H" --with-apr=/usr/local/apr/bin/apr-1-config</pre>
<div><strong>Cygwin</strong></div>
<div class="blog_h3">
<pre class="crayon-plain-tag">./autogen.sh
./configure  CXXFLAGS="-O0 -mms-bitfields -std=gnu++0x"
make &amp;&amp; make install</pre>
</div>
<div class="blog_h2"><span class="graybg">Google Test 1.7.0</span></div>
<div><strong>MinGW</strong></div>
<pre class="crayon-plain-tag">./configure --with-pthreads=no
make
#编辑的静态库位于lib/.lib目录下</pre>
<div><strong>Cygwin</strong></div>
<pre class="crayon-plain-tag">./configure
make</pre>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/compile-step-of-frequently-used-cpp-lib">常用C++库的编译步骤记录</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/compile-step-of-frequently-used-cpp-lib/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用Atomikos来进行分布式事务(XA)开发</title>
		<link>https://blog.gmem.cc/jta-with-atomikos</link>
		<comments>https://blog.gmem.cc/jta-with-atomikos#comments</comments>
		<pubDate>Mon, 26 Nov 2012 10:16:53 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[ActiveMQ]]></category>
		<category><![CDATA[Atomikos]]></category>
		<category><![CDATA[JTA]]></category>
		<category><![CDATA[XA]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=1141</guid>
		<description><![CDATA[<p>结合Spring使用 和Spring集成的时候，不需要JNDI服务器 注意，不支持Spring的事务传播性：PROPAGATION_NESTED JTA事务管理器配置样例： [crayon-69d26efd6f286968126033/] 配合消息中间件使用（ActiveMQ）的配置样例： 3.9以前的版本，必须设置sessionTransacted为true [crayon-69d26efd6f28a478363464/] 配合数据库使用的配置样例： [crayon-69d26efd6f28c953260127/] 不使用Spring，单独和Hibernate集成 Hibernate配置示例： [crayon-69d26efd6f28f163532457/] jndi.properties配置示例： [crayon-69d26efd6f291005934430/] jta.properties配置示例： [crayon-69d26efd6f294156734027/] 通过编程方式初始化数据源，并绑定到JNDI，进行编程式事务控制： [crayon-69d26efd6f296862121270/] 一些常见问题 com.atomikos.icatch.RollbackException: Prepare: <a class="read-more" href="https://blog.gmem.cc/jta-with-atomikos">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/jta-with-atomikos">使用Atomikos来进行分布式事务(XA)开发</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div><span class="bh2">结合Spring使用</span></div>
和Spring集成的时候，不需要JNDI服务器<br />
注意，不支持Spring的事务传播性：PROPAGATION_NESTED<br />
JTA事务管理器配置样例：<br />
<pre class="crayon-plain-tag">&lt;bean id="localLogAdministrator" class="com.atomikos.icatch.admin.imp.LocalLogAdministrator" /&gt;
&lt;bean id="userTransactionService" class="com.atomikos.icatch.config.UserTransactionServiceImp" init-method="init"
    destroy-method="shutdownForce"&gt;
    &lt;constructor-arg&gt;
        &lt;!-- 配置Atomikos属性 --&gt;
        &lt;props&gt;
            &lt;prop key="com.atomikos.icatch.service"&gt;com.atomikos.icatch.standalone.UserTransactionServiceFactory&lt;/prop&gt;
        &lt;/props&gt;
    &lt;/constructor-arg&gt;
    &lt;property name="initialLogAdministrators"&gt;
        &lt;list&gt;
            &lt;ref bean="localLogAdministrator" /&gt;
        &lt;/list&gt;
    &lt;/property&gt;
&lt;/bean&gt;

&lt;!-- Atomikos 事务管理器配置 --&gt;
&lt;bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close"
    depends-on="userTransactionService"&gt;
    &lt;property name="startupTransactionService" value="false" /&gt;
    &lt;!-- close()时是否强制终止事务 --&gt;
    &lt;property name="forceShutdown" value="false" /&gt;
&lt;/bean&gt;

&lt;!-- Atomikos UserTransaction配置 --&gt;
&lt;bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp" depends-on="userTransactionService"&gt;
    &lt;property name="transactionTimeout" value="300" /&gt;
&lt;/bean&gt;

&lt;!-- JTA事务管理器 --&gt;
&lt;bean id="jtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager" depends-on="userTransactionService"&gt;
    &lt;property name="transactionManager" ref="atomikosTransactionManager" /&gt;
    &lt;property name="userTransaction" ref="atomikosUserTransaction" /&gt;
&lt;/bean&gt;</pre><br />
配合消息中间件使用（ActiveMQ）的配置样例：<br />
3.9以前的版本，必须设置sessionTransacted为true<br />
<pre class="crayon-plain-tag">&lt;bean id="xaFactory" class="org.apache.activemq.ActiveMQXAConnectionFactory"&gt;
    &lt;property name="brokerURL" value="tcp://localhost:61616" /&gt;
&lt;/bean&gt;
&lt;bean id="jmsFactory" class="com.atomikos.jms.AtomikosConnectionFactoryBean" init-method="init" destroy-method="close"&gt;
    &lt;property name="uniqueResourceName" value="amq" /&gt;
    &lt;property name="xaConnectionFactory" ref="xaFactory" /&gt;
&lt;/bean&gt;
&lt;bean id="tipsMessageListener" class="cc.gmem.demo.TipsMessageListener" /&gt;
&lt;!-- 接收消息 --&gt;
&lt;jms:listener-container container-type="default" connection-factory="jmsFactory"
    transaction-manager="jtaTransactionManager" session-transacted="true"&gt;
    &lt;jms:listener destination="TIPS.10000.BATCH" ref="tipsMessageListener" method="onMessage" /&gt;
&lt;/jms:listener-container&gt;
&lt;!-- 发送消息 --&gt;
&lt;bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"&gt;
    &lt;property name="connectionFactory"&gt;
        &lt;ref bean="jmsFactory" /&gt;
    &lt;/property&gt;
    &lt;property name="receiveTimeout" value="1000" /&gt;
    &lt;property name="sessionTransacted" value="true" /&gt;
&lt;/bean&gt;</pre><br />
配合数据库使用的配置样例：<br />
<pre class="crayon-plain-tag">&lt;bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"&gt;
    &lt;!-- 必须为Hibernate指定一个Atomikos JTA/XA 数据源 --&gt;
    &lt;property name="dataSource" ref="dataSource" /&gt;
&lt;/bean&gt;
&lt;bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate"&gt;
    &lt;property name="sessionFactory" ref="sessionFactory" /&gt;
&lt;/bean&gt;
&lt;bean id="dataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"&gt;
    &lt;property name="uniqueResourceName" value="mysqlDataSource" /&gt;
    &lt;property name="xaDataSourceClassName"&gt;
        &lt;value&gt;com.mysql.jdbc.jdbc2.optional.MysqlXADataSource&lt;/value&gt;
    &lt;/property&gt;
    &lt;property name="xaProperties"&gt;
        &lt;props&gt;
            &lt;prop key="databaseName"&gt;gmem&lt;/prop&gt;
            &lt;prop key="serverName"&gt;localhost&lt;/prop&gt;
            &lt;prop key="port"&gt;3306&lt;/prop&gt;
            &lt;prop key="user"&gt;user&lt;/prop&gt;
            &lt;prop key="password"&gt;pswd&lt;/prop&gt;
            &lt;prop key="url"&gt;jdbc:mysql://localhost:3306/gmem&lt;/prop&gt;
        &lt;/props&gt;
    &lt;/property&gt;
    &lt;property name="minPoolSize" value="10"&gt;
&lt;/bean&gt;
&lt;bean id="dataSourceOracle" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"&gt;
    &lt;property name="uniqueResourceName" value="oralceDataSource" /&gt;
    &lt;property name="xaDataSourceClassName"&gt;
        &lt;value&gt;oracle.jdbc.xa.client.OracleXADataSource&lt;/value&gt;
    &lt;/property&gt;
    &lt;property name="xaProperties"&gt;
        &lt;props&gt;
            &lt;prop key="user"&gt;gmemx&lt;/prop&gt;
            &lt;prop key="password"&gt;pswd&lt;/prop&gt;
            &lt;prop key="URL"&gt;jdbc:oracle:thin:@192.168.0.25:1521:DEV&lt;/prop&gt;
        &lt;/props&gt;
    &lt;/property&gt;
&lt;/bean&gt;</pre>
<div><span class="bh2">不使用Spring，单独和Hibernate集成</span></div>
Hibernate配置示例：<br />
<pre class="crayon-plain-tag">&lt;?xml version='1.0' encoding='utf-8'?&gt;
&lt;!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"&gt;

&lt;hibernate-configuration&gt;

    &lt;session-factory&gt;
    	&lt;property name="connection.datasource"&gt;ds0&lt;/property&gt;
        &lt;property name="dialect"&gt;org.hibernate.dialect.MySQL5Dialect&lt;/property&gt;

        &lt;property name="current_session_context_class"&gt;jta&lt;/property&gt;
		&lt;property name="transaction.manager_lookup_class"&gt;com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup&lt;/property&gt;
 		&lt;property name="transaction.factory_class"&gt;org.hibernate.transaction.JTATransactionFactory&lt;/property&gt;
        &lt;property name="connection.release_mode"&gt;auto&lt;/property&gt;

        &lt;property name="cache.provider_class"&gt;org.hibernate.cache.NoCacheProvider&lt;/property&gt;
        &lt;property name="show_sql"&gt;true&lt;/property&gt;

        &lt;mapping resource="cc/gmem/demo/vo/User.hbm.xml"/&gt;
    &lt;/session-factory&gt;

&lt;/hibernate-configuration&gt;</pre><br />
jndi.properties配置示例：<br />
<pre class="crayon-plain-tag">java.naming.factory.initial=org.apache.naming.java.javaURLContextFactory</pre><br />
jta.properties配置示例：<br />
<pre class="crayon-plain-tag">com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory
com.atomikos.icatch.automatic_resource_registration=true
com.atomikos.icatch.console_log_level=DEBUG</pre><br />
通过编程方式初始化数据源，并绑定到JNDI，进行编程式事务控制：<br />
<pre class="crayon-plain-tag">import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.UserTransaction;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import com.atomikos.jdbc.SimpleDataSourceBean;

Context ctx = new InitialContext();
SimpleDataSourceBean ds0 = new SimpleDataSourceBean();
ds0.setUniqueResourceName("ds0");
ds0.setXaDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlXADataSource");
ds0.setXaDataSourceProperties("databaseName=gmem");
ds0.setConnectionPoolSize(10);
ctx.rebind("ds0", ds0);

SimpleDataSourceBean ds1 = = new SimpleDataSourceBean();
ds0.setUniqueResourceName("ds1");
ds0.setXaDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlXADataSource");
ds0.setXaDataSourceProperties("databaseName=gmembkp");
ds0.setConnectionPoolSize(10);
ctx.rebind("ds1", ds1);
ctx.close();

UserTransaction tx = new com.atomikos.icatch.jta.UserTransactionImp();

SessionFactory sf0 = new Configuration().configure("/hibernate0.cfg.xml").buildSessionFactory();
SessionFactory sf1 = new Configuration().configure("/hibernate1.cfg.xml").buildSessionFactory();

tx.setTransactionTimeout(60);
tx.begin();

try {
	//执行DML操作
	userTransaction.commit();
}
catch (Exception ex) {
	tx.rollback();
}

sf0.close();
sf1.close();
ds0.close();
ds1.close();</pre>
<div><span class="bh2">一些常见问题</span></div>
<div><span class="bh3">com.atomikos.icatch.RollbackException: Prepare: NO vote</span></div>
<p>详细参考：http://www.atomikos.com/Documentation/JtaProperties<br />
注意事务的超时设置：UserTransaction.setTransactionTimeout()<br />
另外，超时设置无法超过最大值限制：jta.properties中的com.atomikos.icatch.max_timeout</p>
<div><span class="bh3">Oracle 9.2.0.8.0版本下操作blob导致挂起（Halt）</span></div>
<p>10.x驱动无该问题，可能Oracle驱动的bug。INSERT时没有问题，UPDATE时，如果1个blob也没问题，2个就挂起。<br />
可以禁用Hibernate批量操作来临时解决：jdbc.batch_size=0</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/jta-with-atomikos">使用Atomikos来进行分布式事务(XA)开发</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/jta-with-atomikos/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
