• 微信公众号:美女很有趣。 工作之余,放松一下,关注即送10G+美女照片!

「gRPC」- 学习笔记 @20210322

互联网 diligentman 2个月前 (03-22) 22次浏览

问题描述

我们认为是从 DevOps 盛行之后,运维也开始写代码(应该说比以前写的更多),诞生运维开发岗位(应该说越来越多)。比如说,开发 Prometheus Exporter 成为运维的工作(其实以前都是现有的,工作都是别人做好的,基本不用开发,或者简单配置即可实现)。

现在,我们需要为我们自己的应用开发 Prometheus Exporter,但是应用提供 gRPC 接口,所以运维就要看学习如何使用 gRPC 框架。

该笔记将记录:gRPC 的快速入门方法、学习笔记,以及相关问题处理(该笔记是我们对 gRPC 的学习笔记,因此仅记录基础入门相关的内容)。

解决方案

我们通过学习 gRPC 的使用流程,来理解它是如何工作的:
1)编写 .proto 文件:该文件包含 RPC 方法、数据结构等等信息
2)生成桩代码:使用命令 protoc 生成桩代码(能够生成多种语言,比如 Go C++ Java Python C# 等等)。“桩代码”类似于“类库”。
3)编写服务端:在程序中,使用这些类库,编写服务端,实现 RPC 方法。然后,运行该服务端,监听 TCP 端口,等待请求;
4)编写客户端:在程序中,使用这些类库,编写客户端,该客户端连接服务端,并调用这些 RPC 方法,接收服务端返回结果;

在调用时,各个部分之间的关系,可以使用下图解释:

「gRPC」- 学习笔记 @20210322

我们使用 Python 语言,因此重点使用 Python 示例。但是,使用其他语言也是可以的,并且只要使用 gRPC 框架,鉴于 gRPC 也是网络通信,所以多个语言之间是互通的。

学习路线图

官方文档已经提供详细的示例,是最好的教程。跟着官方文档,一边学习,一边练习,已经足够。

对于 Python 使用 gRPC 框架,如果需要快速入门,建议按照以下顺序阅读文档
1)针对 gRPC 介绍:Introduction to gRPC | gRPC
2)使用 gRPC 框架的入门示例(Python):Quick start | Python | gRPC
3)使用 gRPC 框架的完整示例(Python):Basics tutorial | Python | gRPC

常用文档(Python):
1)Welcome to gRPC Python’s documentation! — gRPC Python 1.36.1 documentation
2)Language Guide  |  Protocol Buffers  |  Google Developers
3)关于 gRPC 概念及术语:Core concepts, architecture and lifecycle | gRPC

入门案例

因为我们使用 Python 语言,所以这里的示例使用 Python 环境。虽然与其他语言不同,但是流程是类似的。

准备工作(安装环境)

# 为了不影响主机环境,我们选择在 Virtual Environment 中操作
# 既然大家已经开始学习 gRPC 框架,我们相信虚拟环境等工具应该信手拈来,所以该笔记未进一步介绍虚拟环境
mkvirtualenv "grpc"
workon "grpc"

# 安装 gRPC 及工具
pip install grpcio grpcio-tools

第一阶段、定义服务

定义 helloworld.proto 文件:

syntax = "proto3";

package helloworld;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

该文件定义“客户端与服务端的通信协议”:
1)站在服务端的角度:届时服务端将实现 Greeter 服务,该服务提供 SayHello 方法,参数为 HelloRequest 结构,返回 HelloReply 结构;
2)站在客户端的角度:届时客户端将调用 Greeter 服务的 SayHello 方法,传入 HelloRequest 结构,获得 HelloReply 结构的响应;

第二阶段、创建桩代码

helloworld.proto 文件定义协议,但是无法直接使用,需要生成桩代码(类库)。

使用已有的 Protocal Buffers 定义来创建桩代码:

# 创建桩代码
python -m grpc_tools.protoc 
    --proto_path=./ 
    --python_out=. --grpc++_python_out=. 
    ./helloworld.proto

当如上命令执行后,将生成如下文件:
1)helloworld_pb2.py:该文件包含我们在“协议”中定义的数据结构、服务信息等等(多用于客户端)。
2)helloworld_pb2_grpc.py:该文件包含 gRPC 相关的方法(多用于服务端)

命令行参数的说明:
1)–proto_path:指定 .proto 查找路径(类似于 Java 的 CLASSPATH,Python 的 PYTHONPATH)。代码通常会写在不同文件中,然后相互引用(import、include),所以需要指定搜索路径,这就是该选项的作用。当然,我们的示例只使用 helloworld.proto,未使用其他文件,所以在这里是多余的。
2)–python_out:helloworld_pb2.py 的输出路径
3)–grpc++_python_out:文件 helloworld_pb2_grpc.py 的输出路径
4)helloworld.proto:我们的协议文件

第三阶段、编写服务端

定义 helloworld_server.py 文件:

from concurrent import futures

import grpc

import helloworld_pb2
import helloworld_pb2_grpc


class Greeter(helloworld_pb2_grpc.GreeterServicer):

    def SayHello(self, request, context):
        return helloworld_pb2.HelloReply(message='Hello, %s!' % request.name)


if __name__ == '__main__':
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    server.wait_for_termination()

class Greeter(helloworld_pb2_grpc.GreeterServicer):
1)服务端应该提供服务,所以它要实现 Greeter 服务,继承自 GreeterServicer 类(这基本是固定格式)。然后,实现 SayHello 方法。
2)SayHello 方法的 request 参数则为 HelloRequest 结构,通过 request.name 获取 name 属性;
3)正如协议的定义,该方法返回 HelloReply 结构,该结构包含 message 参数。从代码上看:HelloReply 以方法的形式出现;它的属性以方法参数形式出现;而不是直接传入 HelloReply 结构。

然后,main 方法启动 gRPC 服务,监听 50051 端口,等待客户端的访问。

最后,我们需要启动这个服务端,否则后面的客户端无法连接:python ./helloworld_server.py

第四阶段、编写客户端

定义 helloworld_client.py 文件:

from __future__ import print_function

import grpc

import helloworld_pb2
import helloworld_pb2_grpc
from google.protobuf.json_format import MessageToJson
        
if __name__ == '__main__':
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = helloworld_pb2_grpc.GreeterStub(channel)
        response = stub.SayHello(helloworld_pb2.HelloRequest(name='you'))
        print("Greeter client received: " + response.message)
		print(MessageToJson(response, including_default_value_fields=True))

MessageToJson():将返回转为 Json 字符串类型
including_default_value_fields=True:如果字段为默认值,是不会在 Client 和 Server 之间传递的。需要该属性,才能显示默认字段。

该示例代码的流程(基本都是这样的流程):
1)连接 gRPC 服务;
2)设置桩代码使用该连接;
3)发送请求,调用那 SayHello 方法,并传入 HelloRequest 结构(当然,这里依旧是以方法的形式来体现数据结构);

运行该客户端,将获取到服务端的输出:

# python3 helloworld_client.py
Greeter client received: Hello, you!
{
  "message": "Hello, you!"
}

至此,一个简单的 gRPC 示例就运行成功了。

常见问题处理

ModuleNotFoundError: No module named ‘c++ommon_pb2’

ImportError: No module named ‘InternalMessage_pb2’ · Issue #3430 · protocolbuffers/protobuf

问题描述

Traceback (most recent call last):                                    
  File "agent.py", line 22, in <module>                 
    import controller_pb2 as Controller                                                    
  File "/srv/http/agent/controller_pb2.py", line 14, in <module>
    import common_pb2 as common__pb2                                                    
ModuleNotFoundError: No module named 'common_pb2'

问题原因:所有的 .proto 都要参与桩代码的生成。

解决方案:注意在创建桩代码时,我们使用 *.proto 就是为了将所有 .proto 定义参与桩代码的生成。

相关文章

「gRPC」- 4.Packages and Build Tools
「gRPC」- 05.DEBUGING

参考文献

Introduction to gRPC | gRPC
Quick start | Python | gRPC
Protobuf to json in python – Stack Overflow
python – Protobuf doesn’t serialize default values – Stack Overflow
python – Getting all field names from a protocol buffer? – Stack Overflow

展开阅读全文

python

© 著作权归作者所有

举报

打赏

0


0 收藏

微信
QQ
微博

分享

作者的其它热门文章

「Shell」- 判断字符串结尾 @20210121
「开源视频管理系统」- 搭建属于自己的视频站点 @20210118
「Jenkins Pipeline」- 执行 Shell 命令 @20210224
「NGINX Ingress Controller」- 修改默认重定向状态码 @20210129


程序员灯塔
转载请注明原文链接:「gRPC」- 学习笔记 @20210322
喜欢 (0)