最近在用 python 写项目,项目中有许多 AI 服务需要相互调用,为了服务之间的解耦合,想尝试采用 gRPC 来进行服务之间相互调用。之所以考虑采用 gRPC 这种方式来在不同服务之间传递数据,主要是出于对于性能有更高的要求的考虑。而且 AI 的服务需要传递大量的数据(图像数据),而又希望不影响性能,所以最终想尝试采用 gRPC,具体来看一下 gRPC 是如何解决性能问题。在 gRPC 中,通过 protobuf ,可以将数据压缩编码转化为二进制格式,通常传递的数据量要小得多,而且通过 http2 ,可以实现异步的请求,从而大大提高了通信效率。说了这么多我们看一看如何使用 gRPC,
RPC
RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务。如果 http 就是一种远程调用的协议。这里提到了远程过程调用,那么对于远程就应该有本地过程调用那么什么是本地过程调用,也就是调用本地方法对本地的对象进行操作。例如调用 Add 方法向列表中添加一个对象。而远程过程调用,向列表添加对象方法并不在本地,调用远程提供 Add() 来实现该操作。
安装
pip install grpcio-tools
pip install grpc
快速入门
其实首先我们需要定义 service ,这里类似我们对服务说明书,这个文件以proto扩展名结尾,在其中定义服务(service),在服务中提供方法(rpc) 每一个方法有两参数一个请求体参数,一个是响应体参数,这里点和我们平时写 http 服务很相似,还需要定义写请求体和响应体的数据类型。总体来看类似一个说明书,把 rpc 服务相关的内容都记录这里,然后就可以通过 grpc_tools.protoc 工具会根据这个proto文件生成两个 python 文件。
定义服务(service)
service 关键字随后是服务名称,通常准照驼峰命名规则。
service HelloWord{
}
接下来在 service 内定义方法,方法名前需要添加关键字 rpc 表示,在 grpc 中提供了 4 种类型的方法供我们来选择使用。
比较简单 RPC 方法是客户端使用 stub 向服务端发起请求,然后等待服务端响应返回值,这个和一般我们了解的 web 请求没有什么区别
rpc GetFeature(Point) returns (Feature) {}
response-streaming RPC
方式是客户端发起请求到服务端后,服务端处理请求会以流形式返回消息给客户端,客户端通过流的形式读取返回信息。要实现这种方式请求,与普通请求不同之处需要在响应体前添加 stream
rpc ListFeatures(Rectangle) returns (stream Feature) {}
request-streaming RPC
方式是指在客户端将以序列形式组织要送的消息,然后以流形式发送给客户端,一旦客户端完成写入消息到序列后,就只需要等待服务端返回值,在这种请求方式,需要在请求体前添加 stream
rpc RecordRoute(stream Point) returns (RouteSummary) {}
bidirectionally-streaming RPC
通过读写流实现服务端和客户端之间通讯,服务端和客户端可以按一定顺序相互读取消息。服务端可以在接收所有客户端消息后,返回响应给客户端,这样可以交替在客户端和服务器端间读写消息,要实现双向通讯,需要在请求体和响应体之前都添加关键字 stream
$ python -m grpc_tools.protoc -I../../protos --python_out=. --grpc_python_out=. ../../protos/route_guide.proto
运行命令会自动生成route_guide_pb2.py
和 route_guide_pb2_grpc.py
两个文件
其中生成来 route_guide.proto
中定义的 message
类和定义的 service
类,代码中 RouteGuideStub
用于在客户端调用 RouteGuide RPC
提供的功能,在 RouteGuideServicer
中定义了 RouteGuide
服务需要实现方法,route_guide.proto
中定义 add_RouteGuideServicer_to_server
用于将 RouteGuideServicer
服务添加到 grpc
中,
创建服务端(server)
接下来是看如何创建服务端,要创建一个服务端有两件事需要做:
需要实现之前定义好的接口方法,在其中实现具体逻辑
Request-streaming RPC
运行 gRPC 服务来监听客户端请求,然后返回响应值
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
route_guide_pb2_grpc.add_RouteGuideServicer_to_server(
RouteGuideServicer(), server)
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()
在 start() 方法是无阻塞的,因为会实例化一个新的线程来处理客户端的请求,在启动后不会同时做其他任务,
创建客户端(client)
有了服务端,接下来就可以创建向客户端发起请求的的客户端,如果在客户端想要调用服务端的方法,就需要在客户端stub
,可以实例化一个 RouteGuideStub
,这个方法是由刚刚使用命令通过 proto 文件生成的route_guide_pb2_grpc
所提供的方法。
channel = grpc.insecure_channel('localhost:50051')
stub = route_guide_pb2_grpc.RouteGuideStub(channel)
对于返回单个响应的 RPC 方法(响应方法),grpc python 同时支持同步(阻塞)和异步(非阻塞)两种方式来控制方法间数据传输。对于响应流RPC 的方法,调用立即返回响应迭代器。调用迭代器的 next() 方法来获取值。
网友评论