Channelz 简短介绍
Channelz 是一个工具,它提供 gRPC 中不同层级连接的全面运行时信息。它旨在帮助调试可能存在网络、性能、配置等问题的正在运行的程序。gRFC 详细解释了 Channelz 的设计,并且是所有语言中 Channelz 实现的权威参考。这篇博客的目的是让读者熟悉 Channelz 服务以及如何使用它来调试问题。本文的上下文基于 gRPC-Go,但整体思路应适用于所有语言。在撰写本文时,Channelz 已可用于 gRPC-Go 和 gRPC-Java。对 C++ 和包装语言的支持即将推出。
让我们通过一个简单的例子来学习 Channelz,该例子使用 Channelz 来帮助调试问题。我们仓库中的 helloworld 示例经过轻微修改,以设置一个有 bug 的场景。您可以在这里找到完整的源代码:客户端, 服务器。
客户端设置: 客户端将向指定目标发出 100 个 SayHello RPC,并使用轮询策略进行负载均衡。每个调用都有 150ms 的超时时间。为了调试目的,会记录 RPC 响应和错误。
运行程序时,我们在日志中注意到存在间歇性错误,错误代码为 DeadlineExceeded,如图 1 所示。
然而,没有任何线索表明是什么导致了截止时间超限错误,并且存在许多可能性
- 网络问题,例如:连接丢失
- 代理问题,例如:请求/响应在传输过程中被丢弃
- 服务器问题,例如:请求丢失或响应缓慢


图 1. 程序日志截图
让我们开启 gRPC INFO 级别日志以获取更多调试信息,看看能否找到有用的线索。


图 2. gRPC INFO 日志
如图 2 所示,INFO 日志表明与服务器的所有三个连接均已连接并准备好传输 RPC。日志中没有出现可疑事件。从 INFO 日志可以推断出,所有连接始终处于活动状态,因此可以排除连接丢失的假设。
为了进一步缩小问题根源的范围,我们将向 Channelz 求助。
Channelz 通过 gRPC 服务提供 gRPC 内部网络机制的统计信息。要启用 Channelz,用户只需在其程序中将 Channelz 服务注册到 gRPC 服务器并启动服务器。下面的代码片段展示了将 Channelz 服务注册到 grpc.Server 的 API。请注意,在我们的示例客户端中已经完成了此操作。
import "google.golang.org/grpc/channelz/service"
// s is a *grpc.Server
service.RegisterChannelzServiceToServer(s)
// call s.Serve() to serve channelz service
一个名为 grpc-zpages 的 Web 工具已被开发出来,方便地通过网页提供 Channelz 数据。首先,配置 Web 应用连接到提供 Channelz 服务的 gRPC 端口(参见前一个链接中的说明)。然后,在浏览器中打开 Channelz 网页。您应该会看到一个类似于图 3 的页面。现在我们可以开始查询 Channelz 了!


图 3. Channelz 主页面
由于错误发生在客户端,我们首先点击 TopChannels。TopChannels 是没有父级的根 Channel 集合。在 gRPC-Go 中,顶层 Channel 是用户通过 NewClient 创建的 ClientConn,用于发起 RPC 调用。在 Channelz 中,顶层 Channel 属于 Channel 类型,它是可以发出 RPC 的连接的抽象。


图 4. TopChannels 结果
因此,我们点击 TopChannels,出现了一个类似于图 4 的页面,列出了所有活动的顶层 Channel 及其相关信息。
如图 5 所示,只有一个 ID 为 2 的顶层 Channel(请注意,方括号中的文本是内存中 Channel 对象的引用名称,在不同语言中可能有所不同)。
查看数据 (Data) 部分,我们可以看到此 Channel 上 100 个调用中有 15 个失败。


图 5. 顶层 Channel (id = 2)
在右侧,它显示此 Channel 没有子 Channels,有 3 个 Subchannels(如图 6 中突出显示),以及 0 个 Sockets。


图 6. Channel (id = 2)拥有的 Subchannels
一个 Subchannel 是连接的抽象,用于负载均衡。例如,您想向“google.com”发送请求。解析器将“google.com”解析为多个提供“google.com”服务的后端地址。在此示例中,客户端配置了轮询负载均衡器,因此所有活动的后端都会收到相等的流量。然后,到每个后端的(逻辑)连接表示为一个 Subchannel。在 gRPC-Go 中,SubConn 可以视为一个 Subchannel。
父 Channel 拥有的三个 Subchannel 意味着有三个连接到三个不同的后端来发送 RPC。让我们查看每个 Subchannel 的详细信息。
因此,我们点击列出的第一个 Subchannel ID(即“4[]”),然后呈现出类似于图 7 的页面。我们可以看到此 Subchannel 上的所有调用都已成功。因此,此 Subchannel 不太可能与我们遇到的问题相关。


图 7. Subchannel (id = 4)
于是我们返回,并点击 Subchannel 5(即“5[]”)。同样,网页显示 Subchannel 5 也从未有过失败的调用。


图 8. Subchannel (id = 6)
最后,我们点击 Subchannel 6。这次有所不同。如图 8 所示,此 Subchannel 上 34 个 RPC 调用中有 15 个失败。并且请记住,父 Channel 也正好有 15 个失败的调用。因此,问题来源于 Subchannel 6。此 Subchannel 的状态为 READY,这意味着它已连接并准备好传输 RPC。这排除了网络连接问题。为了挖掘更多信息,让我们查看此 Subchannel 拥有的 Socket。
一个 Socket 大致相当于文件描述符,通常可以看作是两个端点之间的 TCP 连接。在 gRPC-Go 中,http2Client 和 http2Server 对应于 Socket。请注意,网络监听器也被视为 Socket,并将显示在 Channelz Server 信息中。


图 9. Subchannel (id = 6) 拥有 Socket (id = 8)
我们点击页面底部的 Socket 8(参见图 9)。现在我们看到一个类似于图 10 的页面。
该页面提供了关于 Socket 的全面信息,例如正在使用的安全机制、流计数、消息计数、keepalives、流量控制数字等。Socket 选项信息未在截图中显示,因为它们数量很多且与我们正在调查的问题无关。
远端地址 (Remote Address) 字段表明我们遇到问题的后端是 “127.0.0.1:10003”。这里的流计数与父 Subchannel 的调用计数完全对应。由此,我们可以知道服务器并未主动发送 DeadlineExceeded 错误。这是因为如果 DeadlineExceeded 错误是由服务器返回的,那么流都会成功。客户端流的成功与调用是否成功无关。它取决于是否收到了带有 EOS 位设置的 HTTP2 帧(有关更多信息,请参阅 gRFC)。此外,我们可以看到发送的消息数量是 34,等于调用次数,这排除了客户端因某种原因卡住并导致截止时间超限的可能性。总而言之,我们可以将问题范围缩小到在 127.0.0.1:10003 提供服务的服务器。可能是服务器响应缓慢,或者它前面的某个代理丢弃了请求。


图 10. Socket (id = 8)
如您所见,Channelz 只需几次点击就帮助我们查明了问题的潜在根源。您现在可以专注于该特定服务器上发生的事情。此外,Channelz 也可能有助于加快服务器端的调试。
我们将在此止步,让读者自行探索服务器端的 Channelz,它比客户端更简单。在 Channelz 中,Server 也是一个 RPC 入口点,就像 Channel 一样,传入的 RPC 在此到达并被处理。在 gRPC-Go 中,grpc.Server 对应于 Channelz 中的 Server。与 Channel 不同,Server 只有 Socket(包括监听 Socket 和普通已连接 Socket)作为其子项。
以下是一些给读者的提示
- 查找地址为 (127.0.0.1:10003) 的服务器。
- 查看调用计数。
- 查看服务器拥有的 Socket。
- 查看 Socket 流计数和消息计数。
您应该注意到,服务器 Socket 接收到的消息数量与客户端 Socket(Socket 8)发送的数量相同,这排除了中间存在行为异常代理(丢弃请求)的情况。并且服务器 Socket 发送的消息数量等于客户端接收到的消息数量,这意味着服务器未能在截止时间前发送响应。您现在可以查看 server 代码以验证这是否确实是原因。
服务器设置: 服务器端程序启动三个 GreeterServers,其中两个使用一种实现在响应客户端时没有延迟(server),另一个使用另一种实现(slowServer),该实现在发送响应前注入 100ms - 200ms 的可变延迟。
通过此演示您可以看到,Channelz 帮助我们快速缩小了问题可能原因的范围,并且易于使用。有关更多资源,请参阅详细的 Channelz gRFC。请访问 GitHub 上的 github.com/grpc/grpc-go 找到我们。