http://www.jack-yin.com/coding/translation/activemq-in-action/1789.html
13.1通用技术
1.非持久化消息 2.在需要确保消息发送成功时使用事务来将消息分批组合
通常不考虑使用费持久化消息,因为消息可能被丢失,并且事务将消息分批也不是总是可行的,但是activemq为非持久化的消息分发采用了失效安全策略,因此只有灾难性的失败才会导致消息丢失
13.1.1消息的持久化和费持久化
JMS 允许有两种消息传输方式:持久化和非持久化,默认的传输方式是持久化,当消息生产者发送具有持久化标识的消息到代理时,消息代理将持久化消息到消息存储中,然后在发送给消费者,这是为了应对灾难性失败
或者在稍后时间发送消息给尚未激活的消息消费者
如果你正在使用非持久化消息分发,则JMS允许消息服务提供者使用最大效率发送消息给当前活动的消费者,activemq对非持久化分发还提供了额外的保证
非持久化比持久化更快的原因
1.producer 发送异步的消息,他不必等到得到receipt
2.持久化的消息存储写到磁盘需要时间
使用持久化消息的主要一个理由是在系统崩溃时防止消息丢失,正如12章所述,可以配置activemq代理的失效转移协议来缓存异步消息,并且在传输连接失效时重新发送消息(使用失效转移协议的trackMessage属性)
同样activemq还可以通过客户端或者客户端或者代理端的消息审计以防止消息重复发送,对于各种仅需可靠性的应用场景(相对确保消息能成功送达的场景)使用非持久化消息分发模式就可以满足你的需求
MessageProducer producer = session.createProducer(topic);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
13.1.2事务
当发送消息使用了事务,只有在事务的边界处(即,session。commit方法)会导致与消息代理的同步通信,因此可以通过批量化的消息生产和消息消费来改善发送持久化的性能
在创建session时可以选择开启事务控制。所谓事务控制,即将消息生成发送,或接受,消费操作放入一个事务中。但不能同时控制发送与消费这一整个过程。因为事务都是基于一个session下的操作。
如下代码即开启了事务处理:
ActiveMQSession session = connection.createSession(true,Session.CLIENT_ACKNOWLEDGE);
在事务状态下进行发送操作,消息并未真正投递到中间件,而只有进行session.commit操作之后,消息才会发送到中间件,再转发到适当的消费者进行处理。如果是调用rollback操作,则表明,当前事务期间内所发送的
消息都取消掉。此时无论commit或rollback,会重新打开一个事务。与此同时,在rollback之后,随着新的事务打开,一些持久化的消息会重新接收。原因在于当传送模式处于持久话的状态,产生的消息如若没有被及时
签收确认,则消息会被中间件持久化。此时,当客户端重新连接或新的事务开启,消息会被再次发送到客户端。为什么commit之后,不会有持久的消息重新传送呢?
原因在于commit操作会自动将为签收确认的消息进行签收确认,如果是当前接收但未签收确认的消息,都会被确认处理。因而在commit之后不会有持久化的消息出现。
public void sendTransacted() throws JMSException {
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory();
Connection connection = cf.createConnection();
connection.start();
Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
Topic topic = session.createTopic("Test.Transactions");
MessageProducer producer = session.createProducer(topic);
int count =0;
for (int i =0; i < 1000; i++) {
Message message = session.createTextMessage("message " + i);
producer.send(message);
if (i!=0 && i%10==0){
session.commit();
}
}
}
public void sendNonTransacted() throws JMSException {
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory();
Connection connection = cf.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic("Test.Transactions");
MessageProducer producer = session.createProducer(topic);
int count =0;
for (int i =0; i < 1000; i++) {
Message message = session.createTextMessage("message " + i);
producer.send(message);
}
}
下面我们逐步的介绍一些activemq特有的调优技巧以便改善性能,首先可以采用嵌入式代理,嵌入式代理减少了activemq网络传输中需要的序列化对象,以至于消息看起来相同的jvm中传输
13.1.3嵌入式代理
通常有需求要求应用程序和代理在同一个位置部署,因此所有的依赖消息的服务只有在消息代理可用的时候才可用,创建一个嵌入式代理想当容易,并且使用虚拟机连接的一个优势是通过嵌入式代理分发消息不会因网络传输而产生性能
消耗,这就使得嵌入式代理成为那些要求能对大量服务做出快速程序的理想选择
你可以使用一个监听TCP连接器来创建一个嵌入式代理,并且依然可以使用vm连接器来连接到这个代理,所有监听使用vm://<broker name>的传输连接
public void service() throws Exception{
//By default a broker always listens on vm://<broker name>
BrokerService broker = new BrokerService();
broker.setBrokerName("service");
broker.setPersistent(false);
broker.addConnector("tcp://localhost:61616");
broker.start();
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("vm://service");
Connection connection = cf.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//we will need to respond to multiple destinations - so use null
//as the destination this producer is bound to
final MessageProducer producer = session.createProducer(null);
//create a Consumer to listen for requests to service
Queue queue = session.createQueue("service.queue");
MessageConsumer consumer = session.createConsumer(queue);
consumer.setMessageListener(new MessageListener() {
public void onMessage(Message msg) {
try {
TextMessage textMsg = (TextMessage)msg;
String payload = "REPLY: " + textMsg.getText();
Destination replyTo;
replyTo = msg.getJMSReplyTo();
textMsg.clearBody();
textMsg.setText(payload);
producer.send(replyTo, textMsg);
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
默认情况下 activemq总是复制消息发送者发送的真实消息以隔离消息在通过传输时可能产生的改变以及消息被代理处理后发生的改变,消息的发送和拷贝在同一个java虚拟机中完成,如果你不打算使用保证发送状态时的原始消息你可以通过设置ActivemqConnecrtionFactory的
setCopyMessageOnSend(false);来避免复制产生的开销
public void requestor() throws Exception{
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("tcp://localhost:61616");
QueueConnection connection = cf.createQueueConnection();
connection.start();
QueueSession session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue("service.queue");
QueueRequestor requestor = new QueueRequestor(session,queue);
for(int i =0; i < 10; i++) {
TextMessage msg = session.createTextMessage("test msg: " + i);
TextMessage result = (TextMessage)requestor.request(msg);
System.err.println("Result = " + result.getText());
}
}
如果你打算从不重用发送消息,
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("vm://service");
cf.setCopyMessageOnSend(false);
13.1.4优化open wire 协议
Property name 默认值 描述
tcpNoDelayEnabled false 通知传输连接器对端启动tcp无延迟,如果启用在你通过相对
cachedEnable true 如果启用,则缓存重复的值(比如producerid和消息目的地)允许传输与缓存值对应的简单的key,
则设置较少了网络传输数据的大小,因而在网络性能不佳时可以提升性能,但是因为在缓存中查找数据会
在机器中引入cpu负载的额外开销,配置时,请考虑开销的影响
cachedSize 1024 缓存条目的最大数,
tightEncodingEnabled true 当为true时,它将实现了基于字节的更紧凑的编码&#26684;式,成果会导致消息变小和收集机能提拔,
然则如许CUP也许要付出额外的花费,这须要调和考量,若是你觉的CPU不是首要的题目的话,可以启用
该选项,反之封闭、
tcp://hostA:61617?wireFormat.cacheSize=2048
13.5优化tcp传输连接器
在activemq中使用最广泛的连接器是tcp传输连接器,有两个直接影响tcp传输器的性能
1.socketBufferSizetcp传输器用于发送接收数据的缓冲区大小,通常越大越好
tcpNoDelay默认false通常tcp套接字缓存即将被发送的小尺寸数据包
url=tcp://hostA:61617?tcpNoDelay=true
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(url);
13.2优化message producer
13.2.1异步发送
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory();
cf.setUseAsyncSend(true)
13.2.2生产者流量控制
生产者流量控制允许消息代理(message broker)在资源变慢的时候降低速度,使用场景:消费者比生产者慢, 内存中大量的message等着被dispatch
生产者必须等着收到这里有空间给更多的message的通知,
生产者流量控制是默认开启的,但是你必须明确的指出异步publish数量
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory();
cf.setProducerWindowSize(1024000);
在节点destinationPolicy配置策略,可以对单个或者所有的主题和队列进行设置,使用流量监控,当消息达到memoryLimit的时候,ActiveMQ会减慢消息的产生甚至阻塞,destinationPolicy的配置如下
<destinationPolicy>
<policyMap>
<policyEntries>
<policyEntry topic=">" producerFlowControl="true" memoryLimit="1mb">
<pendingSubscriberPolicy>
<vmCursor />
</pendingSubscriberPolicy>
</policyEntry>
</policyEntries>
</policyMap>
</destinationPolicy>
producerFlowControl表示是否监控流量,默认为true,如果设置为false,消息就会存在磁盘中以防止内存溢出;memoryLimit表示在producerFlowControl=”true”的情况下,消息存储在内存中最大量,当消息达到这个值时,ActiveMQ会减慢消息的产生甚至阻塞。policyEntry的属性参考:http://activemq.apache.org/per-destination-policies.html
当producer发送的持久化消息到达broker之后,broker首先会把它保存在持久存储中。接下来,如果发现当前有活跃的consumer,如果这个consumer消费消息的速度能跟上producer生产消息的速度,那么ActiveMQ会直接把消息传递给broker内部跟这个consumer关联的dispatch queue;如果当前没有活跃的consumer或者consumer消费消息的速度跟不上producer生产消息的速度,那么ActiveMQ会使用Pending Message Cursors保存对消息的引用。在需要的时候,Pending Message Cursors把消息引用传递给broker内部跟这个consumer关联的dispatch queue。以下是两种Pending Message Cursors:
VM Cursor:在内存中保存消息的引用。
File Cursor:首先在内存中保存消息的引用,如果内存使用量达到上限,那么会把消息引用保存到临时文件中。
在缺省情况下,ActiveMQ 会根据使用的Message Store来决定使用何种类型的Message Cursors,但是你可以根据destination来配置Message Cursors。
对于topic,可以使用的pendingSubscriberPolicy 有vmCursor和fileCursor。可以使用的PendingDurableSubscriberMessageStoragePolicy有
vmDurableCursor 和 fileDurableSubscriberCursor;对于queue,可以使用的pendingQueuePolicy有vmQueueCursor 和 fileQueueCursor。
2、存储设置
设置消息在内存、磁盘中存储的大小,配置如下:
Java
<systemUsage>
<systemUsage>
<memoryUsage sendFailIfNospace="true">
<memoryUsage limit="20 mb"/>
</memoryUsage>
<storeUsage>
<storeUsage limit="1 gb"/>
</storeUsage>
<tempUsage>
<tempUsage limit="100 mb"/>
</tempUsage>
</systemUsage>
</systemUsage>
<systemUsage>
<systemUsage>
<memoryUsage sendFailIfNospaceAfterTimeout="true">
<memoryUsage limit="20 mb"/>
</memoryUsage>
<storeUsage>
<storeUsage limit="1 gb"/>
</storeUsage>
<tempUsage>
<tempUsage limit="100 mb"/>
</tempUsage>
</systemUsage>
</systemUsage>
memoryUsage表示ActiveMQ使用的内存,这个值要大于等于destinationPolicy中设置的所有队列的内存之和。
storeUsage表示持久化存储文件的大小。
tempUsage表示非持久化消息存储的临时内存大小。
13.3优化message consumer
13.3.1消息预取限制
如果客户端处理很慢的话,Broker会在之前发送消息的反馈之前,继续发送新的消息到客户端。如果客户端依旧很慢的话,
没有得到确认反馈的消息会持续增长。在这种情况下,Broker有可能会停止发送消息给消费者。当未被反馈的消息达到了
prefetch limit设置的数字时,Broker将会停止给消费者发送新的消息。除非消费者开始给与反馈,否则得不到任何消息。
Default Prefetch Limit(默认预取限制):不同的消费者类型有不同的默认设置,具体设置如下:
Queue consumer:默认1000
如果你使用一组消费者进行分散工作量的话(一个Queue对应多个消费者),典型的你应该把数字设置的小一些。如果一个消费者被允许可以聚集大量的未被确认的消息的话,
会导致其它的消费者无事可做。同时,如果这个消费者出错的话,会导致大量的消息不能被处理,直到消费者恢复之前。
Queue browser:默认500
Topic consumer:默认32766
默认值32766是数字short的最大值,也是预取限制的最大值。
Durable topic subscriber:默认100
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory();
Properties props = new Properties();
props.setProperty("prefetchPolicy.queuePrefetch", "1000");
props.setProperty("prefetchPolicy.queueBrowserPrefetch", "500");
props.setProperty("prefetchPolicy.topicPrefetch", "60000");
props.setProperty("prefetchPolicy.durableTopicPrefetch", "100");
cf.setProperties(props);
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic("test.topic?consumer.prefetchSize=32766");