在本章中,我们将讨论如何调整 Apache ActiveMQ Artemis 以获得最佳性能。

1. 调整持久性

  • 为了在使用持久消息时从 Apache ActiveMQ Artemis 获得最佳性能,建议使用文件存储。Apache ActiveMQ Artemis 也支持 JDBC 持久性,但与本地磁盘相比,将数据持久化到数据库存在性能成本。

  • 将消息日志放在单独的物理卷上。如果磁盘与其他进程共享,例如事务协调器、数据库或其他也从该磁盘读取和写入的日志,那么这可能会大大降低性能,因为磁盘头可能会在不同文件之间四处跳跃。仅追加日志的优点之一是磁盘头移动量最小化 - 如果磁盘共享,则此优点会被破坏。如果您使用分页或大型消息,请确保它们也最好放在单独的卷上。

  • 日志文件最小数量。将 journal-min-files 设置为适合您的平均可持续速率的文件数量。此数字代表日志文件池的下限。

  • 设置日志文件池的上限。(journal-min-files 是下限)。将 journal-pool-files 设置为一个数字,该数字代表您的最大预期负载附近的某个值。如果需要,日志将溢出池,但在可能的情况下会缩小回上限。这允许文件重用,而不会占用比需要更多的磁盘空间。如果您发现日志数据目录经常创建新文件,即正在持久化大量数据,则需要增加 journal-pool-size,这样日志将重用更多文件而不是创建新的数据文件,从而提高性能。

  • 日志文件大小。日志文件大小应与磁盘上的气缸容量对齐。默认值 10MiB 在大多数系统上应该足够。

  • 使用 ASYNCIO 日志。如果使用 Linux,请尝试将您的日志类型保持为 ASYNCIOASYNCIO 比 Java NIO 扩展性更好。

  • 调整 journal-buffer-timeout。可以增加超时以增加吞吐量,但会牺牲延迟。

  • 如果您运行的是 ASYNCIO,您可能可以通过增加 journal-max-io 来获得更好的性能。如果您运行的是 NIO,请不要更改此参数。

  • 如果您 100% 确定不需要断电持久性保证,请禁用 journal-data-sync 并使用 NIOMAPPED 日志:您将在具有进程故障持久性保证的写入中获得巨大的性能提升。

2. 调整 JMS

如果您使用的是 JMS API,则有一些区域可以进行一些调整。

  • 禁用消息 ID。如果不需要消息 ID,请在 MessageProducer 类上使用 setDisableMessageID() 方法禁用消息 ID。这会减小消息的大小,还会避免创建唯一 ID 的开销。

  • 禁用消息时间戳。如果不需要消息时间戳,请在 MessageProducer 类上使用 setDisableMessageTimeStamp() 方法禁用消息时间戳。

  • 避免使用 ObjectMessageObjectMessage 很方便,但也有成本。ObjectMessage 的主体使用 Java 序列化将其序列化为字节。即使是小型对象的 Java 序列化形式也很冗长,因此在网络上占用大量空间,而且 Java 序列化速度比自定义编组技术慢。只有在您确实无法使用其他消息类型时,才使用 ObjectMessage,即如果您在运行时之前真的不知道有效负载的类型。

  • 避免使用 AUTO_ACKNOWLEDGEAUTO_ACKNOWLEDGE 模式需要从服务器发送对客户端接收到的每条消息的确认,这意味着网络上的流量更多。如果可以,请使用 DUPS_OK_ACKNOWLEDGE 或使用 CLIENT_ACKNOWLEDGE 或事务性会话,并将许多确认批量处理成一次确认/提交。

  • 避免使用持久消息。默认情况下,JMS 消息是持久的。如果您确实不需要持久消息,请将其设置为非持久。持久消息在将其持久化到存储时会产生更多开销。

  • 在单个事务中批量发送或确认许多消息。Apache ActiveMQ Artemis 只需要在提交时进行一次网络往返,而不是在每次发送或确认时都进行。

3. 其他调优

在 Apache ActiveMQ Artemis 中还有其他几个地方可以进行一些调整。

  • 使用异步发送确认。如果您需要非事务性地发送持久消息,并且您需要保证在调用 send() 返回时它们已到达服务器,请不要将持久消息设置为阻塞发送,而是使用异步发送确认在单独的流中获取发送的确认,请参阅 发送和提交的保证,以获取有关此内容的更多信息。

  • 使用预确认模式。在预确认模式下,消息在发送到客户端之前被确认。这减少了网络上的确认流量。有关此内容的更多信息,请参阅 额外的确认模式

  • 禁用安全性。通过在 broker.xml 中将 security-enabled 参数设置为 false,您可以获得少许性能提升。

  • 禁用持久性。如果您不需要消息持久性,请通过在 broker.xml 中将 persistence-enabled 设置为 false 来完全关闭它。

  • 延迟同步事务。在 broker.xml 中将 journal-sync-transactional 设置为 false 可以让您获得更好的事务性持久性能,但会以在故障时可能丢失一些事务为代价。请参阅 发送和提交的保证,以获取更多信息。

  • 延迟同步非事务性。在 broker.xml 中将 journal-sync-non-transactional 设置为 false 可以让您获得更好的非事务性持久性能,但会以在故障时可能丢失一些持久消息为代价。请参阅 发送和提交的保证,以获取更多信息。

  • 非阻塞发送消息。在 jms 配置(如果您使用的是 JMS 和 JNDI)或直接在 ServerLocator 中将 block-on-durable-sendblock-on-non-durable-send 设置为 false。这意味着您不必等待每个发送消息的整个网络往返。请参阅 发送和提交的保证,以获取更多信息。

  • 如果您有非常快的消费者,您可以增加 consumer-window-size。这实际上会禁用消费者流量控制。

  • 使用核心 API 而不是 JMS。使用 JMS API,您的性能会略低于使用核心 API,因为所有 JMS 操作都需要在服务器处理之前转换为核心操作。如果使用核心 API,请尽量使用尽可能多地接受 SimpleString 的方法。SimpleString 与 java.lang.String 不同,它不需要在写入网络之前进行复制,因此如果您在调用之间重新使用 SimpleString 实例,则可以避免一些不必要的复制。

  • 如果您使用的是 Spring 等框架,请在代理端永久配置目标,并在客户端启用 cacheDestinations。有关此内容的更多信息,请参阅 设置目标缓存

4. 调整传输设置

  • TCP 缓冲区大小。如果您拥有快速的网络和快速的机器,您可以通过增加 TCP 发送和接收缓冲区大小来获得性能提升。有关此内容的更多信息,请参阅 配置传输

    请注意,某些操作系统(如更高版本的 Linux)包含 TCP 自动调整,手动设置 TCP 缓冲区大小可能会阻止自动调整工作,实际上会降低您的性能!

  • 增加服务器上文件句柄的限制。如果您期望服务器上有很多并发连接,或者客户端正在快速打开和关闭连接,那么您应该确保运行服务器的用户有权创建足够的文件句柄。

    这因操作系统而异。在 Linux 系统上,您可以在 /etc/security/limits.conf 文件中增加允许打开的文件句柄数量,例如,添加以下行

    serveruser   soft  nofile  20000
    serveruser   hard  nofile  20000

    这将允许用户 serveruser 打开多达 20000 个文件句柄。

  • 对于非常小的消息,使用 batch-delay 并将 direct-deliver 设置为 false 以获得最佳吞吐量。Apache ActiveMQ Artemis 在 broker.xml 中预配置了连接器/接受器对 (netty-throughput) 以及 activemq-jms.xml 中的 JMS 连接工厂 (ThroughputConnectionFactory),它们可以用于提供最佳吞吐量,特别是对于小型消息。有关此内容的更多信息,请参阅 配置传输

5. 调整 VM

我们强烈建议您使用最新的 Java JVM 以获得最佳性能。我们在内部使用 Sun JVM 进行测试,因此这些调整中的一些不适用于来自其他提供商(例如 IBM 或 JRockit)的 JDK。

  • 内存设置。尽可能多地为服务器提供内存。Apache ActiveMQ Artemis 可以通过使用分页(在 分页 中描述)在低内存中运行,但如果它可以在 RAM 中运行所有队列,这将提高性能。您需要的内存量将取决于队列的大小和数量以及消息的大小和数量。使用 JVM 参数 -Xms-Xmx 设置服务器可用的 RAM。我们建议将它们设置为相同的较高的值。

    在高负载期间,Artemis 可能会生成和销毁大量对象。这会导致过时对象堆积。为了降低内存不足并导致完全 GC(这可能会引入暂停和意外行为)的可能性,建议 JVM 的最大堆大小 (-Xmx) 至少设置为代理的 global-max-size 的 5 倍。例如,在代理处于高负载并以 global-max-size 为 1GB 运行的情况下,建议将最大堆大小设置为 5GB。

6. 避免反模式

  • 重用连接/会话/消费者/生产者。我们看到的最常见的错误消息模式是用户为他们发送的每条消息或他们消费的每条消息创建一个新的连接/会话/生产者。这是一种对资源的浪费。这些对象需要时间创建,并且可能涉及多个网络往返。始终重用它们。

    Spring 的 JmsTemplate 众所周知会使用这种错误模式。它只能与连接池安全地使用(例如,在使用 JCA 的 Java EE 应用程序服务器中),即使这样,也应该仅用于发送消息。它不能安全地用于同步消费消息,即使使用连接池也是如此。如果您需要连接池,请查看 this,它从 ActiveMQ 代码库中分叉到自己的项目中,并完全支持 JMS 2。

  • 避免使用胖消息。冗长的格式(如 XML)在网络上占用大量空间,因此性能会受到影响。如果可以,请避免在消息主体中使用 XML。

  • 不要为每个请求创建临时队列。这种常见的错误模式涉及临时队列请求-响应模式。使用临时队列请求-响应模式,消息将发送到目标,并且将使用本地临时队列的地址设置 reply-to 标头。当接收者收到消息时,他们会处理它,然后将响应发送回 reply-to 中指定的地址。这种模式中常见的一个错误是在发送的每条消息上创建一个新的临时队列。这会大大降低性能。相反,临时队列应该被重用于许多请求。

  • 不要为了使用而使用消息驱动的 Bean。一旦开始使用 MDB,与直接的消息消费者相比,每个接收到的消息的代码路径都会大大增加,因为执行了大量额外的应用程序服务器代码。问问自己,你真的需要 MDB 吗?你可以使用普通的消息消费者完成同样的任务吗?

7. 故障排除

7.1. UDP 不工作

在某些情况下,用于发现的 UDP 可能无法正常工作。典型情况包括

  1. 节点位于防火墙之后。如果您的节点位于不同的机器上,那么防火墙可能会阻止多播。您可以通过禁用每个节点的防火墙或添加相应的规则来测试这一点。

  2. 您使用的是家庭网络或位于网关之后。通常,家庭网络会将任何 UDP 流量重定向到互联网服务提供商,然后由 ISP 丢弃或丢失。要解决此问题,您需要在防火墙/网关中添加一条路由,将任何多播流量重定向回本地网络。

  3. 所有节点都位于一台机器上。如果是这种情况,它与第 2 点类似,相同的解决方案应该可以解决它。或者,您可以向环回接口添加多播路由。在 Linux 上,命令如下:

    # you should run this as root
    route add -net 224.0.0.0 netmask 240.0.0.0 dev lo

    这将把任何指向 224.0.0.0 的流量重定向到环回接口。即使您没有网络,这也将起作用。在 Mac OS X 上,命令略有不同

    sudo route add 224.0.0.0 127.0.0.1 -netmask 240.0.0.0