## 13.1 ActiveMQ通用调优技术

### 13.1 ActiveMQ通用调优技术

You can do two simple things to improve JMS messaging performance: use nonpersistentmessaging, or if you really need guaranteed messaging, then use transactions tobatch up large groups of messages. Usually nonpersistent message delivery isn’t a considerationunless you don’t care that a message will be lost (for example, in real-timedata feeds, since the status will be sent repeatedly within such a short period of time),and batching messages in transactions won’t always be applicable. But ActiveMQ incorporatesfailsafes for reliable delivery of nonpersistent messages so that only catastrophicfailure would result in message loss. In this section we’ll explain why nonpersistentmessage delivery and batching messages are faster, and why they could beapplicable to use in your application if you don’t need to absolutely guarantee thatmessages will never be lost. 有两种简单方式可以提升JMS消息性能:使用非持久化消息,或者在需要确保消息发送成功时使用事务来将消息分批组合.通常不考虑使用非持久化消息分发除非你不在乎消息可能会丢失(比如,一个实时的数据源,因为状态数据在很短的时间内会重复发送),并且使用事务将消息分批也不总是可行的.但是ActiveMQ为非持久化的消息分发采用了失效安全策略,因此只有灾难性的失败才会导致消息丢失.本节中我们将解释为什么非持久化消息和消息分批会更快,以及为什么在不需要绝对保证消息不会丢失的情况下,可以将它们可应用在你的程序中.

#### 13.1.1 持久化消息 VS 非持久化消息

The JMS specification allows for two message delivery modes: persistent and nonpersistentdelivery. The default delivery mode is persistent. When a producer sends a messageto the broker that’s marked as persistent, the message broker will always persist itto its message store before dispatching it to a consumer. This is to guard against catastrophicfailure, or for later delivery to consumers who might not yet be active. JMS规范允许两种消息分发模式:持久化分发和非持久化分发.默认的分发模式是持久化分发.当消息生产者发送具有持久化标识的消息到代理时,消息代理将持久化消息到消息存储中然后在发送消息给消费者.这样是为了应对在灾难性失败或者在稍后时间再发送消息给目前尚未激活的消息消费者.
If you’re using nonpersistent delivery, then the JMS specification allows themessaging provider to make best efforts to deliver the message to currentlyactive message consumers. ActiveMQ provides additional reliability here, whichis covered later in this section. Nonpersistent messages are significantlyfaster than persistent messages— there are two reasons for this:

? Messages are sent asynchronously from the message producer, so the producerdoesn’t have to wait for a receipt from the broker. This is shown in figure13.1.
? Persisting messages to the message store (which typically involves writing todisk) is slow compared to messaging over a network.

(1) 消息生产者通过异步方式发送消息,所以生产者无需等待代理的回执.如图13.1所示.
(2) 持久化消息保存到消息存储(通常需要写磁盘数据)比在网络中传输消息要慢.
The main reason for using persistence is to negate message loss in the case of a systemoutage. But as we saw in chapter 12, ActiveMQ can be configured to prevent this byconfiguring the failover transport to cache asynchronous messages to resend again ona transport failure (using the trackMessages failover transport property). ActiveMQalso prevents duplicate messages by using message auditing at both the client and theActiveMQ broker. So for usage scenarios where only reliability is required (as opposedto guaranteed message delivery), a nonpersistent delivery mode can meet your needs. 使用持久化消息的一个主要理由是在系统崩溃时防止消息丢失.但是正如12章中所述,可以配置ActiveMQ代理的失效转移协议来缓存异步消息,并且在传输连接器失效时重新发送消息(使用失效转移协议的trackMessages属性).同样,ActiveMQ还可以通过客户端或代理端的消息审计以防止消息重复发送.因此,对于各种仅需保证可靠性的应用场景(相对于确保消息能成功送达的场景)来说使用非持久化消息分发模式即可满足你的需求.
As by default the message delivery mode is persistent, you have to explicitly set thedelivery mode on the MessageProducer to send nonpersistent messages as can be seenin the following listing.
Listing 13.1 Persistent message delivery

  MessageProducer producer = session.createProducer(topic);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
We’ve seen why there’s such a big performance difference between persistent andnonpersistent delivery of messages, and the steps that ActiveMQ takes to improve reliabilityof nonpersistent messages. The benefit of reliable message delivery allows nonpersistentmessages to be used in many more cases than would be typical of a JMSprovider. 我们已经看到为何使用用持久化消息分发和非持久化消息分发会有那么大的性能差异,并且了解了ActiveMQ为改善非持久化消息分发的可靠性而采取的措施.保证消息分发可靠性的好处是允许非持久化消息不仅可用于典型的JSM提供者,而且可以用于更多其他场景中.
Having covered nonpersistent messages, let’s move on to the second generalizedtechnique for improving performance of delivering messages in your application—batching messages together. The easiest way to batch messages is to use transactionboundaries, which are explained next. 了解了非持久化消息之后,让我把目光转向程序中可用的第二种通用的消息分发调优技术–消息分批.使用消息分批最简单的方式是使用事务边界,这将在下一节中介绍.

#### 13.1.2 事务

When you send messages using a transaction, only the transaction boundary (theSession.commit() method) results in synchronous communication with the messagebroker. So it’s possible to batch up the production and/or the consumption of messagesto improve performance of sending persistent messages. An example of this isshown next.
Listing 13.2 Transacted and nontransacted example

  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();
//create a default session (no transactions)
Session session = connection.createSession(false, Session.AUTO_ACKNOWELDGE);
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);
}
}

So we’ve covered some of the easy pickings in terms of performance, use of nonpersistentmessaging where possible, and now use of transaction boundaries for persistentmessages if it makes sense for your application. 至此我们已经了解了性能调优的几个简单方式:如果可行的化使用非持久化消息,以及上面介绍的,在合适的情况下,在应用程序中使用事务边界发送持久化消息.
We’re now  going to  start (slowly)  delving into  some ActiveMQ  specifics fortechniques that can  aid performance. The  first is to  use an embedded  broker.Embedded brokers  cut down  on the  amount of  serialization and network trafficthat ActiveMQ uses, as messages can be passed around in the same JVM. 下面我们将(逐步地)介绍一些ActiveMQ特有的调优技术以便改善性能.首先可以使用嵌入式代理.嵌入式代理减少了ActiveMQ网络传输中需要序列化的数量,以至于消息看起来就相当于在同一个JVM中传输的.

#### 13.1.3嵌入式代理

It’s often a requirement to co-locate applications with a broker, so that any servicethat’s dependent on a message broker will only be available at the same time as themessage broker, as shown in figure 13.2. Creating an embedded broker is straightforward,but one of the advantages of using the VM transport is that messages deliveredthrough a broker don’t incur the cost of being serialized on the wire to be transportedacross the network, making it ideal for applications that need to service lots ofresponses quickly. 通常有的需求要求应用程序和代理在同一位置(译注:虚拟机)部署,因此所有依赖消息代理的服务只有在消息代理可用的时候才可用,如图13.2所示.创建一个嵌入式代理相当容易,并且使用虚拟机传输连接器的一个优势是通过嵌入式代理分发的消息不会因网络传输要求的序列化而产生性能损耗,这就使得嵌入式代理成为那些要求能对大量服务做出快速响应的程序的理想选择.
You can create an  embedded broker with a  transport connector to listen  to TCPconnections but still connect to it using the VM transport. By default, a brokeralways listens for  transport connections on  vm://<broker name>. The  followinglisting is an example of setting up a service using an embedded broker to listenfor requests on a queue named service.queue.
Listing 13.3 Creating a queue service

  BrokerService broker = new BrokerService();
broker.setBrokerName("service");
broker.setPersistent(false);
broker.start();

ActiveMQConnectionFactory cf =  new ActiveMQConnectionFactory("vm://service");
cf.setCopyMessageOnSend(false);
Connection connection = cf.createConnection();
connection.start();

Session session =  connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

final MessageProducer producer = session.createProducer(null);
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();
textMsg.clearBody();
}
catch (JMSException e)
{
e.printStackTrace();
}
}
});

You can test out this service with a javax.jms.QueueRequestor that connects to theservice’s embedded broker via the TCP transport connector, as shown in the followinglisting.
Listing 13.4 Connecting a QueueRequestor

  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());
}
As an aside, ActiveMQ by default will always copy the real message sent by a messageproducer to insulate the producer from changes to the message as it passes throughthe broker and is consumed by the consumer, all in the same Java Virtual Machine. Ifyou intend to never reuse the sent message, you can reduce the overhead of this copyby setting the copyMessageOnSend property on the ActiveMQConnectionFactory tofalse, as shown next.
Listing 13.5 Using the setCopyMessageOnSend method

ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory();
cf.setCopyMessageOnSend(false);
We’ve looked at some relatively easy-to-implement techniques to improve messagingperformance. Using an embedded broker co-located with an application is a relativelytrivial change to make. The performance gains and atomicity of the service co-locatedwith its broker can be an attractive architectural change, too. 我们已经了解了ActiveMQ的一些比较容易实现的提升性能的技术.使用一个和应用程序部署在一起的嵌入式代理是一种很微小的结构变化.获取的性能提升和与代理一起部署的服务的原子性也是这种架构变化有吸引力的原因.
Having gone through  some of the  easier “quick wins,”  we’re going to  startmoving into some  more difficult configuration  areas. So the  next section willtouch on the OpenWire  protocol and list some  parameters you can tune  to boostthe performance of your messaging applications. These are dependent on both  thehardware and the type of network you use. 在了解一些相对容易的”快赢”优化配置后,我们将开始了解一些更困难的配置.因此,下一节中我们将着眼OpenWire协议,并列出一些可调整参数以便你提高消息程序的性能.这些优化调整依赖硬件和网络类型.

#### 13.1.4 优化OpenWire协议

It’s worth covering some of the options available on the OpenWire protocol. TheOpenWire protocol is the binary format used for transporting commands over a transport(such as TCP) to the broker. Commands include messages and message acknowledgements,as well as management and control of the broker. Table 13.1 shows someOpenWire wire format parameters that are relevant to performance. OpenWire协议的一些选项参数值得关注.OpenWire协议是一些二进制格式规范,传输连接器(例如)使用这种格式的传输命令传输数据到代理.这些命令包含消息相关命令和消息确认相关命令,以及代理相关的管理和控制命令.表13.1展示了一些OpenWire协议与性能调整有关的包装格式参数.
Table 13.1 OpenWire tuning parameters

Property
name
Default
value
Description
tcpNoDelay
Enabled
false Provides a hint to the peer transport to enable/disable tcpNoDelay. If this is set, it may improve performance where you’re sending lots of small messages across a relatively slow network.
cacheEnabled true Commonly repeated values (like producerId and
destination) are cached, enabling short keys to be passed instead. This decreases message size, which can have a positive impact on performance where network performance is relatively poor. The cache lookup involved does add overhead to CPU load on both the clients and the broker machines, so take this into account.
cacheSize 1024 Maximum number of items kept in the cache. This shouldn’t be bigger than Short.MAX_VALUE/2. The larger the cache, the better the performance where caching is enabled. But one cache will be used with every transport connection, so bear in mind the memory overhead on the broker, especially if it’s loaded with a large number of clients.
tightEncoding
Enabled
true A CPU-intensive way to compact messages. We recommend disabling this if the broker starts to consume all the available CPU.

tcpNoDelay
Enabled
false 通知传输连接器的对端是否启用 tcpNoDelay(tcp 无延迟).如果启用,

cacheEnabled true 如果启用,则缓存重复的值(比如producerId和消息目的地),允许传输与缓存值对应的简短的key.该设置减小了传输数据的大小,因而在网络性能不佳时可以提升性能.但是因为在缓存中查找数据会同时在客户端和代理所在的机器中引入CPU负载的额外开销,配置时请考虑中引入开销的影响.
cacheSize 1024 缓存条目的最大数量.该值不能超过Short.MAX_VALUE值的二分之一.当开启缓存时,该值设置的越大性能越好.但是因为每一个传输连接都独立使用一个缓存,所以需要在代理端考虑因缓存带来的额外开销,特别是当有大量的客户端连接到代理时.
tightEncoding
Enabled
true 以CPU敏感的方式压缩消息.我们推荐在代理打算使用所有可用CPU时将该选项设置为停用.
You can add  these parameters to  the URI used  to connect to  the broker in thefollowing  way. The  following listing  demonstrates disabling  tight encoding,using the tightEncodingEnabled parameter.
Listing 13.6 Enabling tight encoding on the wire format

  String uri = "failover://(tcp://localhost:61616?wireFormat.cacheEnabled=false&wireFormat.
tightEncodingEnabled=false)";
  ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(url);
cf.setAlwaysSyncSend(true);
These parameters are dependent on the type of application, type of machine(s) usedto run the clients and the broker, and type of network used. Unfortunately this isn’t anexact science, so some experimentation is recommended. 如何配置上述参数取决与应用程序的类型,运行客户端和代理的机器类型,以及使用的网络类型.不幸的是,不是所有的优化配置都有科学依据,因此一些优化经验值得推荐.
As we’ve  lightly introduced  some of  the tuning  parameters available  on theOpen-Wire  protocol, in   the next  section we’ll  look at  some of  the tuningparameters available on the TCP transport. 至此,我们已经简要的介绍了一些OpenWire协议的优化参数.下一节中,我们将看到一些TCP传输连接器的优化配置参数.

#### 13.1.5 优化TCP传输连接器

The most commonly used transport for ActiveMQ is the TCP transport. Two parametersdirectly affect performance for this transport:

? socketBufferSize—The size of the buffers used to send and receive data overthe TCP transport. Usually the bigger the better (though this is operating systemdependent, so it’s worth testing!). The default value is 65536, which is thesize in bytes.

? tcpNoDelay—The default is false. Normally a TCP socket buffers up smallpieces of data before being sent. When you enable this option, messages will besent as soon as possible. Again, it’s worth testing this out, as whether this boostsperformance can be operating system dependent.

ActiveMQ中使用最广泛的连接器是TCP传输连接器.有两个直接影响TCP传输连接器的性能,它们是:
(1)socketBufferSize–TCP传输连接器用于发送和接收数据的缓冲区大小.通常该参数设置的越大越好(尽管这个最大值收到操作系统限制,但是可以去测试).默认值为65536,单位是byte.
(2)tcpNoDelay–默认值为false.通常TCP套接字缓存即将被发送的小尺寸数据包.当启用这个参数时,消息会被尽快发送(译注:不缓冲).同样可以测试这个配置,因为修改这个参数是否能提升性能还和操作系统有关.
Here’s an example transport URI where the tcpNoDelay property is enabled:
Listing 13.7 Using the tcpNoDelay setting to tune the TCP transport

  String url = "failover://(tcp://localhost:61616?tcpNoDelay=true)";
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(url);
cf.setAlwaysSyncSend(true);
We’ve covered some general techniques to improve performance at the applicationlevel, and looked at tuning the wire protocol and the TCP transport. In the next twoparts of this chapter, we’ll look at tuning message producers and then message consumers. 至此,我们已经了解了一些通用的应用程序级别的性能调优技术,并且了解了优化wire协议和TCP协议的相关技术.后面两个章节我们将了解消息生产者和消费者的优化.
ActiveMQ is flexible in its configuration, and its producers can be configuredto optimize their message exchanges with the broker, which can boost throughputconsiderably. ActiveMQ的配置非常灵活,并且其消息生产者可以被配置以优化其与代理之间的消息交换,这种优化可以显著提高吞吐量.

微信赞赏  支付宝赞赏