原文 Networking and RabbitMQ

简介

Clients 是通过 network 与 RabbitMQ 通信的。所有 Broker (译注:即RabbitMQ服务器)支持的协议,都是基于 TCP 的。RabbitMQ与操作系统都提供了许多可以调整的参数。它们中有一些是直接与 TCP/IP 操作相关的,另外有一些是与应用层级别协议如 TLS 相关的。这份指南,覆盖了许多与RabbitMQ上下文中 network 相关的主题。本指抽不是广泛的参考,而是概述。讨论的一些可以调参数是OS特定的。当覆盖特定OS时,这份指南重点介绍 Linux 平台的主题,因为它是 RabbitMQ 最常见的部署平台。

可以配置或调整的地方:

  • interface 和 端口
  • TLS
  • TCP socket 的设置(如 buffer 大小)
  • 内核 TCP 的设置(如 TCP keepalives)
  • (AMQP 0-9-1, STOMP) heartbeats,在MQTT中称为 keepalives
  • hostname 和 DNS

除了 OS 内核参数和 DNS,所有 RabbitMQ 设置都是 通过 RabbitMQ 配置文件来配置的

Networking 是一个比较广泛的话题。有许多配置选项对某些 workloads (译注:工作负载)会产生积极或消极的影响。因此,本指南不能作为完整的参考,而是提供关键可以调参数的索引的起点。

network interface (网络接口)

对于 RabbitMQ 要接受 client 的连接,它需要绑定到一个或多个 interfaces 并监听(特定协议)端口。 interfaces 可以使用 rabbit.tcp_listeners 配置选项来配置。默认情况下,RabbitMQ 将在所有可用的 interfaces 上监听 5672 端口。

TCP listeners 配置 interface 和 端口。以下示例演示了如何在特定IP和标准端口上配置 RabbitMQ

[
  {rabbit, [
    {tcp_listeners, [{"192.168.1.99", 5672}]}
  ]}
].

监听双栈(IPV4 和 IPV6) interfaces

[
  {rabbit, [
    {tcp_listeners, [{"127.0.0.1", 5672},
                     {"::1",       5672}]}
  ]}
].

使用现代的 Linux 内核 和 Windows Vista 之后 的版本中,当指定端口并且 RabbitMQ 配置为监听所有 IPv6 地址但 IPv4 末被明确禁用时,IPv4 地址仍将会被包含在内。因此

[
  {rabbit, [
    {tcp_listeners, [{"::",       5672}]}
  ]}
].

是等同于

[
  {rabbit, [
    {tcp_listeners, [{"0.0.0.0", 5672},
                     {"::",      5672}]}
  ]}
].

仅监听 IPv4 interfaces

在这个例子中,RabbitMQ 将仅监听 IPv4 interfaces

[
  {rabbit, [
    {tcp_listeners, [{"192.168.1.99", 5672}]}
  ]}
].

或者,如果需要单栈设置,则可以使用 RABBITMQ_NODE_IP 环境变量来配置 interfaces。 请参考我们的 配置指南

仅监听 IPv6 interfaces

在这个例子中,RabbitMQ 将仅监听 IPv6 interfaces

[
  {rabbit, [
    {tcp_listeners, [{"fe80::2acf:e9ff:fe17:f97b", 5672}]}
  ]}
].

或者,如果需要单栈设置,则可以使用 RABBITMQ_NODE_IP 环境变量来配置 interfaces。 请参考我们的 配置指南

端口访问

SELinux 以及其他相似机制可能会防止 RabbitMQ 去绑定一个端口。当发生这种情况时,RabbitMQ 将无法启动。防火墙可以防止节点和CLI工具相互通信。请确保可以打开以下端口:

  • 4369 : epmd RabbitMQ 节点和CLI工具使用的对等发现服务
  • 5672,5671 : 由 AMQP 0-9-1 和 1.0 客户端使用,不带TLS和TLS
  • 25672 : 由 Erlang 分发用于节点间和CLI工具通信,并从动态范围分配(默认情况下限制为单个端口,计算为 AMQP端口+20000)。详情请参阅 网络指南
  • 15672 : HTTP API 客户端和 rabbitmqadmin (只有启用了 management plugin )
  • 61613, 61614 : STOMP 客户端 不带和带TLS(只有启用了 STOMP plugin)
  • 1883, 8883 : MQTT 客户端 不带和带 TLS (只有启用了 MQTT plugin )
  • 15674 : STOMP 之于 WebSockets 的客户端 (只有启用了 Web STOMP plugin)
  • 15675 : MQTT 之于 WebSockets 的客户端 (只有启用了 Web MQTT plugin)

可以将 RabbitMQ 配置 为使用 不同的端口和特定的 network interfaces

EPMD 和 内部节点通信端口

Erlang 使用端口映射程序守护进程(epmd) 来解决集群中的节点名称。 默认的 epmd 端口是 4369 ,但可以使用 ERL_EPMD_PORT 环境变量来更改。所有节点必须使用相同的端口。有关详细信息,请参阅 Erlang epmd manpage

一旦分布式的 Erlang 节点地址已通过 epmd 解析,其他节点将尝试使用 Erlang 分发协议与该地址直接通信。有关详细信息,请参阅下一节。

RabbitMQ 节点使用称为分发端口的端口与CLI工具和其他节点通信。它是从一系列值动态分配的。默认情况下,该范围仅限于以配置的 RABBITMQ_NODE_PORT (AMQP 端口) + 20000 或 25672 。此单个端口范围可以使用 RABBITMQ_DIST_PORT 环境变量进行配置。

范围也可以通过两个配置键进行控制:

  • kernel.inet_dist_listen_min
  • kernel.inet_dist_listen_max

它们定义范围的下限和上限,(含)

下面的示例使用单个端口的范围,但是与默认值不同的值:

[
  {kernel, [
    {inet_dist_listen_min, 33672},
    {inet_dist_listen_max, 33672}
  ]},
  {rabbit, [
    ...
  ]}
].

要验证节点使用哪个端口进行节点间和CLI工具通信,请在该节点主机上运行:

epmd -names

它将输出如下所示:

epmd: up and running on port 4369 with data:
name rabbit at port 25672

TLS (SSL) 支持

可以使用 RabbitMQ 的TLS 加密连接。使用对等证书的身份验证也是可行的。有关详细信息,请参阅 TLS/SSL 指南

调整吞吐量

调整吞吐量是一个常见的目标。可以通过优化以下:

  • 增加 TCP buffer 的大小
  • 确保 Nagle 的算法被禁用
  • 启用可选的 TCP 功能和扩展

对于后两者,请参阅下面的操作系统级调优部分。请注意,调整吞吐量将涉及权衡。例如,增加 TCP buffer 大小,将增加每个连接使用的 RAM ,这可能是显著增加服务器的 RAM 使用量。

TCP Buffer 大小

这是关键的可调参数之一。每个TCP连接都有为其分配的 buffers 。一般来说,这些缓冲区越大,每个连接使用的 RAM 越多,吞吐量越好。在 Linux 上,默认情况下,操作系统将自动调整 TCP buffer 大小,通常设置在 80 到 120 KB 之间。为了获得最大吞吐量,可以使用

  • rabbit.tcp_listen_options
  • rabbitmq_mqtt.tcp_listen_options
  • rabbitmq_amqp1_0.tcp_listen_options

和相关的 key 来增加 buffer 的大小。

以下示例将 AMQP 0-9-1 连接的 TCP buffer 设置为 192 KiB:

[
  {rabbit, [
    {tcp_listen_options, [
                          {backlog,       128},
                          {nodelay,       true},
                          {linger,        {true,0}},
                          {exit_on_close, false},
                          {sndbuf,        196608},
                          {recbuf,        196608}
                         ]}
  ]}
].

与 MQTT 和 STOMP 连接相同的示例:

[
  {rabbitmq_mqtt, [
    {tcp_listen_options, [
                          {backlog,       128},
                          {nodelay,       true},
                          {linger,        {true,0}},
                          {exit_on_close, false},
                          {sndbuf,        196608},
                          {recbuf,        196608}
                         ]}
                         ]},
  {rabbitmq_stomp, [
    {tcp_listen_options, [
                          {backlog,       128},
                          {nodelay,       true},
                          {linger,        {true,0}},
                          {exit_on_close, false}
                          {sndbuf,        196608},
                          {recbuf,        196608}
                         ]}
  ]}
].

请注意,将发送和接收的 buffer 大小设置不同的值是危险的,并不推荐。

Erlang VM I/O 线程池

Erlang runtime 使用一组线程来异步执行 I/O 操作。池的大小通过 +A VM 命令行标志配置,例如: +A 128 我们强烈建议使用 RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS 环境变量来覆盖该标志:

RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="+A 128"

最近的 RabbitMQ 版本中的默认值为 128 (之前为 30)。具有 8 个核心或更多核心可用的节点建议使用高于 96 的值,即每个核心可用的12个或更多个 I/O 线程。请注意,由于等待 I/O ,较高的值不一定意味着更好的吞吐量或更低的 CPU 消耗。

调整大量连接

一些工和负载,通常被称为 “物联网”,假设每个节点有大量客户端连接,每个节点的流量相对较少。一个这样的工作量是传感器网络:可以部署数十万或百万个传感器,每个传感器每隔几分钟发射一次数据。优化并发客户端的最大数量可能比总吞吐量更重要

有几个因素可以限制单个节点可以支持的并发连接数:

  • 打开文件句柄(包括套接字)的最大数量以及其他内核强制的资源限制
  • 每个连接使用的 RAM 量
  • 每个连接使用的 CPU 资源量
  • VM 配置允许的最大 Erlang 进程数

打开文件句柄限制

大多数操作系统限制可以同时打开的文件句柄的数量。当操作系统进行 (如 RabbitMQ 的 Erlang VM) 达到极限时,它将无法打开任何新文件或接受任何更多的 TCP 连接

如何配置限制从操作系统到操作系统和分发到分发,例如:取决于是否使用 systemd 。对于 Linux,在Debian 和 RPM 安装指南中提供了 linux 上的控制系统限制。 Linux 内核限制管理被 web 上许多资源所涵盖,包括打开的文件句柄限制MacOS 使用类似的系统

在优化并发连接数时,确保你的系统具有足够的文件描述符,不仅可以支持客户端连接,还可以支持节点可能使用的文件。要计算一个 ballpark 限制,将每个节点的连接数乘以 1.5 。例如,要支持 100,000 个连接,请将限制设置为 15 万。增加限制稍微增加 RAM 空闲机使用量,但这是一个合理的权衡。

TCP buffer 大小

有关概述,请参阅上面的部分。可以使用

  • rabbit.tcp_listen_options
  • rabbitmq_mqtt.tcp_listen_options
  • rabbitmq_amqp1_0.tcp_listen_options

和相关的配置 key 来减少 buffer 的耚,以减少每个连接使用的服务器的 RAM 数量。在每个节点持续并发数量比吞吐量更重要的环境中,这通常是必要的。

以下示例将 AMQP 0-9-1 连接的 TCP buffer 大小设置为 32 KiB

[
  {rabbit, [
    {tcp_listen_options, [
                          {backlog,       128},
                          {nodelay,       true},
                          {linger,        {true,0}},
                          {exit_on_close, false},
                          {sndbuf,        32768},
                          {recbuf,        32768}
                         ]}
  ]}
].

与 MQTT 和 STOMP 连接相同的示例:

[
  {rabbitmq_mqtt, [
    {tcp_listen_options, [
                          {backlog,       128},
                          {nodelay,       true},
                          {linger,        {true,0}},
                          {exit_on_close, false},
                          {sndbuf,        32768},
                          {recbuf,        32768}
                         ]}
                         ]},
  {rabbitmq_stomp, [
    {tcp_listen_options, [
                          {backlog,       128},
                          {nodelay,       true},
                          {linger,        {true,0}},
                          {exit_on_close, false},
                          {sndbuf,        32768},
                          {recbuf,        32768}
                         ]}
  ]}
].

请注意,较低的 TCP buffer 大小将导致显著的吞吐量下降,因此需要为每个工作负载找到吞吐量和每个连接 RAM 使用率之间的最佳值。将发送和接收的 buffer 大小设置为不同的值是危险的,不建立使用。不建议使用低于 8 KiB 的值。

Nagle 的算法 (nodelay)

禁用 Nagle 的算法主要用于减少延迟,但也可以提高吞吐量。 kernel.inet_default_connect_optionskernel.inet_default_listen_options 必须包括 {nodelay, true} 以禁用 Nagle 的节点间连接的算法。配置提供客户端连接的套接字时, rabbit.tcp_listen_options 必须包含相同的选项。这是默认值。以下示例说明了:

[
  {kernel, [
    {inet_default_connect_options, [{nodelay, true}]},
    {inet_default_listen_options,  [{nodelay, true}]}
  ]},
  {rabbit, [
    {tcp_listen_options, [
                          {backlog,       4096},
                          {nodelay,       true},
                          {linger,        {true,0}},
                          {exit_on_close, false}
                         ]}
  ]}
].

Erlang VM I/O 线程池调整

调整大量并发连接时,适当的 Erlang VM I/O 线程池大小也很重要。请参阅上面的部分。

连接积压

客户端数量很少,新的连接速度分布非常不均匀,但也足够小,并没有太大的区别。当数量达到数万以上时,务必服务器可以接受入站连接。未接受的 TCP 连接被放入有限长度的队列中。这个长度必须足以解决峰值负载小时可能的尖峰,例如,当许多客户端由于网络中断或选择重新连接而断开连接时。这是使用 rabbit.tcp_listen_options.backlog 选项

[
  {rabbit, [
    {tcp_listen_options, [
                          {backlog,       4096},
                          {nodelay,       true},
                          {linger,        {true,0}},
                          {exit_on_close, false}
                         ]}
  ]}
].

默认是 128 。当挂起连接队列长度超出此值时,操作系统将拒绝连接。另请参见内核调优部分中的 net.core.somaxconn

OS 级别调整

操作系统设置可能会影响 RabbitMQ 的运行。一些与网络直接相关 (例如 TCP 设置),其他一些会影响 TCP 套接字以及其他事情(例如打开文件句柄)。了解这些限制很重要,因为它们可能会根据工作负载而改变

一些重要的可配置内核选项(对于 IPv4)

  • fs.file-max : 内核将分配的最大文件数。可以使用 /proc/sys/fs/file-nr 检查限制和当前值
  • net.ipv4.ip_local_port_range : 本地IP端口范围,定义为一对值。该范围必须为并发连接的峰值数量提供足够的条目。
  • net.ipv4.tcp_tw_reuse : 启用时,允许内核在TIME_WAIT状态下重新使用套接字。有关详细信息,请参阅 应答忙碌服务器上的TCP TIME_WAIT 连接。 NAT使用后,此选项很危险
  • net.ipv4.tcp_fin_timeout : 将此值降至5-10,会减少关闭连接将保持在 TIME_WAIT 状态的时间。推荐用于大量并发连接的情况。
  • net.core.somaxconn : 监听队列的大小(正在同时建立多少个连接)。默认为 128.增加到4096或更高以支持入站连接的突发情况,例如:大量客户端重新连接时。
  • net.ipv4.tcp_max_syn_backlog : 连接客户端未收到确认的最大连接请求数,默认为128,最大值为 65535 。对于吞吐量进行优化时,建议使用 4096 和 8192 启动值。
  • net.ipv4.tcp_keepalive_* : net.ipv4.tcp_keepalive_time, net.ipv4.tcp_keepalive_intvl, and net.ipv4.tcp_keepalive_probes 配置 TCP keepalive。 AMQP 0-9-1 和 STOMP 具有部分消除其影响的心跳,即可能需要几分钟才检测到无响应的对等体,例如,在硬件或电源故障的情况下。MQTT 也有自己的 keepalive 机制,这是同一个想法在不同的名字。使用默认设置启动 TCP keepalive 时,建议将心跳超时设置为 8-20 秒。另请参阅本指南后面的 TCP keepalive 注释
  • net.ipv4.conf.default.rp_filter : 启动反向路径过滤。如果您的系统不涉及 IP 地址欺骗,请禁用它。

请注意,Linux 内核版本和发行版之间的默认值会有所不同。建议使用最近的内核(3.9或更高版本)。

内核参数调优与操作系统不同。本指南重点介绍 Linux 。要以交互方式配置内核参数,请使用 sysctl -w (需要 root 权限),例如:

sysctl -w fs.file-max 200000

要使更改永久(重新启动之间)生效,它们需要添加到 /etc/sysctl.conf 有关详细信息,请参见 sysctl(8)sysctl.conf(5)

TCP 栈调优是一个广泛的话题,其他地方有很多细节:

TCP 套接字选项

常见选项

  • rabbit.tcp_listen_options.nodelay : 当设为 true ,禁用 Nagle 算法。默认为 true 。对绝大多数用户来说是强烈推荐的。
  • rabbit.tcp_listen_options.sndbuf : 请参阅本指南前面的 TCP buffer 。默认值由操作系统自动调整,通常在现代 Linux 版本上的 88 KiB 到 128 KiB 范围内。增加 buffer 大小可以提高每个连接的消费者吞吐量和 RAM 使用率。减少效果则相反。
  • rabbit.tcp_listen_options.recbuf : 请参阅本指南前面的 TCP buffer 。默认值效果与 rabbit.tcp_listen_options.sndbuf 类似,但一般对于发布者和协议操作
  • rabbit.tcp_listen_options.backlog : 未接受的 TCP 连接队列的最大大小。达到此大小后,新的连接将被拒绝。对于具有数千个并发连接和可能的批量客户端重新连接的环境,设置为 4096 或更高。
  • rabbit.tcp_listen_options.linger : 当设置为 {true,N} 时,设置当(服务器)套接字关闭时刷新未发送数据的超时(以秒为单位)
  • rabbit.tcp_listen_options.keepalive : 当设置为 true 时,启用 TCP keepalive (见上文)。默认为 false,对于连接可以长时间闲置(至少10分钟)的环境,尽管使用心跳仍然建议使用此选项。

默认

以下是 RabbitMQ 默认的 TCP 套接字选项配置

[
  {rabbit, [
    {tcp_listen_options, [{backlog,       128},
                          {nodelay,       true},
                          {linger,        {true, 0}},
                          {exit_on_close,  false}]
  ]}
].

心跳

RabbitMQ 支持的一些协议(包括 AMQP 0-9-1) 支持心跳,更快地检测死 TCP 对等体。有关详细信息,请参阅 心跳指南

Net Tick 时间

心跳用于检测客户端和 RabbitMQ 节点之间的对等体或连接故障。 net_ticktime 用于同一目的,但用于集群节点通信。小于5(秒)的值,可能会导致假阳性,不推荐使用。

TCP keepalives

TCP包含一个类似于心跳(a.k.a. keepalive)的机制,一个在消息协议和上面覆盖的 net tick 超时:TCP keepalives。 由于缺省设置不正确,TCP Keepalives通常不能按照它们的方式工作:需要很长时间(比如说一个小时或更长时间)来检测死亡对等体。 然而,通过调整,它们可以起到与心跳相同的目的,并清除陈旧的TCP连接,例如 客户选择不使用心跳,有意或无意。 以下是TCP Keepalive的一个示例,表示TCP连接在TCP连接死亡或120秒后无法访问(连接空闲60秒后每15秒尝试一次):

net.ipv4.tcp_keepalive_time=60
net.ipv4.tcp_keepalive_intvl=15
net.ipv4.tcp_keepalive_probes=4

对于RabbitMQ操作员无法控制应用程序设置或客户端库的环境,TCP keepalive可以成为一个有用的附加防御机制。

连接握手超时

RabbitMQ 有一个连接握手超时,默认为 10 秒。当客户端在严重受限的环境中运行时,可能需要增加超时。这可以通过 rabbit.handshake_timeout (以毫秒为单位)完成

[
  {rabbit, [
    %% 20 seconds
    {handshake_timeout, 20000}
  ]}
].

应该指出的是,只有极其有限的客户端和网络才需要这样做。在其他情况下握手超时表示其他地方出现问题。

TLS/SSL 握手

如果启用TLS / SSL,则可能还需要增加TLS / SSL握手超时。这可以通过 rabbit.ssl_handshake_timeout(以毫秒为单位)完成:

[
  {rabbit, [
    %% 10 seconds
    {ssl_handshake_timeout, 10000}
  ]}
].

主机名解析和DNS

在许多情况下,RabbitMQ依赖于Erlang运行时进行节点间通信(包括诸如rabbitmqctl,rabbitmq-plugins等工具)。 客户端库在连接到RabbitMQ节点时也执行主机名解析。 本节简要介绍与之相关的大多数常见问题。

由客户端库执行

如果将客户端库配置为连接到主机名,则它将执行主机名解析。 根据DNS和本地解析器(/etc/hosts 和类似的)配置,这可能需要一些时间。 不正确的配置可能导致解析超时,例如 当尝试通过DNS解析本地主机名(如my-dev-machine)时。因此,客户端连接可能需要很长时间(从几十秒到几分钟)。

短和全称的RabbitMQ节点名称

RabbitMQ依赖于Erlang运行时进行节点间通信。 Erlang节点包括一个主机名,一个是short(rmq1)或者full-qualified(rmq1.dev.megacorp.local)。 运行时不允许混合短标准和完全限定的主机名。集群中的每个节点都必须能够解析每个其他节点的主机名,短或完全限定。 默认情况下,RabbitMQ将使用较短的主机名。 设置RABBITMQ_USE_LONGNAME环境变量,使RabbitMQ节点使用完全限定名称,例如 rmq1.dev.megacorp.local

反向DNS查找

如果将 rabbit.reverse_dns_lookups 配置选项设置为 true,则RabbitMQ将对客户端IP地址执行反向DNS查找,并在连接信息中列出主机名(例如,在管理UI中)。