一、背景介绍:
由于本人最近在研究基于TensorFlow的分布式计算相关的问题。需要基于现有的代码做一些定制化的修改。在学习TensorFlow源码实现的过程中,了解到了关于TensorFlow的通信的方法。由于本人完全是通过网络上查找资料自学的,期间也走了一些弯路。由于网上的帖子多是一些大佬写的,因此对于像本人这样的小白而言,理解起来难免会有一些费劲。因此,写这篇文章来给大家分享一下我自己的心得,也希望能给大家的学习带来一些便利。
二、名词及相关概念:
2.1 Protobuf:
Protobuf 是一种数据交换格式,定义接口和数据类型。可以理解为一种定制化的通讯协议。
由三部分组成:proto 文件: 使用的 proto 语法的文本文件, 用来定义数据格式。
protoc: protobuf 编译器(compile), 将 proto 文件编译成不同语言的实现, 这样不同语言中的数据就可以和 protobuf 格式的数据进行交互。
protobuf 运行时(runtime): protobuf 运行时所需要的库, 和 protoc 编译生成的代码进行交互。
2.2 gRPC:
gRPC是什么可以用官网的一句话来概括A high-performance, open-source universal RPC framework
所谓RPC(remote procedure call 远程过程调用)框架实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从server/client模型。使用的时候客户端调用server端提供的接口就像是调用本地的函数一样。如下图所示就是一个典型的RPC结构图。gRPC通信结构图
2.2.1 使用场景需要对接口进行严格约束的情况,比如我们提供了一个公共的服务,很多人,甚至公司外部的人也可以访问这个服务,这时对于接口我们希望有更加严格的约束,我们不希望客户端给我们传递任意的数据,尤其是考虑到安全性的因素,我们通常需要对接口进行更加严格的约束。这时gRPC就可以通过protobuf来提供严格的接口约束。
对于性能有更高的要求时。有时我们的服务需要传递大量的数据,而又希望不影响我们的性能,这个时候也可以考虑gRPC服务,因为通过protobuf我们可以将数据压缩编码转化为二进制格式,通常传递的数据量要小得多,而且通过http2我们可以实现异步的请求,从而大大提高了通信效率。
2.2.2 grpc 的4 种通信方式:
根据不同的业务场景, grpc 支持 4 种通信方式:客服端一次请求, 服务器一次应答
客服端一次请求, 服务器多次应答(流式)
客服端多次请求(流式), 服务器一次应答
客服端多次请求(流式), 服务器多次应答(流式)
官方提供了一个 route guide service 的 demo, 应用到了这 4 种通信方式, 具体的业务如下:数据源: json 格式的数据源, 存储了很多地点, 每个地点由经纬度(point)和地名(location)组成
通信方式 1: 客户端请求一个地点是否在数据源中
通信方式 2: 客户端指定一个矩形范围(矩形的对角点坐标), 服务器返回这个范围内的地点信息
通信方式 3: 客户端给服务器发送多个地点信息, 服务器返回汇总信息(summary)
通信方式 4: 客户端和服务器使用地点信息 聊天(chat)
三、代码实战:
3.1 代码相关流程:
其中:(filename)_pb2.py: 用来和 protobuf 数据进行交互
(filename)_pb2_grpc.py: 用来和 grpc 进行交互
3.2 配置环境:
# 安装 grpc 相关的 python 模块
pip install grpcio
# 安装 python 下的 protoc 编译器, 使用 protoc 编译 proto 文件, 生成 python 语言的实现
pip install grpcio-tools
编译proto文件:
python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. helloworld.proto
python -m grpc_tools.protoc: python 下的 protoc 编译器通过 python 模块(module) 实现, 所以说这一步非常省心
--python_out=. : 编译生成处理 protobuf 相关的代码的路径, 这里生成到当前目录
--grpc_python_out=. : 编译生成处理 grpc 相关的代码的路径, 这里生成到当前目录
-I. helloworld.proto : proto 文件的路径, 这里的 proto 文件在当前目录
3.3 proto文件代码实现及相关介绍:
// [python quickstart](https://grpc.io/docs/quickstart/python.html#run-a-grpc-application)
// python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. helloworld.proto
// helloworld.proto
syntax = "proto3";
service Greeter {
rpc SayHello(HelloRequest) returns (HelloReply) {}
rpc SayHelloAgain(HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
3.4 服务器端代码实现与介绍:
from concurrent import futures
import time
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
# 实现 proto 文件中定义的 GreeterServicer
class Greeter(helloworld_pb2_grpc.GreeterServicer):
# 实现 proto 文件中定义的 rpc 调用
def SayHello(self, request, context):
return helloworld_pb2.HelloReply(message = 'hello {msg}'.format(msg = request.name))
def SayHelloAgain(self, request, context):
return helloworld_pb2.HelloReply(message='hello {msg}'.format(msg = request.name))
def serve():
# 启动 rpc 服务
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
server.add_insecure_port('[::]:50051')
server.start()
try:
while True:
time.sleep(60*60*24) # one day in seconds
except KeyboardInterrupt:
server.stop(0)
if __name__ == '__main__':
serve()
3.5 客户端代码实现与介绍:
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
def run():
# 连接 rpc 服务器
channel = grpc.insecure_channel('localhost:50051')
# 调用 rpc 服务
stub = helloworld_pb2_grpc.GreeterStub(channel)
response = stub.SayHello(helloworld_pb2.HelloRequest(name='czl'))
print("Greeter client received: " + response.message)
response = stub.SayHelloAgain(helloworld_pb2.HelloRequest(name='daydaygo'))
print("Greeter client received: " + response.message)
if __name__ == '__main__':
run()
参考与引用: