GRPC学习笔记
1 GRPC简介
1.1 定义
gRPC(Google Remote Procedure Call,Google远程过程调用)协议是谷歌发布的基于HTTP2协议承载的高性能、通用的RPC开源软件框架,提供了支持多种编程语言的、对网络设备进行配置和管理的方法。
1.2 目的
随着网络复杂化,服务之间远程调用的普遍使用,对远程调用工具的需求也越迫切,gRPC协议应运而生。基于gRPC协议实现对设备的管理已经应用在Telemetry订阅管理,且可以满足大规模、高性能的网络监控需求,逐渐在业界广泛使用。另外,基于gRPC协议实现对设备的查询和配置管理,以便获取设备的异常信息,及时进行网络收敛和业务切换,避免大量丢包导致的业务中断。于是设备提供了一种通过gRPC方式来管理设备的方法,包括配置、查询和能力获取三个方法。这些方法是通过设备和采集器对接,实现采集设备数据的功能。
2 GRPC原理
2.1 GRPC协议框架
2.1.1 gRPC协议栈分层
协议栈分层
各层详细说明
层次 | 说明 |
---|---|
TCP层 | 底层通信协议,基于TCP连接。 |
TLS层 | 该层是可选的,基于TLS加密通道。 |
HTTP2层 | gRPC承载在HTTP2协议上,利用了HTTP2的双向流、流控、头部压缩、单连接上的多路复用请求等特性。 |
gRPC层 | 远程过程调用,定义了远程过程调用的协议交互格式。 |
编码层 | gRPC通过编码格式承载数据,包括GPB(Google Protocol Buffer)编码格式、JSON(JavaScript Object Notation)编码格式。 |
数据模型层 | 业务模块的数据。通信双方需要了解彼此的数据模型,才能正确调用信息。当前设备提供了订阅、配置、查询业务模块。 |
2.2.2 GRPC网络架构
gRPC采用客户端和服务器模型,使用HTTP2协议传输报文。
gRPC网络的工作机制如下:
- 服务器通过监测指定服务端口来等待客户端的连接请求。
- 用户通过执行客户端程序登录到服务器。
- 客户端调用.proto文件提供的gRPC方法发送请求消息。
- 服务器回复应答消息。
2.2.3 Dial-in模式和Dial-out模式
设备在网络架构里支持Dial-in和Dial-out两种对接模式。
-
Dial-in模式:设备作为gRPC服务器,采集器作为gRPC客户端。由采集器主动向设备发起gRPC连接并获取需要采集的数据信息或下发配置。Dial-in模式适用于小规模网络和采集器需要向设备下发配置的场景。
Dial-in模式支持以下操作:
- Subscribe操作:高速采集设备的接口流量统计、CPU和内存等数据信息。当前仅支持基于Telemetry技术的Subscribe操作。
- Get操作:获取设备运行状态和运行配置。当前仅支持基于gNMI(gRPC Network Management Interface)规范的Get操作。
- Capabilities操作:获取设备能力数据。当前仅支持基于gNMI规范的Capabilities操作。
- Set操作:向设备下发配置。当前仅支持基于gNMI规范的Set操作。
-
Dial-out模式:设备作为gRPC客户端,采集器作为gRPC服务器。设备主动和采集器建立gRPC连接,将设备上配置的订阅数据推送给采集器。Dial-out模式适用于网络设备较多的情况下,由设备主动向采集器提供设备数据信息。Dial-out模式只支持基于Telemetry技术的Subscribe操作。
2.2 基于GRPC的订阅原理
2.3 基于gNMI的gRPC数据处理
gRPC支持通过gNMI(gRPC Network Management Interface)规范定义Capabilities、Set和Get、Subscribe方法,为用户提供基于gRPC协议的数据配置与查询功能。
2.3.1 gNMI接口
gRPC支持Capabilities、Get、Set、Subscribe; gNMI的rpc接口如下
service gNMI {
// Capabilities allows the client to retrieve the set of capabilities that
// is supported by the target. This allows the target to validate the
// service version that is implemented and retrieve the set of models that
// the target supports. The models can then be specified in subsequent RPCs
// to restrict the set of data that is utilized.
// Reference: gNMI Specification Section 3.2
rpc Capabilities(CapabilityRequest) returns (CapabilityResponse);
// Retrieve a snapshot of data from the target. A Get RPC requests that the
// target snapshots a subset of the data tree as specified by the paths
// included in the message and serializes this to be returned to the
// client using the specified encoding.
// Reference: gNMI Specification Section 3.3
rpc Get(GetRequest) returns (GetResponse);
// Set allows the client to modify the state of data on the target. The
// paths to modified along with the new values that the client wishes
// to set the value to.
// Reference: gNMI Specification Section 3.4
rpc Set(SetRequest) returns (SetResponse);
// Subscribe allows a client to request the target to send it values
// of particular paths within the data tree. These values may be streamed
// at a particular cadence (STREAM), sent one off on a long-lived channel
// (POLL), or sent as a one-off retrieval (ONCE).
// Reference: gNMI Specification Section 3.5
rpc Subscribe(stream SubscribeRequest) returns (stream SubscribeResponse);
}
gnmi.proto文件链接:gnmi/proto/gnmi/gnmi.proto at master · openconfig/gnmi · GitHub
2.4 参考博客
CloudEngine 16800系列交换机 产品文档 (huawei.com)
3 gRPC的四种通信模式
- RPC有四种通信⽅式,分别是:简单 RPC(Unary RPC)、服务端流式 RPC (Server streaming RPC)、客户端流式 RPC (Clientstreaming RPC)、双向流式 RPC(Bi-directional streaming RPC)。它们主要有以下特点:
服务类型 | 特点 |
---|---|
简单 RPC | ⼀般的rpc调⽤,传⼊⼀个请求对象,返回⼀个返回对象 |
服务端流式 RPC | 传⼊⼀个请求对象,服务端可以返回多个结果对象 |
客户端流式 RPC | 客户端传⼊多个请求对象,服务端返回⼀个结果对象 |
双向流式 RPC | 结合客户端流式RPC和服务端流式RPC,可以传⼊多个请求对象,返回多个结果对象 |
3.1 简单RPC
- 简单rpc 这就是⼀般的rpc调⽤,⼀个请求对象对应⼀个返回对象
- 客户端发起⼀次请求,服务端响应⼀个数据,即标准RPC通信。
- 这种模式,⼀个每⼀次都是发起⼀个独⽴的tcp连接,⾛⼀次三次握⼿和四次挥⼿!
- 这个就是我们基础案例的示例,模式图如下
[](https://images.cnblogs.com/cnblogs_com/Mcoming/2385829/o_240318095709_20220514-image-20220514155737224 .png)
3.2 服务端流式RPC
-
服务端流式rpc ⼀个请求对象,服务端可以传回多个结果对象
-
服务端流 RPC 下,客户端发出⼀个请求,但不会⽴即得到⼀个响应,⽽是在服务端与客户端之间建⽴⼀个单向的流,服务端可以随时向流
中写⼊多个响应消息,最后主动关闭流,⽽客户端需要监听这个流,不断获取响应直到流关闭 -
应⽤场景举例:
- 典型的例⼦是客户端向服务端发送⼀个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端
[](https://images.cnblogs.com/cnblogs_com/Mcoming/2385829/o_240318095709_20220514-image-20220514155832935 .png)
3.3 客户端流RPC
- 客户端流式rpc 客户端传⼊多个请求对象,服务端返回⼀个响应结果
- 应用场景如:物联⽹终端向服务器报送数据
[](https://images.cnblogs.com/cnblogs_com/Mcoming/2385829/o_240318095709_20220514-image-20220514155907457 .png)
3.4 双向流RPC
- 双向流式rpc 结合客户端流式rpc和服务端流式rpc,可以传⼊多个对象,返回多个响应对象
- 应⽤场景:聊天应⽤
[](https://images.cnblogs.com/cnblogs_com/Mcoming/2385829/o_240318095709_20220514-image-20220514194549273 .png)
3.5 实战
stream_grpc.proto 定义
syntax = "proto3";
package example;
service StreamService {
rpc BidirectionalStream(stream ExampleMessage) returns (stream ExampleMessage); // 双向流模式
rpc ClientStream(stream ExampleMessage) returns (ExampleMessage); // 客户端流模式
rpc ServerStream(ExampleMessage) returns (stream ExampleMessage); // 服务端流模式
}
message ExampleMessage {
string data = 1;
}
客户端代码
import time
import grpc
import pb.stream_grpc_pb2 as service_pb2
import pb.stream_grpc_pb2_grpc as service_pb2_grpc
def run():
with grpc.insecure_channel('localhost:50051') as channel:
stub = service_pb2_grpc.StreamServiceStub(channel)
response_iterator = stub.BidirectionalStream(iter([service_pb2.ExampleMessage(data='Hello'),
service_pb2.ExampleMessage(data='Hello2'), service_pb2.ExampleMessage(data='Hello2'),
service_pb2.ExampleMessage(data='Hello4'), service_pb2.ExampleMessage(data='Hello5')
]))
for response in response_iterator:
print(response.data)
def send_stream_data():
for i in range(10) :
yield service_pb2.ExampleMessage(data=f'client send data {i}')
time.sleep(1)
def ClientStream():
with grpc.insecure_channel('localhost:50051') as channel:
stub = service_pb2_grpc.StreamServiceStub(channel)
response = stub.ClientStream(send_stream_data())
print('返回结果', response.data)
def ServerSteam():
with grpc.insecure_channel('localhost:50051') as channel:
stub = service_pb2_grpc.StreamServiceStub(channel)
response = stub.ServerStream(service_pb2.ExampleMessage(data='Start'))
for msg in response:
print('返回结果', msg.data)
def BothStream():
with grpc.insecure_channel('localhost:50051') as channel:
stub = service_pb2_grpc.StreamServiceStub(channel)
response = stub.BidirectionalStream(send_stream_data())
for msg in response:
print('客户端收到:', msg.data)
if __name__ == '__main__':
# run()
# ClientStream()
# ServerSteam()
BothStream()
服务端代码
import time
from concurrent import futures
import grpc
import pb.stream_grpc_pb2 as service_pb2
import pb.stream_grpc_pb2_grpc as service_pb2_grpc
class StreamService(service_pb2_grpc.StreamServiceServicer):
def BidirectionalStream(self, request_iterator, context):
for message in request_iterator:
print('服务端收到:',message.data)
# 处理接收到的消息
# yield service_pb2.ExampleMessage(data=message.data + " response")
for i in range(10):
time.sleep(1)
yield service_pb2.ExampleMessage(data=f'服务端响应给客户端 {i}')
def ClientStream(self, request_iterator, context):
for message in request_iterator:
print(message.data)
return service_pb2.ExampleMessage(data=message.data + " client stream,server not stream")
def ServerStream(self, request, context):
print(request)
for i in range(10):
print(context.is_active())
time.sleep(1)
print('给客户端发送数据:', i)
yield service_pb2.ExampleMessage(data=f'服务端响应 {i}')
print(context.is_active())
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
service_pb2_grpc.add_StreamServiceServicer_to_server(StreamService(), server)
server.add_insecure_port('[::]:50051')
server.start()
try:
server.wait_for_termination()
except KeyboardInterrupt:
server.stop(0)
if __name__ == '__main__':
serve()
3.6 参考博客
grpc python 实战 python grpc stream_mob64ca13faa4e6的技术博客_51CTO博客
gRPC的四种通信模式 - BigSun丶 - 博客园 (cnblogs.com)
server.stop(0)
if name == ‘main’:
serve()
### 3.6 参考博客
[grpc python 实战 python grpc stream_mob64ca13faa4e6的技术博客_51CTO博客](https://blog.51cto.com/u_16213593/7315989)
[gRPC的四种通信模式 - BigSun丶 - 博客园 (cnblogs.com)](https://www.cnblogs.com/Mcoming/p/18080564)