RPC是远程过程调用(Remote Procedure Call)的缩写形式。RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息的到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用过程接收答复信息,获得进程结果,然后调用执行继续进行。
gRPC 是Google开源的一款高性能 RPC 框架,基于 ProtoBuf 序列化协议进行开发,支持多种语言。下面介绍Python语言的gRPC通信示例。
使用案例:小写字符串转换为大写字符串。目录结构如下:
pip install protobuf
pip install grpcio
pip install grpcio-tools
data.proto 为protobuf格式文件,定义了数据类以及远程调用方法。
syntax = "proto3";
package base_package;
service FormatData { //定义服务,用在rpc传输中
rpc DoFormat(actionrequest) returns (actionresponse){}
}
message actionrequest {
string text = 1;
}
message actionresponse{
string text=1;
}
在Linux平台中运行:
python -m grpc_tools.protoc -I. --python_out=./base_package --grpc_python_out=./base_package ./data.proto
在 base_package 文件夹下生成 data_pb2.py 和 data_pb2_grpc.py,其中 data_pb2.py 为数据格式文件,data_pb2_grpc.py 为gRPC方法文件。
服务端具体实现了proto文件中定义的方法 DoFormat,将实现后的服务类添加到gRPC服务端中,当服务端接收到客户端要调用的函数名时通过反射机制执行具体的方法并返回执行结果。
import grpc
import time
from concurrent import futures
from base_package import data_pb2, data_pb2_grpc
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
_HOST = 'localhost'
_PORT = '8080'
class FormatData(data_pb2_grpc.FormatDataServicer):
# 重写接口函数
def DoFormat(self, request, context):
str = request.text
return data_pb2.actionresponse(text=str.upper()) # 返回一个类实例
def server():
# 定义服务器并设置最大连接数,corcurrent.futures是一个并发库,类似于线程池的概念
grpcServer = grpc.server(futures.ThreadPoolExecutor(max_workers=4)) # 创建一个服务器
data_pb2_grpc.add_FormatDataServicer_to_server(FormatData(), grpcServer) # 在服务器中添加派生的接口服务(自己实现了处理函数)
grpcServer.add_insecure_port(_HOST + ':' + _PORT) # 添加监听端口
grpcServer.start() # 启动服务器
try:
while True:
time.sleep(_ONE_DAY_IN_SECONDS)
except KeyboardInterrupt:
grpcServer.stop(0) # 关闭服务器
if __name__ == '__main__':
server()
客户端连接gRPC服务端后,即可进行远程过程调用。
import grpc
from base_package import data_pb2, data_pb2_grpc
_HOST = 'localhost'
_PORT = '8080'
def run():
conn = grpc.insecure_channel(_HOST + ':' + _PORT) # 监听频道
client = data_pb2_grpc.FormatDataStub(channel=conn) # 客户端使用Stub类发送请求,参数为频道,为了绑定链接
response = client.DoFormat(data_pb2.actionrequest(text='hello,world!')) # 返回的结果就是proto中定义的类
print("received: " + response.text)
if __name__ == '__main__':
run()
输出结果:
received: HELLO,WORLD!
一个完整的RPC架构里面包含了四个核心的组件,分别是Client,Client Stub,Server以及Server Stub,这个Stub可以理解为存根。
(1) 客户端(client)以本地调用方式(即以接口的方式)调用服务;
(2) 客户端存根(client stub)接收到调用后,负责将方法、参数等组装成能够进行网络传输的消息体(将消息体对象序列化为二进制);
(3) 客户端通过sockets将消息发送到服务端;
(4) 服务端存根( server stub)收到消息后进行解码(将消息对象反序列化);
(5) 服务端存根( server stub)根据解码结果调用本地的服务;
(6) 本地服务执行并将结果返回给服务端存根( server stub);
(7) 服务端存根( server stub)将返回结果打包成消息(将结果消息对象序列化);
(8) 服务端(server)通过sockets将消息发送到客户端;
(9) 客户端存根(client stub)接收到结果消息,并进行解码(将结果消息反序列化);
(10) 客户端(client)得到最终结果。
RPC的目标是要把2、3、4、7、8、9这些步骤都封装起来。
作用
优势
1、一般使用长链接,不必每次通信都要3次握手,减少网络开销
2、一般都有注册中心,有丰富的监控管理
3、发布、下线接口、动态扩展等,对调用方来说是无感知、统一化的操作
4、协议私密,安全性较高
5、rpc 能做到协议更简单内容更小,效率更高
6、rpc 是面向服务的更高级的抽象,支持服务注册发现,负载均衡,超时重试,熔断降级等高级特性。
1、传输协议:
RPC:可以基于HTTP协议,也可以基于TCP协议
HTTP:基于HTTP协议
从网络协议来说,Http协议与Rpc同属于应用层, 他们的底层都是tcp协议。RPC和HTTP他们最本质的区别,就是RPC主要工作在TCP协议之上,而HTTP服务主要是工作在HTTP协议之上,我们都知道HTTP协议是在传输层协议TCP之上的,所以效率来看的话,RPC当然是要更胜一筹。
2、传输效率:
RPC:使用自定义的TCP协议,可以让请求报文体积更小,或者使用HTTP2协议,也可以很好的减小报文体积,提高传输效率
HTTP:如果是基于http1.1的协议,请求中会包含很多无用的内容,如果是基于HTTP2.0,那么简单的封装下可以作为一个RPC来使用,这时标准的RPC框架更多的是服务治理。
http协议其实是属于面向桌面浏览器的一个通信协议,对于缓存,幂等或者Cookies相关的方面做了很多的事情。但是对于服务器之间直接的交互,Rpc就能够体现出来他的优势了。自定义协议,减少数据传输:我们大概看一下http协议。请求行,请求头部,请求数据,空行。很明显对于远程调用场景,我们对于请求行的依赖不是特别的强,那么这一部分在我们应用场景下,将会成为负担,但是http协议又是固定的,我们也不可能随便修改协议的格式。所以,通过rpc协议我们可以精简请求的数据,来尽可能少的传输我们的数据。当前,rpc也可以通过http协议来进行传输。
3、性能消耗:
RPC:可以基于protobuf实现高效的二进制传输
HTTP:大部分是基于json实现的,字节大小和序列化耗时都比protobuf要更消耗性能。
4、负载均衡:
RPC:基本自带了负载均衡策略
HTTP:需要配置Nginx、HAProxy配置
5、服务治理:(下游服务新增,重启,下线时如何不影响上游调用者)
RPC:能做到自动通知,不影响上游
HTTP:需要事先通知,如修改NGINX配置。
6、连接:
RPC:长连接
HTTP:短连接
rpc使用长连接:直接基于socket进行连接,不用每个请求都重新走三次握手的流程。
RPC服务一般用于集群内部微服务之间的通信,如果需要对外暴露服务一般会提供等价的HTTP REST接口。
Python语言实现gRPC通信 (在Linux环境下生成proto文件!在windows下面执行错误了)
RPC原理解析
Python——gRPC详解及实战避坑方案(上)
什么是RPC
http与rpc对比