0%

Python gRPC 实战

gRPC(google RPC)使用 Protocol Buffers 作为它的接口定义语言(Interface Definition Language,IDL)以及它的底层消息交换格式。通过 gRPC,client 应用程序可以直接调用位于不同机器上的 server 应用程序的接口,就好像 client 应用程序和 server 应用程序在同一个进程中一样。这篇文章介绍如何在 Python 中使用 gRPC。

gRPC 的核心思想是:定义一个 service,并指定该 service 所提供的方法以及方法的请求参数/返回结果的类型。在 server 端,需要实现这些接口,并启动一个 gRPC server 来处理客户端请求,在客户端,通过 stub 来调用这些方法。

Protocol Buffers 是 gRPC 的底层消息交换格式,gRPC 使用 protobuf 来进行结构数据的序列化/反序列化。同时 Protocol Buffers 也是 gRPC 的 IDL,gRPC 允许你定义四种类型的服务方法:

  • Unary RPCs:client 发送单个请求,server 返回一个响应。就和普通的函数调用类似

例如:

1
rpc SayHello(HelloRequest) returns (HelloResponse);
  • Server streaming RPCs:client 发送单个请求,server 可以返回响应消息流

例如:

1
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
  • Client streaming RPCs:client 发送请求消息流,server 返回单个响应

例如:

1
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
  • Bidirectional streaming RPCs:client 可以发送请求消息流、server 可以返回应答消息流

例如:

1
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);

接下来将介绍如何在 Python 中使用 gRPC。

安装 gRPC

首先创建一个测试虚拟环境:

1
2
3
$ virtualenv grpc-env
$ cd grpc-env
$ source bin/activate

安装 gRPC:

1
pip install grpcio

Python 的 gRPC 工具包含 protocol buffer 编译器 protoc 以及一个能够根据 proto 文件生成 server 和 client 代码的特殊插件。使用如下命令安装 gRPC 工具包:

1
pip install grpcio-tools

Hello World

接下来使用 Python gRPC 包生成一个服务器和客户端程序。gRPC 通过 .proto 文件来定义服务接口和消息类型,因此首先需要编写一个 proto 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
syntax = "proto3";

package helloworld;

// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings
message HelloReply {
string message = 1;
}

接下来执行如下命令根据该 proto 文件生成对应 python gRPC 代码,我们的程序会使用这些生成的 gRPC 代码来进行通信:

1
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. hello_world.proto
  • -I 选项:指定搜索 proto 文件的目录,这里指定为当前目录
  • –python_out:用于保存所生成代码(所有的 XXX_pb2.py)的目录,这里指定为当前目录
  • –grpc_python_out:用于保存所生成代码(所有的 XXX_grpc_pb2.py)的目录,这里也是当前目录

注意这里的 pb2 是指生成代码遵循 Protocol Buffers Python API version2,它和 Protocol Buffer Language 的版本无关(在 .proto 文件中通过 syntax = "proto3" 指定了 PB 版本为 version 3)。

命令执行成功后,可以在当前目录下看到两个新增的文件:

  • hello_world_pb2.py:包含所生成的 request/response/service 类
  • hello_world_pb2_grpc.py:包含所生成的 server/client 类

接下来我们需要在自己的代码中实现 rpc 方法,即在 rpc 方法中编写真实的业务逻辑。这里我们直接在自己的 gRPC server 文件中实现自己的 Greeter 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from concurrent import futures
import logging

import grpc
import hello_world_pb2
import hello_world_pb2_grpc


class Greeter(hello_world_pb2_grpc.GreeterServicer):

def SayHello(self, request, context):
return hello_world_pb2.HelloReply(message='Hello, %s!' % request.name)


def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
hello_world_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()


if __name__ == '__main__':
logging.basicConfig()
serve()
  • gRPC 自动生成的 helloworld_pb2_grpc.GreeterServicer,定义了 RPC 接口
  • 我们自己的 Greeter 类,它继承自 helloworld_pb2_grpc.GreeterServicer ,然后实现了其 RPC 方法 SayHello
  • 启动一个 grpc server,并向其添加 Greeter service,开始接受客户端的 RPC 请求。
  • gRPC 框架会负责对接收到的请求进行反序列化、执行 RPC方法、对响应进行序列化

然后在我们的 client 代码中,访问 Greeter service 的 SayHello 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from __future__ import print_function

import logging

import grpc
import hello_world_pb2
import hello_world_pb2_grpc


def run():
with grpc.insecure_channel('localhost:50051') as channel:
stub = hello_world_pb2_grpc.GreeterStub(channel)
response = stub.SayHello(hello_world_pb2.HelloRequest(name='you'))
print("Greeter client received: " + response.message)


if __name__ == '__main__':
logging.basicConfig()
run()
  • 创建一个 gRPC channel,该通道表示和 gRPC server 的网络连接
  • 使用该 grpc Channel 初始化 helloworld_pb2_grpc.GreeterStub
  • 客户端使用 helloworld_pb2_grpc.GreeterStub 访问 gRPC server 的 Greeter 服务
  • gRPC 框架负责对请求进行序列化、发送请求消息、对应答消息进行反序列化

分别启动 server 和 client,程序运行符合预期:

1
2
3
$ python server.py
$ python client.py
Greeter client received: Hello, you!

Reference