跳到主要内容

RabbitMQ 2.7.0 和 2.7.1 发布

·阅读 9 分钟
Steve Powell

RabbitMQ 的上一个版本 (2.7.0) 带来了更好的插件管理方式,客户端一键式 URI 连接,Java 客户端中的线程安全消费者,以及许多性能改进和错误修复。 最新版本 (2.7.1) 本质上是一个错误修复版本;不过它也使 RabbitMQ 与 Erlang R15B 兼容,并增强了一些管理界面。上一个版本没有发布博客文章,因此我将这两个版本都包含在这个文章中。(这些是我个人的评论,不具约束力;疏漏或错误完全由我个人负责——Steve Powell。)

插件

在 2.7.0 之前,如果要使用插件,则需要将一个 .ez 文件放置在 plugins 目录中,然后重启代理。此目录中找到的任何插件都会在启动时安装。这意味着两件事:插件不会随服务器一起提供(即使是我们支持的插件),并且安装或卸载插件需要移动文件以及确保安装了其他插件依赖项。插件的管理变得不必要的混乱。

在 2.7.0 中,我们引入了一个命令 rabbitmq-plugins,用于启用或禁用 plugins 目录中的任何插件。只需发出命令 rabbitmq-plugins list 即可查看有哪些插件文件,并使用 rabbitmq-plugins enable <plugin-name> 在下次代理启动时使用其中一个。无需移动文件——它们可以一直保留在 plugins 目录中——并且现在所有 rabbit 插件都随服务器一起提供,默认情况下处于禁用状态。该命令还了解哪些插件依赖于哪些其他插件,并自动启用依赖项。使用和管理插件现在变得容易多了。有关更多信息,请参见插件页面

在 2.7.1 中,修复了 consistent-hash-exchange 插件在处理多个交换机时错误路由消息的问题。

通过 URI 连接

所有 rabbit 客户端(.NET、Java 和 Erlang)客户端都接受用于连接的 amqp URI 方案。这是一种方便的一站式服务,允许通过单个 URI 或字符串参数提供用户名和密码、主机名和端口以及虚拟主机。例如

amqp://guest:ghost@rabbit01.coderus.moc:5672/vhost01

有关详细信息,请参阅各个客户端 API。

Java 线程

在 2.7.0 版本中,Java 客户端的线程结构已进行了重大重新设计。在此版本之前,Java 客户端的 Consumer 回调方法中存在一些限制,并且在哪些应用程序线程可以调用 Channel 方法方面也存在限制。这些是由于 Java 客户端的基础线程结构将通道线程与回调共享造成的。通道和连接对象上的锁意味着从 Consumer 直接调用 Channel 方法会导致死锁(有一些例外,例如确认)。QueueingConsumer 辅助类可用以使应用程序与其中一些问题隔离开来,但代价是引入了另一个队列(在 Java 客户端中)。

使用新的线程结构,对哪些应用程序线程可以调用通道操作的限制要少得多,因为所有 Consumer 回调都在与通道分开的线程上执行。实际上,现在可以配置连接以管理专门用于回调的线程池,并保留每个通道内这些回调的执行顺序。简单的客户端应用程序可以采用提供少量回调线程的默认设置,而复杂的客户端可以提供自己的 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 中,这一点得到了改进,这意味着拥有大量低使用率的消费者对整体性能的影响要小得多。
  • 具有大量绑定的队列和具有大量绑定的交换机的删除花费的时间比我们希望的要长。这在 2.7.1 中得到了加速。

HiPE 选项

Erlang 为某些平台提供了一个高性能编译器 (HiPE),Erlang 模块可以编译成原生代码。但是,此编译并不总是产生更快的系统,并且并非所有 Erlang 环境和版本都支持它。在 2.7.0 中,我们引入了配置选项以使用 HiPE,并在服务器启动时自动执行重新编译。并非每个 rabbit 模块都会重新编译,只有我们确定可能从这种处理中受益的模块才会重新编译。尽管此选项会使启动延迟几十秒,但它在运行时会产生显着的性能改进,这对某些较大的 rabbit 安装至关重要。

此选项默认情况下处于禁用状态,因为它实际上可能会影响行为(我们尚未检测到这一点),并且性能改进未在我们用户使用的所有环境中进行测试。但是,如果它对您有用,请使用它。如果您的 Erlang 环境不支持 HiPE,则会显示一条简短的诊断消息,并且会忽略此选项。

我们非常希望了解您使用此功能的体验。

重新入队的消息

RabbitMQ 处理 FIFO 队列。如果一切顺利,消息进入队列的顺序与它们被消费的顺序相同。但是,当消费者失败时,它接收到的某些消息可能尚未确认,在这种情况下,这些消息将重新入队,以便可以再次传递。这样,消息看起来像是重新排序的。在此版本之前,没有保证重新入队消息的顺序。

从 2.7.0 开始,单个消费者重新入队消息的相对顺序得以保留。因此,如果另一个消费者稍后接收它们,它们将按照最初出现的顺序被消费。当然,如果同一队列上的两个或多个消费者失败,则不能保证由不同消费者重新入队的消息会保持其相对顺序。但在大多数情况下,如果顺序很重要,则此保证应该足够了。

高可用性问题

2.7.1 中包含了许多针对高可用性功能(主要是在 2.7.0 中引入)的修复。这些与一些内存泄漏有关;主队列的恢复;频繁重启导致 HA 队列失败;以及在某些情况下,从属节点(提升为主节点)的提升失败。此代码区域的整体质量很高,但故障场景的复杂性(此功能专门设计用于防止此类场景)使其成为错误潜伏的沃土。2.7.1 中修复的几乎所有错误都是由于恢复或重启事件的罕见或模糊组合引起的,我们有信心认为剩下的意外很少。当然,高可用性并不意味着保证可用性,因此会有一些我们无法恢复的情况。

其他小的改进和错误修复

  • 如果长时间运行代理,则可能会包装一个内部 GUID(全局唯一标识符)。显然,这不是预期的,并且无论如何它在实践中都不会造成问题,对吧?好吧,它确实造成了问题!(你不会知道的——有些人会长时间运行代理!)我们在 2.7.1 中修复了它。
  • 管理插件界面现在显示了更多关于队列长度的信息,并且铲除信息显示得更漂亮,此外,还有一些关于统计信息和 HA 从属节点信息的小问题已得到修复。
  • rabbitmqctl eval <expr> 是新的(在 2.7.1 中),用于在代理节点中评估任意 Erlang 表达式。
  • .net 客户端会话自动关闭有时可能会返回 AlreadyClosed 异常(它不应该这样做)。
  • STOMP 适配器之前没有正确地支持 reply-to 队列(它们不可重用),并且如果 SEND 帧提供了 message-id 头,则可能会在 MESSAGE 帧上提供多个 message-id 头。我们现在检查后一种情况并拒绝 SEND。
  • 已删除一些不再存在于 Erlang R15B 中的功能(并重写了代码),因此 RabbitMQ 现在应该可以在最新的 Erlang 版本下构建和运行。如果不行,请告诉我们!

感谢您的聆听

像往常一样,Rabbit 团队欢迎您提供您的使用体验反馈,无论好坏。我们鼓励您使用rabbitmq-discuss邮件列表。

© 2024 RabbitMQ. All rights reserved.