RabbitMQ 2.7.0 和 2.7.1 发布
RabbitMQ 的上一个版本 (2.7.0) 带来了更好的插件管理方式、客户端的统一 URI 连接、Java 客户端中的线程安全消费者,以及一系列性能改进和错误修复。而最新版本 (2.7.1) 主要是一个错误修复版本;但它也使 RabbitMQ 与 Erlang R15B 兼容,并增强了部分管理界面。由于上一个版本没有发布博客文章,因此我将两个版本合并在这篇文章中介绍。 (这些是我个人的观点,不具有约束力;任何错误或遗漏均由我本人负责——Steve Powell。)
插件
在 2.7.0 之前,如果您想使用插件,需要将 `.ez` 文件放在 `plugins` 目录中,然后重启 broker。在此目录中找到的任何插件都会在启动时安装。这意味着两点:插件不随服务器一起提供(即使是我们支持的插件),并且安装或卸载插件涉及移动文件以及确保其他插件依赖项已安装。插件的管理不必要地麻烦。
在 2.7.0 中,我们引入了一个名为 `rabbitmq-plugins` 的命令,用于启用或禁用 `plugins` 目录中的任何插件。只需发出 `rabbitmq-plugins list` 命令即可查看存在哪些插件文件,然后使用 `rabbitmq-plugins enable <plugin-name>` 命令在下次 broker 启动时启用某个插件。无需移动任何文件——它们可以一直保留在 plugins 目录中——并且所有 RabbitMQ 插件现在都随服务器一起提供,默认情况下处于禁用状态。该命令还能理解哪些插件依赖于其他插件,并自动启用依赖项。使用和管理插件现在变得容易多了。有关更多信息,请参阅插件页面。
在 2.7.1 中,`consistent-hash-exchange` 插件的一个错误得到了修复,该错误在处理多个 exchange 时会导致消息路由错误。
通过 URI 连接
所有 RabbitMQ 客户端(.NET、Java 和 Erlang)都接受 `amqp` URI 方案进行连接。这是一个方便的一站式解决方案,允许通过单个 URI 或 String 参数提供用户名和密码、主机名和端口以及虚拟主机。例如:
amqp://guest:ghost@rabbit01.coderus.moc:5672/vhost01
有关详细信息,请参阅各个客户端 API。
Java 线程
在 2.7.0 版本中,Java 客户端的线程结构得到了显著重新设计。在此版本之前,Java 客户端的 `Consumer` 回调方法以及哪些应用程序线程可以调用 `Channel` 方法存在限制。这是由于 Java 客户端底层的线程结构将 channel 线程与回调共享。对 channel 和连接对象的锁定意味着直接从 `Consumer` 调用 `Channel` 方法会导致死锁(除了一些例外,例如确认)。`QueueingConsumer` 辅助类被提供出来,以将应用程序与其中一些问题隔离开,但代价是引入了另一个队列(在 Java 客户端中)。
通过新的线程结构,应用程序线程可以调用 channel 操作的限制大大减少,因为所有 `Consumer` 回调都在独立于 channel 的线程上执行。事实上,现在可以配置连接来管理一个专门用于回调的线程池,从而在每个 channel 内保持这些回调的执行顺序。简单的客户端应用程序可以采用默认设置,该设置提供一个小型回调线程池,而复杂的客户端可以提供自己的 `ExecutorService` 对象,从而允许它们自行创建和管理线程池的大小和行为。现在不再需要 `QueueingConsumer`,因为作为 `Consumer` 回调的结果,任何您想做的事情都可以在 `Consumer` 方法中直接完成,而不会死锁。有关更多信息,请参阅Java API 指南。
2.7.1 修复了 2.7.0 中 Java 客户端调整带来的一些小问题:我们无意中隐藏了一些 API,现在已将其恢复。还存在一些潜在的资源泄漏,我们现在已将其修复。
性能
在 2.7.0 和 2.7.1 版本中,服务器的性能得到了一系列小的改进。这些改进的范围各不相同,我在这里只能触及其中一些。
- 首先,通过使用更底层的基本文件操作,基本的 I/O 操作得到了改进。这使得某些之前通过 Erlang 进程串行化的操作可以并行发生。这消除了瓶颈,并略微加速了几个区域,包括服务器关闭。
- 一个 I/O 密集型的区域,有趣的是不受上述调整的影响,是被称为“消息存储”的部分。顾名思义,这是消息存储的地方(存储有各种原因,不仅仅是消息持久化)。RabbitMQ 不使用传统数据库,而是管理自己的消息文件存储。(传统数据库对于排队系统的性能特征几乎完全是错误的——最近使用的项很可能是最后访问的项。)消息存储是服务器中最复杂的部分之一,因为它需要响应非常快,同时又不因其相对缓慢的 I/O 操作而阻碍系统的其余部分。它的行为有点像分页系统缓存,也就是说,当消息等待写入时,如果它们随后被读取、重写甚至删除,它们可能会从写入列表中“窃取”。然而,“超越”规则与分页系统有很大不同,在这个版本中,组织方式进行了更改,允许某些删除“取消”尚未发生的存储请求。这减少了不必要的写入,因此提高了每个队列的整体吞吐量。大量测试表明,在负载下,性能有所提高,同时保持了可靠性。
- 对于拥有大量消费者的连接,尤其是那些利用率较低的消费者,处理方式欠佳。相对不活跃的消费者似乎存在一些开销。在 2.7.0 版本中,这种情况得到了改善,这意味着大量低使用率的消费者对整体性能的影响要小得多。
- 删除具有大量绑定键的队列以及具有大量绑定键的 exchange 所需的时间比我们期望的要长。在 2.7.1 版本中,这得到了加速。
HiPE 选项
Erlang 为某些平台提供了一个高性能编译器 (HiPE),可以将 Erlang 模块编译为本地代码。然而,这种编译并不总是能产生更快的系统,并且并非所有 Erlang 环境和版本都支持。在 2.7.0 版本中,我们引入了一个配置选项来使用 HiPE,并在服务器启动时自动执行重新编译。并非所有 RabbitMQ 模块都会被重新编译,只有我们认为可能从中受益的模块才会。虽然此选项会延迟启动几十秒,但它能在运行时带来显著的性能改进,这对于一些大型 RabbitMQ 安装可能至关重要。
此选项默认是禁用的,因为它可能会影响行为(尽管我们尚未检测到),并且性能改进并未在我们用户使用的所有环境中进行测试。但是,如果它对您有效,那么就使用它。如果您的 Erlang 环境不支持 HiPE,将会出现一个简短的诊断消息,并且该选项将被忽略。
我们非常想听听您对此功能的体验。
重新排队的的消息
RabbitMQ 处理 FIFO 队列。如果一切顺利,消息进入队列的顺序与它们被消费的顺序相同。但是,当消费者失败时,它接收到的某些消息可能未被确认,在这种情况下,这些消息会被重新排队以便再次传递。通过这种方式,消息的顺序可能会发生变化。在此版本之前,重新排队的的消息的顺序没有保证。
从 2.7.0 版本开始,来自单个消费者的重新排队的消息的相对顺序得以保留。因此,如果另一个消费者稍后接收它们,它们将按原始出现的顺序被消费。当然,如果同一队列上的两个或多个消费者失败,不能保证由不同消费者重新排队的消息会保留其相对顺序。但在大多数需要考虑顺序的情况下,这个保证应该足够了。
高可用性问题
2.7.1 版本包含对高可用性功能(主要在 2.7.0 中引入)的一系列修复。这些修复涉及一些内存泄漏;主队列的恢复;频繁重启导致 HA 队列失败;以及在某些情况下主节点(升为主节点)失败的问题。该区域代码的整体质量很高,但故障场景的复杂性(此功能正是为防止这些场景而设计的)使得 bug 容易隐藏。2.7.1 版本中修复的几乎所有 bug 都是由于恢复或重启事件的罕见或晦涩的组合引起的,我们有信心认为不会再有什么惊喜了。当然,高可用性并不意味着保证可用性,因此总会有我们无法恢复的情况。
其他小的改进和错误修复
- 如果您长时间运行 broker,可能会导致内部 GUID(全局唯一标识符)之一发生环绕。这显然不是预期的,而且无论如何,它实际上不会引起问题,对吧?嗯,它确实引起了! (你猜怎么着——有些人会长时间运行 broker!)我们在 2.7.1 版本中修复了这个问题。
- 管理插件界面现在显示更多关于队列长度的信息,并且 Shovel 信息也显示得更好了,此外,统计信息和 HA 备节点信息也存在一些小问题,现已修复。
- `rabbitmqctl eval <expr>` 是新增功能(在 2.7.1 中),用于在 broker 节点上评估任意 Erlang 表达式。
- .NET 客户端会话自动关闭有时可能会返回 `AlreadyClosed` 异常(它本不应该这样做)。
- STOMP 适配器未能正确支持 reply-to 队列(它们不可重用),并且在 MESSAGE 帧上可能提供多个 message-id 头部,如果 SEND 帧提供了其中一个。我们现在会检查后一种情况并拒绝 SEND。
- 一些已不再存在于 Erlang R15B 中的函数已被移除(并重写了代码),因此 RabbitMQ 现在应该可以在最新的 Erlang Release 下构建和运行。如果不行,请告知我们!
感谢您的倾听
一如既往,RabbitMQ 团队欢迎您提供好坏体验的反馈。我们鼓励您使用 rabbitmq-discuss 邮件列表。