gRPC 中使用 Channelz

January 4, 2021 · 544 words · 3 min

gRPC 中使用 Channelz

gRPC 提供了 Channelz 用于对外提供服务的数据,用于调试、监控等;根据服务的角色不同,可以提供的数据有:

  • 服务端: Servers, Server, ServerSockets, Socket
  • 客户端: TopChannels, Channel, Subchannel

Channelz 服务定义

参考 Channelz 的设计 gRPC Channelz 以及服务定义 channelz.proto,提供了以下方法:

service Channelz {
// 返回所有的根 Channel(即应用直接创建的 Channel)
  rpc GetTopChannels(GetTopChannelsRequest) returns (GetTopChannelsResponse);
  // 根据 Channel ID 返回单个的 Channel 详情,包括 Subchannel,如果没有则返回 NOT_FOUND
  rpc GetChannel(GetChannelRequest) returns (GetChannelResponse);
  // 根据 Subchannel ID 返回 Subchannel 详情
  rpc GetSubchannel(GetSubchannelRequest) returns (GetSubchannelResponse);
  // 返回所有存在的 Server
  rpc GetServers(GetServersRequest) returns (GetServersResponse);
  // 根据 Server ID 返回 Server 详情
  rpc GetServer(GetServerRequest) returns (GetServerResponse);
  // 根据 Server ID 返回 Server 所有的 Socket
  rpc GetServerSockets(GetServerSocketsRequest) returns (GetServerSocketsResponse);
  // 根据 Socket ID 返回 Socket 详情
  rpc GetSocket(GetSocketRequest) returns (GetSocketResponse);
}

使用

相关项目参考 github.com/helloworlde/grpc-java-sample

添加依赖

  • build.gradle.kts

Channelz 服务在 grpc-services 包中,需要添加该依赖

dependencies {
    implementation("io.grpc:grpc-netty:${grpcVersion}")
    implementation("io.grpc:grpc-protobuf:${grpcVersion}")
    implementation("io.grpc:grpc-stub:${grpcVersion}")
    implementation("io.grpc:grpc-services:${grpcVersion}")
}

Server 端

Server 端添加 Channelz 服务非常简单,只需要将 Channelz 的服务添加到 Server 中即可

  • Server

可以通过 ChannelzService.newInstance(100) 直接构建 Channelz 服务实例,参数是获取数据时分页的大小

@Slf4j
public class ChannelzServer {

    @SneakyThrows
    public static void main(String[] args) {

        // 构建 Server
        Server server = NettyServerBuilder.forAddress(new InetSocketAddress(9090))
                                          // 添加服务
                                          .addService(new HelloServiceImpl())
                                          // 添加 Channelz 服务
+                                         .addService(ChannelzService.newInstance(100))
                                          // 添加反射服务,用于 grpcurl 等工具调试
+                                         .addService(ProtoReflectionService.newInstance())
                                          .build();

        // 启动 Server
        server.start();
        log.info("服务端启动成功");

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            try {
                server.awaitTermination(10, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }));

        // 保持运行
        server.awaitTermination();
    }
}

Client 端

Client 端不能直接开启,需要单独启动一个 Server,用于提供数据,与 Server 端一样,不过只提供 Channelz 的服务

@Slf4j
public class ChannelzClient {

    @SneakyThrows
    public static void main(String[] args) throws InterruptedException {

        // 构建并启动 Channelz 服务
+       Server server = NettyServerBuilder.forPort(9091)
+                                         // 添加 Channelz 服务
+                                         .addService(ChannelzService.newInstance(100))
+                                         // 添加反射服务,用于 grpcurl 等工具调试
+                                         .addService(ProtoReflectionService.newInstance())
+                                         .build()
+                                         .start();

        // 构建 Channel
        ManagedChannel channel = ManagedChannelBuilder.forAddress("127.0.0.1", 9090)
                                                      .usePlaintext()
                                                      .build();

        // 使用 Channel 构建 BlockingStub
        HelloServiceGrpc.HelloServiceBlockingStub blockingStub = HelloServiceGrpc.newBlockingStub(channel);

        // 发送多个请求,用于观察数据变化
        for (int i = 0; i < 10000; i++) {
            // 构建消息
            HelloMessage message = HelloMessage.newBuilder()
                                               .setMessage("Channelz " + i)
                                               .build();

            // 发送消息,并返回响应
            HelloResponse helloResponse = blockingStub.sayHello(message);
            log.info(helloResponse.getMessage());
            Thread.sleep(1000);
        }

        // 等待终止
+       server.awaitTermination();
    }
}

测试

分别启动 Server 端和 Client 端,发送请求;使用其他工具观察

grpc-zpages

gRPC 官方提供了 grpc-zpages 工具,可用通过 Web 查看指定服务的信息,不过该工具已经很久没有维护,使用较为复杂,具体可以参考 A short introduction to Channelz

channelzcli

是一个 CLI 工具,可以通过命令行实现获取 Channelz 的信息,对 Channelz 的数据做了一定的处理,较为友好,具体使用参考 channelzcli

安装

go get -u github.com/kazegusuri/channelzcli

使用

  • 获取 Channel 信息
channelzcli list channel -k --addr localhost:9091

ID	Name                                                                            	State	Channel	SubChannel	Calls	Success	Fail	LastCall
4	ManagedChannelImpl{logId=4, target=127.0.0.1:9090}                              	READY	0      	1         	4355  	4355  	0     	716ms
  • 描述 Server 信息
channelzcli describe server 2 -k --addr localhost:9090

ID: 	2
Name:	ServerImpl{logId=2, transportServers=[NettyServer{logId=1, address=0.0.0.0/0.0.0.0:9090}]}
Calls:
  Started:        	14486
  Succeeded:      	14476
  Failed:         	9
  LastCallStarted:	2021-01-06 08:51:35.608 +0000 UTC
  • 列出 Channel 信息
channelzcli tree channel -k --addr localhost:9091

127.0.0.1:9090 (ID:4) [READY]
  [Calls] Started:4627, Succeeded:4627, Failed:0, Last:638ms
  [Subchannels]
    |-- [[[/127.0.0.1:9090]/{}]] (ID:6) [READY]
          [Calls]: Started:4627, Succeeded:4627, Failed:0, Last:638ms
          [Socket] ID:7, Name:CallTracingTransport{delegate=CallCredentialsApplyingTransport{delegate=NettyClientTransport{logId=7, remoteAddress=/127.0.0.1:9090, channel=[id: 0x83c4d921, L:/127.0.0.1:52321 - R:/127.0.0.1:9090]}}}, RemoteName:, Local:[127.0.0.1]:52321 Remote:[127.0.0.1]:9090```

grpcurl

是一个 CLI 工具,可以像 CURL 一样对 gRPC 服务发起请求,但是需要服务添加反射服务,详情参考 grpcurl

安装

brew install grpcurl

使用

  • 获取 Server 列表
grpcurl -plaintext localhost:9090 grpc.channelz.v1.Channelz/GetServers

{
  "server": [
    {
      "ref": {
        "server_id": "2",
        "name": "ServerImpl{logId=2, transportServers=[NettyServer{logId=1, address=0.0.0.0/0.0.0.0:9090}]}"
      },
      "data": {
        "calls_started": "13438",
        "calls_succeeded": "13432",
        "calls_failed": "4",
        "last_call_started_timestamp": "2021-01-06T08:34:17.763Z"
      },
      "listen_socket": [
        {
          "socket_id": "3",
          "name": "ListenSocket{logId=3, channel=[id: 0xc703c330, L:/0:0:0:0:0:0:0:0:9090]}"
        }
      ]
    }
  ],
  "end": true
}
  • 获取 Channel 列表
grpcurl -plaintext localhost:9091 grpc.channelz.v1.Channelz/GetTopChannels

{
  "channel": [
    {
      "ref": {
        "channel_id": "4",
        "name": "ManagedChannelImpl{logId=4, target=127.0.0.1:9090}"
      },
      "data": {
        "state": {
          "state": "READY"
        },
        "target": "127.0.0.1:9090",
        "calls_started": "45",
        "calls_succeeded": "45",
        "last_call_started_timestamp": "2021-01-06T07:38:45.217Z"
      },
      "subchannel_ref": [
        {
          "subchannel_id": "6",
          "name": "InternalSubchannel{logId=6, addressGroups=[[[/127.0.0.1:9090]/{}]]}"
        }
      ]
    }
  ],
  "end": true
}