gRPC 入门教程

文章目录
  1. 1. 参考资料
  2. 2. 概念简介
    1. 2.1. 什么是 RPC?
  3. 3. 例说 gRPC
    1. 3.1. 传输图片
      1. 3.1.1. 准备工作
      2. 3.1.2. 解读
      3. 3.1.3. 总结
      4. 3.1.4. 本节参考
  4. 4. 性能测试
    1. 4.1. SimSwap

gRPC 是一个由 Google 开发的跨语言 RPC 支持库。本文系 gRPC 的入门向导型文章。

参考资料

这次直接把参考资料放在第一小节,喜欢自己阅读文档起手的朋友可以直接先看文档。

概念简介

什么是 RPC?

RPC,你可以理解为是调用一个远程函数。

例说 gRPC

传输图片

准备工作

开始之前,先把演示用的仓库 clone 下来:

1
git clone https://github.com/jonblearn/simple-gRPC.git

用 VSCode 打开 ./grpc 文件夹,创建虚拟环境、并安装依赖:

1
2
python -m venv env
pip install grpcio grpcio-tools numpy

解读

示例项目假设的需求情景类似于「拍立淘」:client 拍一张照片,通过 gRPC 传给 server,server 透过深度学习模型识别图上的是什么商品,把结果通过 gRPC 还给 client。

在示例项目中,下图黑笔划掉的文件是 gRPC 自动生成的、剩下的才是程序员自己写的:

目录结构

每一个用到 gRPC 的项目都需要一个 proto 文件,用以定义 gRPC 里面承载的远程函数与数据类型。

打开 image_procedure.proto 文件。其中英文是原作者的注释,中文是酱瓜加的注释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
syntax = "proto3";		// 表明该文件是 proto3 格式, 抄就是了

// input image, width, height
message B64Image {
string b64image = 1; // 这里的数字 1/2/3 只是代表该对象序列化后,
int32 width = 2; // 各变量存在于序列字符串里面的相对位置,
int32 height = 3; // 未必要从 1 开始, 只要有相对大小顺序即可.
}

// output prediction
message Prediction {
int32 channel = 4;
float mean = 5;
}

// service
service ImageProcedure {
rpc ImageMeanWH(B64Image) returns (Prediction) {}
}

在终端中执行下面这行命令,就可以让 gRPC 从 proto 文件生成对应的 Python 下的数据格式定义文件 *_pb2*.py。这两个自动生成的文件后面在 client/server 中直接调用即可,不需要做任何修改。

1
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. image_procedure.proto

然后看 server.py:

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
26
27
# ...
# 虽然是一个小示例, 但作者也进行了模块化开发
import image_procedure

# import the generated classes
import image_procedure_pb2
import image_procedure_pb2_grpc

# based on .proto service
class ImageProcedureServicer(image_procedure_pb2_grpc.ImageProcedureServicer):
def ImageMeanWH(self, request, context):
response = image_procedure_pb2.Prediction()
response.channel, response.mean = image_procedure.predict(request.b64image, request.width, request.height)
return response

# create a gRPC server
server = grpc.server(futures.ThreadPoolExecutor(max_workers=12))

# add the defined class to the server
image_procedure_pb2_grpc.add_ImageProcedureServicer_to_server(
ImageProcedureServicer(), server)

# listen on port 5005
print('Starting server. Listening on port 5005.')
server.add_insecure_port('[::]:5005')
server.start()
# ...

最后是 client.py:

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
26
# ...
# import the generated classes
import image_procedure_pb2
import image_procedure_pb2_grpc
# ...

# open a gRPC channel
channel = grpc.insecure_channel('127.0.0.1:5005')

# create a stub (client)
stub = image_procedure_pb2_grpc.ImageProcedureStub(channel)

# encoding image/numpy array
for _ in range(1000):
frame = np.random.randint(0,255, (416,416,3), dtype=np.uint8) # dummy rgb image

# compress
# 之所以把 frame 复制一份成 data, 其实是为了日后能加入压缩功能做准备
data = frame # zlib.compress(frame)
data = base64.b64encode(data)

# create a valid request message
image_req = image_procedure_pb2.B64Image(b64image = data, width = 416, height = 416)

# make the call
response = stub.ImageMeanWH(image_req)

真正对图像进行处理的模块是 image_procedure.py:

1
2
3
4
5
6
# A procedure which decodes base64 image, runs some machine learning model/ operation(s) (in our case we'll just return the mean of the pixel value)
def predict(b64img_compressed, w, h):
b64decoded = base64.b64decode(b64img_compressed)
decompressed = b64decoded # zlib.decompress(b64decoded)
imgarr = np.frombuffer(decompressed, dtype=np.uint8).reshape(w, h, -1)
return imgarr.shape[2], np.mean(imgarr)

总结

用 gRPC 传图片,发图的一方需要对图片进行编码、收图的一方需要先对图片进行解码,具体代码片段如下。

发送前编码

1
2
3
4
# 发送前编码
data = np.zeros((height, width, 3), np.uint8) # RGB
data[:] = frame
data64 = base64.b64encode(data)

然后是一些不重要的代码:

1
2
3
4
# 构造 gRPC 对象
image_req = image_procedure_pb2.B64Image(b64image=data64, width=width, height=height)
# 发起 RPC 调用, 返回值存在 response 中
response = stub.SimSwap(image_req)

接收后解码的通用代码段:

1
2
3
# 接收后解码
decompressed = base64.b64decode(response.b64image)
imgarr = np.frombuffer(decompressed, dtype=np.uint8).reshape(height, width, -1)

下面这句话意在表达 imgarr 可以直接保存成文件:

1
2
# 保存到文件
cv2.imwrite("./grpc_test/{}.jpg".format(i), imgarr)

本节参考

性能测试

SimSwap

测试一是 SimSwap 处理一张图片的时间。

具体参数:

  • pic_a 是 6.jpg,pic_b 是 ds.jpg
  • gRPC 客户端和服务器均开在同一个机器上,即 127.0.0.1
  • 分别处理 10 次,取平均值

结果如下:

  • 本地处理平均耗时:1.1012424230575562 s
  • gRPC 处理平均耗时:1.1019452095031739 s

可见,加入 gRPC 并没有显著提高延迟。