GRPC学习笔记

发布于:2024-04-25 ⋅ 阅读:(30) ⋅ 点赞:(0)

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两种对接模式。

  1. 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操作。
  2. 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连接,⾛⼀次三次握⼿和四次挥⼿!
  • 这个就是我们基础案例的示例,模式图如下

[image-20220514155737224](https://images.cnblogs.com/cnblogs_com/Mcoming/2385829/o_240318095709_20220514-image-20220514155737224 .png)

3.2 服务端流式RPC

  • 服务端流式rpc ⼀个请求对象,服务端可以传回多个结果对象

  • 服务端流 RPC 下,客户端发出⼀个请求,但不会⽴即得到⼀个响应,⽽是在服务端与客户端之间建⽴⼀个单向的流,服务端可以随时向流
    中写⼊多个响应消息,最后主动关闭流,⽽客户端需要监听这个流,不断获取响应直到流关闭

  • 应⽤场景举例:

    • 典型的例⼦是客户端向服务端发送⼀个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端

[image-20220514155832935](https://images.cnblogs.com/cnblogs_com/Mcoming/2385829/o_240318095709_20220514-image-20220514155832935 .png)

3.3 客户端流RPC

  • 客户端流式rpc 客户端传⼊多个请求对象,服务端返回⼀个响应结果
  • 应用场景如:物联⽹终端向服务器报送数据

[image-20220514155907457](https://images.cnblogs.com/cnblogs_com/Mcoming/2385829/o_240318095709_20220514-image-20220514155907457 .png)

3.4 双向流RPC

  • 双向流式rpc 结合客户端流式rpc和服务端流式rpc,可以传⼊多个对象,返回多个响应对象
  • 应⽤场景:聊天应⽤

[image-20220514194549273](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)