跳到主要内容
版本:dev

Python gRPC

Fory 可以为定义了服务的 Schema 生成 Python gRPC 服务配套代码。生成的模块使用 grpcio 进行传输,并使用 Fory 序列化请求和响应对象。

当每个 RPC 对等端均由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且你希望结合 gRPC 传输语义与 Fory 载荷编码时,请使用此模式。如果客户端或工具必须直接使用 protobuf 消息字节,请采用标准的 protobuf gRPC 代码生成方式。

Python gRPC 生成默认使用 grpc.aio AsyncIO API。生成的服务端实现基类使用 async def 方法,生成的存根与 grpc.aio.Channel 实例配合使用,流式 RPC 则使用异步可迭代对象。同步 grpcio 配套代码仍可通过 --grpc-python-mode=sync 生成。

安装依赖

请安装 grpciopyfory。生成的配套代码会导入 grpc,在默认模式下还会导入 grpc.aio,但 pyfory 不会将 gRPC 作为硬依赖引入。

pip install pyfory grpcio

定义服务

服务定义可以来自 Fory IDL、protobuf IDL 或 FlatBuffers 的 rpc_service 定义。Fory IDL 服务如下所示:

package demo.greeter;

message HelloRequest {
string name = 1;
}

message HelloReply {
string reply = 1;
}

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}

使用 --grpc 生成 Python 模型和 gRPC 配套代码:

foryc service.fdl --python_out=./generated/python --grpc

对于此 Schema,Python 生成器会生成:

文件用途
demo_greeter.pyFory 数据类和注册辅助函数
demo_greeter_grpc.pygrpc.aio 存根、服务端实现基类和注册函数

模块名由 Fory 包名派生而来,其中的点会替换为下划线。没有包名的 Schema 使用 generated.pygenerated_grpc.py

实现异步服务端

继承生成的服务端实现基类,并将其注册到 grpc.aio 服务器。生成的 Python 方法名使用蛇形命名法,而 gRPC 方法路径会保留原始 IDL 方法名。

import asyncio

import grpc.aio

import demo_greeter
import demo_greeter_grpc


class Greeter(demo_greeter_grpc.GreeterServicer):
async def say_hello(self, request, context):
return demo_greeter.HelloReply(reply=f"Hello, {request.name}")


async def serve():
server = grpc.aio.server()
demo_greeter_grpc.add_servicer(Greeter(), server)
server.add_insecure_port("[::]:50051")
await server.start()
await server.wait_for_termination()


if __name__ == "__main__":
asyncio.run(serve())

生成的配套代码会序列化生成的请求和响应类型,因此服务实现不需要手动注册 Fory 类型。

创建异步客户端

通过 grpc.aio 通道使用生成的存根。生产环境中的客户端通常会传入配置了 TLS/身份认证的通道:

import asyncio

import grpc
import grpc.aio

import demo_greeter
import demo_greeter_grpc


async def main():
credentials = grpc.ssl_channel_credentials()
async with grpc.aio.secure_channel("api.example.com:443", credentials) as channel:
stub = demo_greeter_grpc.GreeterStub(channel)
reply = await stub.say_hello(demo_greeter.HelloRequest(name="Fory"))
print(reply.reply)


if __name__ == "__main__":
asyncio.run(main())

对于本地测试和开发,可以显式使用不安全的通道:

# Test-only channel. Use a TLS/auth-configured grpc.aio.Channel in production.
async with grpc.aio.insecure_channel("localhost:50051") as channel:
stub = demo_greeter_grpc.GreeterStub(channel)

通道选项、凭据、截止时间、元数据、重试和拦截器仍由 grpcio 负责。

流式 RPC

Fory 服务定义可以使用单请求单响应、服务端流式、客户端流式和双向流式 RPC 形式:

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
rpc Chat (stream HelloRequest) returns (stream HelloReply);
}

默认的 Python gRPC 输出遵循 grpc.aio 约定:

IDL 形式服务端实现方法形式存根方法形式
rpc A (Req) returns (Res)async def 返回一个响应对象可等待对象返回一个响应对象
rpc A (Req) returns (stream Res)async def 逐个产出响应对象返回响应的异步迭代器
rpc A (stream Req) returns (Res)消费异步迭代器并返回响应接受请求的异步迭代器
rpc A (stream Req) returns (stream Res)消费异步迭代器并通过异步迭代器逐个产出响应接受并返回异步迭代器

服务端实现方法使用蛇形命名法,而生成的描述符会为 gRPC 路径保留 IDL 中准确的服务名和方法名。

服务端实现使用异步方法和异步迭代:

class Greeter(demo_greeter_grpc.GreeterServicer):
async def lots_of_replies(self, request, context):
yield demo_greeter.HelloReply(reply=f"Hello, {request.name}")
yield demo_greeter.HelloReply(reply=f"Welcome, {request.name}")

async def lots_of_greetings(self, request_iterator, context):
names = []
async for request in request_iterator:
names.append(request.name)
return demo_greeter.HelloReply(reply=", ".join(names))

async def chat(self, request_iterator, context):
async for request in request_iterator:
yield demo_greeter.HelloReply(reply=f"Hello, {request.name}")

生成的客户端使用 grpc.aio 流式调用形式:

credentials = grpc.ssl_channel_credentials()
async with grpc.aio.secure_channel("api.example.com:443", credentials) as channel:
stub = demo_greeter_grpc.GreeterStub(channel)

async for reply in stub.lots_of_replies(
demo_greeter.HelloRequest(name="Fory")
):
print(reply.reply)

async def greeting_requests():
yield demo_greeter.HelloRequest(name="Ada")
yield demo_greeter.HelloRequest(name="Grace")

summary = await stub.lots_of_greetings(greeting_requests())
print(summary.reply)

async def chat_requests():
yield demo_greeter.HelloRequest(name="Fory")
yield demo_greeter.HelloRequest(name="RPC")

async for reply in stub.chat(chat_requests()):
print(reply.reply)

同步模式

对于现有的同步 grpcio 应用或未运行 asyncio 事件循环的环境,请使用同步模式。请显式生成同步配套代码:

foryc service.fdl --python_out=./generated/python --grpc --grpc-python-mode=sync

同步模式会生成相同的 <module>_grpc.py 文件名和公共名称,但服务端实现方法使用普通的 def,应用则使用 grpc.server(...) 和标准 grpc.Channel 实例。

单请求单响应同步服务端示例:

from concurrent import futures

import grpc

import demo_greeter
import demo_greeter_grpc


class Greeter(demo_greeter_grpc.GreeterServicer):
def say_hello(self, request, context):
return demo_greeter.HelloReply(reply=f"Hello, {request.name}")


server = grpc.server(futures.ThreadPoolExecutor(max_workers=8))
demo_greeter_grpc.add_servicer(Greeter(), server)
server.add_insecure_port("[::]:50051")
server.start()
server.wait_for_termination()

单请求单响应同步客户端示例:

import grpc

import demo_greeter
import demo_greeter_grpc


with grpc.insecure_channel("localhost:50051") as channel:
stub = demo_greeter_grpc.GreeterStub(channel)
reply = stub.say_hello(demo_greeter.HelloRequest(name="Fory"))
print(reply.reply)

同步流式调用遵循常规的 grpcio 迭代器和生成器约定。

gRPC 运行时行为

生成的服务配套代码只提供 Fory 序列化回调。运维行为仍遵循标准的 grpcio 行为:

  • 截止时间和取消
  • TLS 和身份认证凭据
  • 客户端和服务端拦截器
  • 状态码、详细信息和元数据
  • 默认模式下的异步事件循环、通道和服务器生命周期
  • 同步模式下服务端的线程池大小

故障排查

ModuleNotFoundError: No module named 'grpc'

请在运行生成的服务模块的环境中安装 grpcio

pip install grpcio

TypeError: Unsupported gRPC servicer type

请将生成的服务端实现子类的实例传给 demo_greeter_grpc.add_servicer(...)。如果模式包含多个服务,生成的注册函数只接受与之匹配的生成服务端实现类型。

UNIMPLEMENTED

请确认已将生成的服务端实现注册到服务器,并且客户端和服务端由相同的包名、服务名和方法名生成。

Protobuf 客户端无法解码服务

Fory gRPC 配套代码不会对消息使用 protobuf 编码格式。请为 Fory 生成的服务使用 Fory 生成的客户端,或者为通用 protobuf 客户端提供单独的 protobuf 服务端点。