PRC

什么是RPC

远程过程调用(英语:Remote Procedure Call,缩写为 RPC,也叫远程程序调用)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。如果涉及的软件采用面向对象编程,那么远程过程调用亦可称作远程调用远程方法调用

RPC示意图

背景与用途

在单台计算机中,我们可以通过程序调用来传递控制和数据;或者说通过程序调用,我们可以将多个程序组成一个整体来实现某个功能。

如果将这种调用机制推广到多台彼此间可以进行网络通讯的计算机,由多台计算机中的多个程序组成一个整体来实现某个功能,这也是可以的。调用的一方(发起远程过程调用,然后调用这方的环境挂起,参数通过网络传递给被调用方,被调用的一方执行程序,当程序执行完成后,产生的结果再通过网络回传给调用的一方,调用的一方恢复继续执行。这样一种原型思想,就是我们所说的RPC远程过程调用。

RPC这种思想最早可以追溯到1976年,RPC的发展到今天已经40年有余了。

如今的计算机应用中,单机性能上很难承受住产品的压力,需要不断扩充多台机器来提升整体的性能。同时为了充分利用这些集群里的计算机,需要对其从架构上进行划分,以提供不同的服务,服务间相互调用完成整个产品的功能。RPC就能帮助我们解决这些服务间的信息传递和调用。

RPC结构

RPC的设计思想是力图使远程调用中的通讯细节对于使用者透明,调用双方无需关心网络通讯的具体实现。因而实现RPC要进行一定的封装。

RPC原理上是按如下结构流程进行实现的。

RPC结构

流程:

  1. 调用者(Caller, 也叫客户端、Client)以本地调用的方式发起调用;
  2. Client stub(客户端存根,可理解为辅助助手)收到调用后,负责将被调用的方法名、参数等打包编码成特定格式的能进行网络传输的消息体;
  3. Client stub将消息体通过网络发送给对端(服务端)
  4. Server stub(服务端存根,同样可理解为辅助助手)收到通过网络接收到消息后按照相应格式进行拆包解码,获取方法名和参数;
  5. Server stub根据方法名和参数进行本地调用;
  6. 被调用者(Callee,也叫Server)本地调用执行后将结果返回给server stub;
  7. Server stub将返回值打包编码成消息,并通过网络发送给对端(客户端);
  8. Client stub收到消息后,进行拆包解码,返回给Client;
  9. Client得到本次RPC调用的最终结果。

gRPC

  • gRPC是由Google公司开源的高性能RPC框架。

  • gRPC支持多语言

    gRPC原生使用C、Java、Go进行了三种实现,而C语言实现的版本进行封装后又支持C++、C#、Node、ObjC、 Python、Ruby、PHP等开发语言

  • gRPC支持多平台

    支持的平台包括:Linux、Android、iOS、MacOS、Windows

  • gRPC的消息协议使用Google自家开源的Protocol Buffers协议机制(proto3) 序列化

  • gRPC的传输使用HTTP/2标准,支持双向流和连接多路复用

grpc

Protocol Buffers

gRPC的消息协议使用Google自家开源的Protocol Buffers协议机制(proto3) 序列化

详情可看我的另一篇文章 Protocol Buffers

接口定义

接口原型

接口名称: proto_test

调用参数:

1
2
3
4
ProtoTestReuqest:
id # id
test_content # 测试内容

返回数据:

1
2
3
ProtoTestResponse:
callback_id # 返回测试ID
result # 结果

使用Protobuf 定义的接口如下

使用protobuf定义的接口文件通常以proto作为文件后缀名

在rpc目录下新建protoTest.proto文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
syntax = "proto3"; // 在gRPC中推荐使用proto3版本。

message ProtoTestReuqest {
int32 id=1;
string test_content=2;
}

message ProtoTestResponse {
int32 callback_id=1;
string result=2;
}

service ProtoTest {
rpc proto_test(ProtoTestReuqest) returns(ProtoTestResponse) {}
}

代码生成

安装protobuf编译器和grpc库

1
pip install grpcio-tools

编译生成代码

1
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. protoTest.proto
  • -I表示搜索proto文件中被导入文件的目录
  • --python_out表示保存生成Python文件的目录,生成的文件中包含接口定义中的数据类型
  • --grpc_python_out表示保存生成Python文件的目录,生成的文件中包含接口定义中的服务类型

在rpc目录下执行上述命令,会自动生成如下两个rpc调用辅助代码模块:

  • protoTest_pb2.py 保存根据接口定义文件中的数据类型生成的python类
  • protoTest_pb2_grpc.py 保存根据接口定义文件中的服务方法类型生成的python调用RPC方法

服务端

在rpc目录下新建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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import protoTest_pb2
import protoTest_pb2_grpc
import grpc
from concurrent.futures import ThreadPoolExecutor
import time


# rpc接口定义中服务对应成Python的类
class ProtoTestService(protoTest_pb2_grpc.ProtoTestServicer):

# 在接口定义的同名方法中补全,被调用时应该执行的逻辑
def proto_test(self, request, context):
# request是调用的请求数据对象
id = request.id
test_content = request.test_content
print(id,test_content)
response = protoTest_pb2.ProtoTestResponse()
response.callback_id = id+1
response.result = 'grpc结果'
# 最终要返回一个调用结果
return response


def serve():
"""
rpc服务端启动方法
"""
# 创建一个rpc服务器
server = grpc.server(ThreadPoolExecutor(max_workers=10))

# 向服务器中添加被调用的服务方法
protoTest_pb2_grpc.add_ProtoTestServicer_to_server(ProtoTestService(), server)

# 微服务器绑定ip地址和端口
server.add_insecure_port('127.0.0.1:8888')

# 启动rpc服务
server.start()

# start()不会阻塞,此处需要加上循环睡眠 防止程序退出
while True:
time.sleep(100)


if __name__ == '__main__':
serve()

客户端

在rpc目录下新建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
27
28
29
30
import grpc
import protoTest_pb2
import protoTest_pb2_grpc
import time


def feed_test(stub):
# 构建rpc调用的调用参数
request = protoTest_pb2.ProtoTestReuqest()
request.id = 1
request.test_content = '发送grpc'

# 通过stub进行方法调用,并接收调用返回值
ret = stub.proto_test(request)
print(ret.callback_id)
print(ret.result)

def run():
"""
rpc客户端调用的方法
"""
# 使用with语句连接rpc服务器
with grpc.insecure_channel('127.0.0.1:8888') as channel:
# 创建调用rpc远端服务的辅助对象stub
stub = protoTest_pb2_grpc.ProtoTestStub(channel)
# 通过stub进行rpc调用
feed_test(stub)

if __name__ == '__main__':
run()