跳至主内容
版本:4.3

通道 (Channels)

概述

本指南涵盖了与频道(channel)相关的各类主题,这是 AMQP 0-9-1 规范特有的一种抽象。频道不能脱离连接而独立存在,因此强烈建议先熟悉连接指南

本指南涵盖

以及其他与连接相关的主题。

基础知识

某些应用程序需要与代理(broker)建立多个逻辑连接。然而,同时保持大量 TCP 连接既不理想,因为这样做会消耗系统资源,也会增加防火墙配置的难度。AMQP 0-9-1 连接通过频道进行多路复用,可以将其视为“共享单一 TCP 连接的轻量级连接”。

客户端执行的每一项协议操作都在频道上进行。特定频道上的通信与另一个频道上的通信是完全隔离的,因此每个协议方法也都携带一个频道 ID(也称为频道编号),这是一个整数,代理和客户端都用它来识别该方法属于哪个频道。

频道仅存在于连接的上下文中,从不独立存在。当连接关闭时,该连接上的所有频道也会随之关闭。

对于使用多线程/多进程进行处理的应用程序,通常的做法是为每个线程/进程开启一个新频道,并且不在它们之间共享频道。

频道生命周期

开启频道°

应用程序在成功开启连接后,会立即开启一个频道。

以下是一个 Java 客户端示例,展示了在开启新连接后,使用自动分配的频道 ID 来开启新频道

ConnectionFactory cf = new ConnectionFactory();
Connection conn = cf.createConnection();

Channel ch = conn.createChannel();

// ... use the channel to declare topology, publish, consume

在 .NET 客户端中,频道通过 IModel 接口表示,因此 API 中的命名有所不同

var cf = new ConnectionFactory();
var conn = cf.newConnection();

// the .NET client calls channels "models"
var ch = conn.CreateModel();

// ... use the channel to declare topology, publish, consume

与连接一样,频道应该是长生命周期的。也就是说,不需要针对每个操作都开启一个频道,这样做效率非常低,因为开启频道需要一次网络往返。

关闭频道

当不再需要频道时,应该将其关闭。关闭频道将使其无法使用,并调度其资源进行回收

Channel ch = conn.createChannel();

// do some work

// close the channel when it is no longer needed

ch.close();

使用 .NET 客户端的相同示例

var ch = await conn.CreateChannelAsync();

// do some work

// close the channel when it is no longer needed

ch.Close();

如上所述,关闭的频道无法使用。尝试在关闭的频道上执行操作会导致异常,提示频道已被关闭。

当频道的连接关闭时,该频道也会关闭。

如果频道在消费者确认了其上的若干投递消息后立即关闭,确认信息可能会也可能不会在频道终止前到达目标队列。在这种情况下,频道上挂起确认的消息将在频道关闭后自动重新入队。

这种情况通常适用于短生命周期频道的工作负载。使用长生命周期频道并设计能够处理重复投递的消费者,将减轻上述行为的影响。长生命周期频道通常也与更好的性能相关联。注意,被重新投递的消息将被显式标记

频道与错误处理

在上面的章节中,频道是由应用程序关闭的。还有另一种关闭频道的方式:由于协议异常。

在协议中,某些场景被认为是可恢复的(“软”)错误。它们会导致频道关闭,但应用程序可以打开另一个频道并尝试恢复或重试多次。最常见的例子包括:

  • 重新声明现有队列或交换机时,如果属性不匹配,将失败并返回 406 PRECONDITION_FAILED 错误
  • 访问资源时,如果用户无权访问,将失败并返回 403 ACCESS_REFUSED 错误
  • 绑定不存在的队列或不存在的交换机将失败并返回 404 NOT_FOUND 错误
  • 从不存在的队列进行消费将失败并返回 404 NOT_FOUND 错误
  • 发布消息到不存在的交换机将失败并返回 404 NOT_FOUND 错误
  • 从除声明连接以外的其他连接访问排他队列将失败并返回 405 RESOURCE_LOCKED

客户端库提供了观察和响应频道异常的方法。例如,在 Java 客户端中,有一种注册错误处理器并获取频道关闭原因的方法。

任何在已关闭频道上尝试的操作都会失败并报错。请注意,当 RabbitMQ 关闭频道时,它会使用异步协议方法通知客户端。换句话说,导致频道异常的操作不会立即失败,但频道关闭事件处理器会在稍后触发。

某些客户端库可能使用等待响应的阻塞操作。在这种情况下,它们可能会以不同的方式传达频道异常,例如使用运行时异常、错误类型或该语言适当的其他方式。

有关错误代码的更完整列表,请参阅 AMQP 0-9-1 参考手册

资源使用

每个频道在客户端上消耗相对较少的内存。根据客户端库的实现细节,它还可能使用专用的线程池(或类似机制)来分发消费者操作,从而占用一个或多个线程(或类似资源)。

每个频道在客户端所连接的节点上也消耗少量内存,以及一些 Erlang 进程。由于节点通常服务于多个频道连接,过度使用频道或频道泄漏的影响将主要反映在 RabbitMQ 节点的指标上,而不是客户端上。

鉴于这两个因素,强烈建议限制每个连接使用的频道数量。作为准则,大多数应用程序每个连接可以使用个位数的频道。具有极高并发率的应用程序(通常是消费者)可以从每个线程/进程/协程对应一个频道开始,当指标显示原始模型不再可持续(例如消耗太多内存)时,再切换到频道池模型。

参阅监控、指标和诊断部分,以了解如何检查频道、连接上的频道数量、频道流失率等。

每个连接的最大频道数

一个连接上可同时开启的最大频道数由客户端和服务器在连接时进行协商。该值对于 RabbitMQ 和客户端库都是可配置的。

在服务器端,该限制通过 channel_max 控制

# no more 100 channels can be opened on a connection at the same time
channel_max = 100

如果超过配置的限制,连接将因致命错误而关闭

2019-02-11 16:04:06.296 [error] <0.887.0> Error on AMQP connection <0.887.0> (127.0.0.1:49956 -> 127.0.0.1:5672, vhost: '/', user: 'guest', state: running), channel 23:
operation none caused a connection exception not_allowed: "number of channels opened (22) has reached the negotiated channel_max (22)"

客户端可以配置为允许每个连接使用更少的频道。对于 RabbitMQ Java 客户端ConnectionFactory#setRequestedChannelMax 是控制该限制的方法

ConnectionFactory cf = new ConnectionFactory();
// Ask for up to 32 channels per connection. Will have an effect as long as the server is configured
// to use a higher limit, otherwise the server's limit will be used.
cf.setRequestedChannelMax(32);

对于 RabbitMQ .NET 客户端,请使用 ConnectionFactory#RequestedChannelMax 属性

var cf = new ConnectionFactory();
// Ask for up to 32 channels per connection. Will have an effect as long as the server is configured
// to use a higher limit, otherwise the server's limit will be used.
cf.RequestedChannelMax = 32;

系统会使用两者中的较小值:无法将客户端配置为允许超过服务器最大配置值的频道数。尝试这样做的客户端将遇到如下所示的日志错误

2019-02-11 16:03:16.543 [error] <0.882.0> closing AMQP connection <0.882.0> (127.0.0.1:49911 -> 127.0.0.1:5672):
failed to negotiate connection parameters: negotiated channel_max = 2047 is higher than the maximum allowed value (32)

每个节点的最大频道数

可以使用配置参数 channel_max_per_node 来配置集群中每个节点允许开启的最大频道数

# no more than 500 channels can be opened on each node at the same time
channel_max_per_node = 500

监控、指标和诊断

由于频道会影响节点资源使用,当前开启的频道数量以及频道的开启/关闭速率是系统的重要指标,应该予以监控。监控它们有助于检测许多常见问题

  • 频道泄漏
  • 高频道流失(Churn)

这两个问题最终都会导致节点资源耗尽。

诸如未确认消息数量或 basic.get 操作速率等个体频道指标,有助于识别应用程序行为中的不规则性和低效之处。

内存使用

监控系统和运维人员可能需要检查频道在节点上消耗了多少内存、节点上的频道总数,并识别每个连接上有多少个频道。

频道数量会显示在管理界面的 Overview 选项卡上,连接数量也是如此。通过将频道数量除以连接数量,运维人员可以确定每个连接的平均频道数。

要了解节点上频道占用了多少内存,请使用 rabbitmq-diagnostics memory_breakdown

rabbitmq-diagnostics memory_breakdown -q --unit mb
# => [elided for brevity]
# ...
# => connection_channels: 3.596 mb (2.27%)
# ...
# => [elided for brevity]

详情请参阅 RabbitMQ 内存使用分析指南

频道泄漏

频道泄漏是指应用程序反复开启频道而不关闭它们,或者至少只关闭其中一部分的情况。

频道泄漏最终会耗尽节点(或多个目标节点)的内存和 CPU 资源。

相关指标

管理界面的 Overview 选项卡列出了当前用户有权访问的所有虚拟主机中的频道总数

mgmt-ui-global-channel-count.png

要检查连接上的当前频道数量以及每个连接的频道限制,请导航至 Connections 选项卡,如果未显示相关列,请启用它们

Per connection channel count in management UI

Overview 和各个节点页面提供了自 RabbitMQ 3.7.9 起的频道流失率图表。如果频道开启操作的速率持续高于关闭操作的速率,则证明其中一个应用程序存在频道泄漏

Channel count growth in management UI

要找出哪个连接泄漏了频道,请检查每个连接的频道计数,如本指南所示。

高频道流失

当系统新开启频道和关闭频道的速率持续处于高位时,即称该系统具有高频道流失。这通常意味着应用程序使用了短生命周期频道,或者频道经常因频道级异常而关闭。

虽然对于某些工作负载这是系统的自然状态,但应尽可能使用长生命周期频道。

管理界面提供了频道流失率图表。下图展示了一个相当低的频道流失率,在给定的时间段内开启和关闭的频道数量几乎相同

Node channel churn in management UI

虽然连接和断开连接的速率是系统特定的,但持续超过 100 次/秒的速率通常表明一个或多个应用程序的连接管理不佳,通常值得调查。

High channel churn in management UI

请注意,某些客户端和运行时(尤其是 PHP)不使用长生命周期连接,除非使用了专用代理,否则预期会出现高连接流失率。

在管理界面中检查频道及其状态

要在管理界面中检查频道,请导航至 Channels 选项卡并根据需要添加或删除列

High channel churn in management UI

使用 CLI 工具检查频道及其状态

rabbitmqctl list_connectionsrabbitmqctl list_channels 是检查每个连接频道计数和频道详情(如消费者数量、未确认消息预取计数等)的主要命令。

rabbitmqctl list_connections name channels -q
# => name channels
# => 127.0.0.1:52956 -> 127.0.0.1:5672 10
# => 127.0.0.1:52964 -> 127.0.0.1:5672 33

最右侧的列包含连接上的频道计数。

可以隐藏表头

rabbitmqctl list_connections name channels -q --no-table-headers
# => 127.0.0.1:52956 -> 127.0.0.1:5672 10
# => 127.0.0.1:52964 -> 127.0.0.1:5672 33

要检查单个频道,请使用 rabbitmqctl list_channels

rabbitmqctl list_channels -q
# => pid user consumer_count messages_unacknowledged
# => <rabbit@mercurio.3.815.0> guest 0 0
# => <rabbit@mercurio.3.820.0> guest 0 0
# => <rabbit@mercurio.3.824.0> guest 0 0
# => <rabbit@mercurio.3.828.0> guest 0 0
# => <rabbit@mercurio.3.832.0> guest 0 0
# => <rabbit@mercurio.3.839.0> guest 0 0
# => <rabbit@mercurio.3.840.0> guest 0 0

可以隐藏表头

rabbitmqctl list_channels -q --no-table-headers
# => <rabbit@mercurio.3.815.0> guest 0 0
# => <rabbit@mercurio.3.820.0> guest 0 0
# => <rabbit@mercurio.3.824.0> guest 0 0
# => <rabbit@mercurio.3.828.0> guest 0 0
# => <rabbit@mercurio.3.832.0> guest 0 0
# => <rabbit@mercurio.3.839.0> guest 0 0
# => <rabbit@mercurio.3.840.0> guest 0 0

可以显示不同的列集合

rabbitmqctl list_channels -q --no-table-headers vhost connection number prefetch_count messages_unconfirmed
# => / <rabbit@mercurio.3.799.0> 1 0 0
# => / <rabbit@mercurio.3.802.0> 1 0 0
# => / <rabbit@mercurio.3.799.0> 2 0 0
# => / <rabbit@mercurio.3.799.0> 3 0 0
# => / <rabbit@mercurio.3.802.0> 2 0 0
# => / <rabbit@mercurio.3.802.0> 3 0 0
# => / <rabbit@mercurio.3.799.0> 4 0 0
# => / <rabbit@mercurio.3.802.0> 4 0 0
# => / <rabbit@mercurio.3.799.0> 5 0 0
# => / <rabbit@mercurio.3.799.0> 6 0 0
rabbitmqctl list_channels -s vhost connection number confirm
# => / <rabbit@mercurio.3.799.0> 1 false
# => / <rabbit@mercurio.3.802.0> 1 false
# => / <rabbit@mercurio.3.799.0> 2 false
# => / <rabbit@mercurio.3.799.0> 3 false
# => / <rabbit@mercurio.3.802.0> 2 false
# => / <rabbit@mercurio.3.802.0> 3 false
# => / <rabbit@mercurio.3.799.0> 4 false
# => / <rabbit@mercurio.3.802.0> 4 false
# => / <rabbit@mercurio.3.799.0> 5 false

发布者流控

发布消息的频道可能会超过系统的其他部分,主要是繁忙的队列和执行复制的队列。当这种情况发生时,流控机制会应用于发布频道,进而应用于连接。仅消费消息的频道和连接不受影响。

对于使用自动确认模式的较慢消费者,连接和频道在写入 TCP 套接字时极有可能遇到流控。

监控系统可以收集处于流控状态的连接数量指标。经常遇到流控的应用程序可以考虑使用单独的连接来发布和消费消息,以避免流控影响非发布操作(例如队列管理)。

© . This site is unofficial and not affiliated with VMware.