网络自动化运维 Help

7-1 gRPC框架

本节要求:根据步骤能运行出结果,原理能理解多少算多少。

gRPC框架介绍

RPC

RPC: (远程过程调用Remote Procedure Calls)框架。基于TCP/IP协议。主要组件有:

  • 客户端(Client),服务的调用方。

  • 服务端(Server),真正的服务提供者。

  • 客户端存根,存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。

  • 服务端存根,接收客户端发送过来的消息,将消息解包,并调用本地的方法。

过去主要用在C/S架构,主要用在不同应用程序之间的调用。上一章的netconf协议底层就是rpc协议。

g RPC

gRPC:(Google RPC Remote Procedure Calls)是一个高性能、开源和通用的RPC(远程过程调用)框架,由Google开发。 它允许客户端应用程序直接调用不同服务器上的、不同编程语言开发的应用程序中的方法,就像调用本地对象一样,使得构建分布式应用程序和服务更加容易。

以下是 gRPC 框架的一些关键特点和优点:

  • 高性能:gRPC基于HTTP/2协议,它提供了多路复用、头部压缩等特性,显著提高了通信效率。 使用Protocol Buffer作为接口定义语言和数据序列化格式,这是一种高效的二进制序列化协议。

  • 跨语言:支持多种编程语言,包括 C++、Java、Python、Go、C# 等。使得不同语言编写的应用程序可以轻松地进行通信。

  • 强类型:使用Protocol Buffers定义服务接口,这是一种强类型的数据描述语言,有助于提高代码的可靠性。

  • 支持多种调用模式:

    • 简单RPC(Simple RPC):客户端发送一个请求,服务器返回一个响应。

    • 服务器端流式 RPC(Server-side Streaming RPC):客户端发送一个请求,服务器返回一个数据流。

    • 客户端流式 RPC(Client-side Streaming RPC):客户端发送一个数据流,服务器返回一个响应。

    • 双向流式 RPC(Bidirectional Streaming RPC):客户端和服务器都可以发送数据流。

  • 代码生成:使用 Protocol Buffers 编译器(protoc)根据 .proto 文件自动生成客户端和服务器端的代码,简化了开发过程。

7 1g rpc

Protocol Buffers数据结构定义语法

Protocol Buffers: 直译过来是"协议缓冲",没有找到确定的中文翻译,暂不翻译。其实它代表一种数据结构定义语言。 这种格式类似XML和JSON,与后端语言无关。但不太一样的是它自身相当于一种用于定义数据结构的编程语言,目标是生成目标平台编程语言编写的数据结构。

优点是跨平台,缺点是多了些学习成本和多一层转化。

我给出一个定义,它是一种脱离后端语言内置数据结构定义语言。我想到了一个比喻:Protocol Buffers之于Java、Python,相当于Sass之于CSS(前端软件开发人员会明白这个类比)。 场景:微服务、中间件,在一家大公司的不同自应用间传输数据。
其它替代的rpc框架:JsonRPC、XMLRPC等。

入门例子

gRPC-官网文档
gRPC-Python部分入门教程
gRPC-protocol buffer部分
gRPC-protocol buffer部分 版本proto3

  1. 安装grpcio(负责框架通讯)和grpcio-tools(负责从.proto文件生成目标代码)两个库。

    # Windows终端Powershell下运行 pip install grpcio pip install grpcio-tools
  2. 创建一个名为student.proto的文件。student包含两个属性和一个方法。在proto buffer中称为定义属性和服务。

    syntax = "proto3"; package student; // 定义 Student 消息 message Student { string name = 1; int32 age = 2; } // 定义服务 service StudentService { rpc SayHello(Student) returns (HelloResponse); } // 定义响应消息 message HelloResponse { string message = 1; }

    一些说明:

    • syntax = "proto3";语法版本。

    • package student;定义一个包名/命名空间,文件多的时候不容易命名冲突。

    • 消息 ,理解为"请求"。 响应消息 ,理解为"响应"。 服务把请求、响应、数据结构绑定在一起,可以在传输数据时作验证。

    • string name = 1;有静态类型。数字不是赋值1的意思而是标识符。每个字段的标识符必须是唯一的。使用数字标识符来标识二进制数据中的字段,这样,即使字段的名称发生变化,数据仍然可以正确解析。 使用字段标识符而不是字段名称来编码数据,从而减少了数据的大小,提高了传输效率。理解为多一层映射。在Json和xml中,键名、标签名重复出现,这里多一层映射能大大减少传输流量。 1 ➡️ name ➡️ 小明。

  3. 生成数据结构文件(后端编程语言为Python) 使用protoc (protoc compiler)编译器生成目标后端语言代码(这里生成的是Python代码)。.protoc ➡️.py。
    在Pycharm内置的Windows终端下运行,因为会自动cd到项目目录下,确保参数中的相对路径不会出错。

    python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. student.proto

    运行后将生成两个文件: student_pb2.pystudent_pb2_grpc.py 。当然,.proto是种脱离于后端语言的中间语言,因此也可以生成Java、JS等其它目标语言。

    7 1proto

    参数解释:

    • -m ,Python常用的运行方式是交互式解释器和运行脚本python hello.py ,在脚本中可能import其它的内置库或第三方库。 而-m会直接运行一个库中的默认方法,这个方法是一个库中的最基本最常用的功能,不需要创建脚本和import,算是一种便捷方式。试试python -m http.server快速开启Web服务。 grpc_tools.protoc包含ProtocolBuffers的编译器。

    • -I.指定.proto文件的搜索路径。 .表示当前文件夹下。

    • --python_out=.结果数据结构文件输出到当前文件夹下。 --grpc_python_out=.grpc服务结果文件输出到当前文件夹下。

    • student.proto要编译的 .proto 文件的名称。

  4. 实现服务端
    创建一个名为server.py的文件,实现StudentService服务。

    import grpc from concurrent import futures import time import student_pb2 import student_pb2_grpc class StudentService(student_pb2_grpc.StudentServiceServicer): def SayHello(self, request, context): return student_pb2.HelloResponse(message=f"Hello, {request.name}! You are {request.age} years old.") def serve(): server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) student_pb2_grpc.add_StudentServiceServicer_to_server(StudentService(), server) server.add_insecure_port('[::]:50051') server.start() print("Server is running on port 50051...") try: while True: time.sleep(86400) # Keep the server running except KeyboardInterrupt: server.stop(0) if __name__ == '__main__': serve()

    一些解释:

    • from concurrent import futures futures.ThreadPoolExecutor(max_workers=10) 多线程。(课外)详见Python并发编程实战:优雅地使用 concurrent.futures

    • import student_pb2 import student_pb2_grpc 引入刚才生成的两个文件。

    • class StudentService 服务类,相当于Django框架中的视图方法,接受请求,响应内容,接受和响应时的数据结构都必须符合student_pb2即student.protoc中规定的数据结构。

    • server.add_insecure_port('[::]:50051') ipv6中的localhost地址,这句代码实际上也会开启ipv4上的本地监听,端口是框架习惯上默认的,即服务监听地址127.0.0.1:50051

  5. 实现客户端
    创建一个名为client.py的文件。

    import grpc import student_pb2 import student_pb2_grpc def run(): with grpc.insecure_channel('localhost:50051') as channel: stub = student_pb2_grpc.StudentServiceStub(channel) student = student_pb2.Student(name="Alice", age=20) response = stub.SayHello(student) print(response.message) if __name__ == '__main__': run()

    一些解释:客户端连接服务器地址127.0.0.1:50051, student_pb2.Student(name="Alice", age=25) 实例化了一个student,从这里也可以看出.protoc文件定义了对象结构,因此生成的.py文件中包含Student类而不需要自己写。

  6. 测试 首先启动服务端。服务端是监听程序、阻塞的、不会结束的。

    7 1

    再启动客户端。客户端会向服务端发送一个消息。

    🎉接收到服务端传来的响应消息 Response received: Hello, Alice! You are 25 years old. 为成功。

    7 1

课外

课本上在介绍gRPC框架后直接给出了Huawei S5700设备的.proto文件的生成结果文件.py,但文件生成时间太早跟当前gRPC框架版本不匹配,所以有了本节的框架入门例子。
如果接触过Java EE/Django、django-restful、FastAPI这种Web开发框架的演变,对强制静态类型、数据结构校验、序列化,会更理解gRPC框架的设计。这里相当于在不够了解原理的情况下,直接学了顶级工具的使用。

08 四月 2025