Skip to content

gRPC

Python grpc 代码生成与目录结构

python -m grpc_tools.protoc -I./protos --python_out=. --grpc_python_out=.  ./protos/**/*.proto
# betterproto
python -m grpc_tools.protoc -I./protos --python_betterproto_out=./lib  ./protos/**/*.proto

例如, 有存在以下的目录结构

src
├── core
├── protos
│    └── grpcs
│        └── toolbox
│            └── toolbox.proto
├── grpcs
│    └── toolbox
│        ├── toolbox_pb2.py
│        └── toolbox_pb2_grpc.py

需要注意的时, 生成的 *_pb2_grpc.py 会导入对应的 *_pb2.py , 而导入的模块路径, 是与 {proto_path} 下的目录结构一致.

如将 src/protos 作为 {proto_path} , 那么 toolbox_pb2_grpc.pytoolbox_pb2.py 的导入语句如下

from grpcs.toolbox import toolbox_pb2 as grpcs_dot_toolbox_dot_toolbox__pb2

除了保持 src/protos/grpcs/ 目录结构与 src/grpcs 一致外, 另外的解决方案是通过 PYTHONPATHsys.path , 将 *_pb2.py 所在的目录添加到 Python 解释器的包寻找路径.

Python grpc 服务启动管理

一种策略是每个 *_pb2_grpc.py 文件所在的目录, 存在一个启动服务的入口文件.

这种实现下, 每个服务单独部署, 符合微服务的接口最小职责定义.

但是考虑到实际需求, 比如方便本地测试部署, 那么将多个微服务挂载到一个服务上, 有是有必要的.

思路是每个 *_pb2_grpc.py 文件所在的目录, 对外暴露一个 service.py , 提供 add_servicer_to_serve 的回调, 将对应的服务下的方法绑定的要启动的服务上.

在统一的服务启动入口, 通过配置, 去加载不同服务下的 service.py , 并调用 add_servicer_to_serve 回调.

def add_servicer_to_serve(server: _Server):
    toolbox_pb2_grpc.add_toolboxServicer_to_server(Toolbox(), server)

Python grpc 客户端管理

Grpc 客户端通过 insecure_channel 获得 channel 对象, channel 对象绑定了服务的地址.

每个 *_pb2_grpc.py , 提供了 *Stub 方法, 获得一个 stub 对象, 封装了可调用的 rpc 方法.

对于连接的每个服务地址, 仅有在第一次发起 rpc 调用的时候, 才会建立真正的连接.

所以对于每个内部服务, 启动后, 可以加载并保存所有微服务的 channelstub 对象.

这么做既方便了客户端代码的维护, 又没有额外的网络开销.

FAQ

Mac Apple Silicon 下安装 grpcio 编译异常

尝试在导出以下环境变量后继续安装依赖

export GRPC_PYTHON_BUILD_SYSTEM_ZLIB=true
export GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true

grpc-python 客户端未指定端口导致的请求异常

grpc-python 客户端中使用 grpc.insecure_channel 访问目标时, 如果未指定 target 的端口, 则默认使用 443 端口进行访问.

通过 {host}:{port} 指定端口的格式, 会更加明确.