grpc介绍
gRPC 一开始由 google 开发,是一款语言中立、平台中立、开源的远程过程调用(RPC)系统。
在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。因为 gRPC 对 HTTP/2 协议的支持使其在 Android、IOS 等客户端后端服务的开发领域具有良好的前景。gRPC 提供了一种简单的方法来定义服务,同时客户端可以充分利用 HTTP2 stream 的特性,从而有助于节省带宽、降低 TCP 的连接次数、节省CPU的使用等。
特性
-
基于HTTP/2
-
HTTP/2 提供了连接多路复用、双向流、服务器推送、请求优先级、首部压缩等机制。可以节省带宽、降低TCP链接次数、节省CPU,帮助移动设备延长电池寿命等。gRPC 的协议设计上使用了HTTP2 现有的语义,请求和响应的数据使用HTTP Body 发送,其他的控制信息则用Header 表示。
-
IDL使用ProtoBuf
- gRPC使用ProtoBuf来定义服务,ProtoBuf是由Google开发的一种数据序列化协议(类似于XML、JSON、hessian)。ProtoBuf能够将数据进行序列化,并广泛应用在数据存储、通信协议等方面。压缩和传输效率高,语法简单,表达力强。
-
多语言支持 ( C, C++, Python, PHP, Nodejs, C#, Objective-C、Golang、Java)
-
gRPC支持多种语言,并能够基于语言自动生成客户端和服务端功能库。目前已提供了C版本grpc、Java版本grpc-java 和 Go版本grpc-go,其它语言的版本正在积极开发中,其中,grpc支持C、C++、Node.js、Python、Ruby、Objective-C、PHP和C#等语言,grpc-java已经支持Android开发。
gRPC已经应用在Google的云服务和对外提供的API中,其主要应用场景如下:
- 低延迟、高扩展性、分布式的系统
- 同云服务器进行通信的移动应用客户端
- 设计语言独立、高效、精确的新协议
- 便于各方面扩展的分层设计,如认证、负载均衡、日志记录、监控等
gRPC优缺点:
优点:
protobuf二进制消息,性能好/效率高(空间和时间效率都很不错)
proto文件生成目标代码,简单易用
序列化反序列化直接对应程序中的数据类,不需要解析后在进行映射(XML,JSON都是这种方式)
支持向前兼容(新加字段采用默认值)和向后兼容(忽略新加字段),简化升级
支持多种语言(可以把proto文件看做IDL文件)
Netty等一些框架集成
缺点:
grpc坑:
http2只允许单个链接传输10亿流数据。原因在于: htt2使用31位×××标示流,服务端使用奇数,客户端使用偶数,所以总共10亿可用
解决思路:超过一定数量的流,需要重启链接。
gRPC通信方式
gRPC有四种通信方式:
1、 Simple RPC
简单rpc 这就是一般的rpc调用,一个请求对象对应一个返回对象
proto语法:
rpc simpleHello(Person) returns (Result) {}
2、 Server-side streaming RPC
服务端流式rpc 一个请求对象,服务端可以传回多个结果对象
proto语法 :
rpc serverStreamHello(Person) returns (stream Result) {}
3、 Client-side streaming RPC
客户端流式rpc 客户端传入多个请求对象,服务端返回一个响应结果
proto语法 :
rpc clientStreamHello(stream Person) returns (Result) {}
4、 Bidirectional streaming RPC
双向流式rpc 结合客户端流式rpc和服务端流式rpc,可以传入多个对象,返回多个响应对象
proto语法 :
rpc biStreamHello(stream Person) returns (stream Result) {}
服务定义及ProtoBuf
gRPC使用ProtoBuf定义服务, 我们可以一次性的在一个 .proto 文件中定义服务并使用任何支持它的语言去实现客户端和服务器,反过来,它们可以在各种环境中,从云服务器到你自己的平板电脑—— gRPC 帮你解决了不同语言及环境间通信的复杂性。使用 protocol buffers 还能获得其他好处,包括高效的序列号,简单的 IDL 以及容易进行接口更新。
安装:
gRPC 的安装: pip install grpcio
安装 ProtoBuf 相关的 python 依赖库: pip install protobuf
安装 python grpc 的 protobuf 编译工具: pip install grpcio-tools
demo
新建data.proto文件,定义传输的数据格式和grpc服务要实现的函数
syntax = "proto3";
package example;
service FormatData { //定义服务,用在rpc传输中
rpc DoFormat(actionrequest) returns (actionresponse){}
}
message actionrequest {
string text = 1;
}
message actionresponse{
string text=1;
}
生成proto数据的python调用格式和grpc服务接口
在proto文件目录下 调用下列命令
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ./data.proto
会生成:data_pb2.py 与 data_pb2_grpc.py, 其中data_pb2.py是数据格式调用的文件,data_pb2_grpc.py是grpc传输协议接口调用的文件.
创建实现了grpc传输协议的服务器端
在服务器端代码中需要实现proto文件中编写的服务接口,并重写处理函数,将重写后的服务类实例化以后添加到grpc服务器中,这样创建的grpc服务器就可以实现自定义的proto传输服务了
# 实现了 server 端用于接收客户端发送的数据,并对数据进行大写处理后返回给客户端
# ! /usr/bin/env python
# -*- coding: utf-8 -*-
import grpc
import time
from concurrent import futures
from example import data_pb2, data_pb2_grpc
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
_HOST = 'localhost'
_PORT = '8080'
import json
# 实现一个派生类,重写rpc中的接口函数.自动生成的grpc文件中比proto中的服务名称多了一个Servicer
class FormatData(data_pb2_grpc.FormatDataServicer):
# 重写接口函数.输入和输出都是proto中定义的Data类型
def DoFormat(self, request, context):
str = request.text
print(str, type(str))
return data_pb2.actionresponse(text=json.dumps(str.upper())) # 返回一个类实例
def serve():
# 定义服务器并设置最大连接数,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__':
serve()
创建实现能识别proto数据类和实现grpc传输协议.
# 实现了客户端用于发送数据并打印接收到 server 端处理后的数据
# ! /usr/bin/env python
# -*- coding: utf-8 -*-
import grpc
from example import data_pb2, data_pb2_grpc
_HOST = 'localhost'
_PORT = '8080'
import json
def run():
conn = grpc.insecure_channel(_HOST + ':' + _PORT) # 监听频道
print(conn)
client = data_pb2_grpc.FormatDataStub(channel=conn) # 客户端使用Stub类发送请求,参数为频道,为了绑定链接
print(client)
data = {'name': 'xjt', 'age': 18}
response = client.DoFormat(data_pb2.actionrequest(text=json.dumps(data))) # 返回的结果就是proto中定义的类
print("received: " + response.text)
if __name__ == '__main__':
run()
客户端链接的主机号和端口号,必须是服务器创建的主机号和端口号.
先运行服务端,在运行客户端,结果如下:
client.py
server.py
最终目录结构