从零开始学python | 使用 gRPC 的 Python 微服务 I

举报
Yuchuan 发表于 2021/08/17 08:41:26 2021/08/17
【摘要】 微服务是组织复杂软件系统的一种方式。您不是将所有代码都放在一个应用程序中,而是将应用程序分解为独立部署并相互通信的微服务。本教程将教您如何使用最流行的框架之一 gRPC 启动和运行 Python 微服务。

目录

微服务是组织复杂软件系统的一种方式。您不是将所有代码都放在一个应用程序中,而是将应用程序分解为独立部署并相互通信的微服务。本教程将教您如何使用最流行的框架之一 gRPC 启动和运行 Python 微服务。

很好地实现微服务框架很重要。当您构建一个支持关键应用程序的框架时,您必须确保它的健壮性和对开发人员的友好性。在本教程中,您将学习如何做到这一点。这些知识将使您对成长中的公司更有价值。

为了从本教程中获益最多,您应该了解PythonWeb 应用程序基础知识。如果您想复习这些内容,请先通读提供的链接。

在本教程结束时,您将能够:

  • 在 Python 中实现通过 gRPC 相互通信的微服务
  • 实现中间件来监控微服务
  • 单元测试集成测试您的微服务和中间件
  • 使用Kubernetes 将微服务部署到 Python 生产环境

您可以通过单击以下链接下载本教程中使用的所有源代码:

为什么是微服务?

想象一下,您在 Online Books For You 工作,这是一个在线销售图书的流行电子商务网站。该公司拥有数百名开发人员。每个开发人员都在为某些产品或后端功能编写代码,例如管理用户的购物车、生成推荐、处理付款交易或处理仓库库存。

现在问问自己,您是否希望在一个巨大的应用程序中包含所有这些代码?这有多难理解?测试需要多长时间?您将如何保持代码和数据库模式的健全?这肯定会很困难,尤其是当企业试图快速发展时。

难道您不希望与模块化产品功能相对应的代码是模块化的吗?用于管理购物车的购物车微服务。用于管理库存的库存微服务。

在下面的部分中,您将更深入地了解将 Python 代码分离为微服务的一些原因。

模块化

代码更改通常采用阻力最小的路径。您心爱的 Online Books For You CEO 希望添加新的买两本书送一功能。您是被要求尽快启动它的团队的一员。看看当所有代码都在一个应用程序中时会发生什么。

作为团队中最聪明的工程师,您提到可以向购物车逻辑添加一些代码来检查购物车中是否有两本书以上。如果是这样,您可以简单地从购物车总数中减去最便宜的书的成本。不费吹灰之力——你提出一个拉取请求。

然后你的产品经理说你需要跟踪这个活动对图书销售的影响。这也很简单。由于实现买二送一功能的逻辑在购物车代码中,因此您将在结帐流程中添加一行,更新交易数据库中的新列以指示销售是促销活动的一部分:buy_two_get_one_free_promo = true。完毕。

接下来,您的产品经理会提醒您,该交易仅对每位客户使用一次有效。您需要添加一些逻辑来检查是否有任何先前的事务buy_two_get_one_free_promo设置了该标志。哦,您需要隐藏主页上的促销横幅,因此您也添加了该支票。哦,您需要向未使用促销的人发送电子邮件。加上那个。

几年后,事务数据库变得太大,需要用新的共享数据库替换。所有这些引用都需要更改。不幸的是,此时数据库在整个代码库中都被引用。您认为添加所有这些引用实际上有点容易了。

这就是为什么将所有代码放在一个应用程序中从长远来看会很危险的原因。有时,有界限是件好事。

交易数据库应该只能由交易微服务访问。然后,如果您需要扩展它,那还不错。代码的其他部分可以通过隐藏实现细节的抽象 API 与事务交互。您可以在单个应用程序中执行此操作 — 只是不太可能。代码更改通常采用阻力最小的路径。

灵活性

将 Python 代码拆分为微服务可为您提供更大的灵活性。一方面,您可以用不同的语言编写微服务。通常,公司的第一个 Web 应用程序将使用RubyPHP 编写。这并不意味着其他一切也必须如此!

您还可以独立扩展每个微服务。在本教程中,您将使用一个 Web 应用程序和一个 Recommendations 微服务作为运行示例。

您的 Web 应用程序可能会受I/O 限制,从数据库中获取数据,并可能从磁盘加载模板或其他文件。推荐微服务可能会进行大量的数字运算,使其受 CPU 限制。在不同的硬件上运行这两个 Python 微服务是有意义的。

稳健性

如果您的所有代码都在一个应用程序中,那么您必须一次部署所有代码。这是一个很大的风险!这意味着对一小部分代码的更改可能会导致整个站点瘫痪。

所有权

当一个代码库被很多人共享时,代码的架构通常没有清晰的愿景。在员工来来去去的大公司中尤其如此。可能有人对代码的外观有远见,但是当任何人都可以修改它并且每个人都在快速移动时,很难强制执行。

微服务的好处之一是团队可以清楚地拥有自己的代码。这使得更有可能对代码有一个清晰的愿景,并且代码将保持干净和有条理。它还明确了谁负责向代码添加功能或在出现问题时进行更改。

“微”有多小?

微服务应该有多小是可以在工程师之间引发激烈争论的话题之一。这是我的两分钱:微型是用词不当。我们应该只说服务。但是,在本教程中,您将看到用于一致性的微服务

使微服务太小会导致问题。首先,它实际上违背了使代码模块化的目的。在微服务的代码应该是有意义在一起,就像在数据和方法有意义起来。

要使用类作为类比,请考虑filePython 中的对象。该file对象具有您需要的所有方法。你可以.read().write()它,或者你可以,.readlines()如果你想。你不应该需要一个FileReader和一个FileWriter类。也许你熟悉这样做的语言,也许你一直认为这有点麻烦和混乱。

微服务是一样的。代码的范围应该是正确的。不要太大,也不要太小。

其次,微服务比单体代码更难测试。如果开发人员想要测试跨越多个微服务的功能,那么他们需要在他们的开发环境中启动并运行所有这些功能。这增加了摩擦。有几个微服务还不错,但如果有几十个,那么这将是一个重大问题。

调整微服务规模是一门艺术。需要注意的一件事是每个团队都应该拥有合理数量的微服务。如果您的团队有 5 个人但有 20 个微服务,那么这是一个危险信号。另一方面,如果您的团队只处理一个由其他五个团队共享的微服务,那么这也可能是一个问题。

不要仅仅为了它而使微服务尽可能小。一些微服务可能很大。但是当一个微服务在做两个或更多完全不相关的事情时要小心。这通常是因为向现有微服务添加不相关的功能是阻力最小的路径,而不是因为它属于那里。

以下是您可以将假设的在线书店分解为微服务的一些方法:

  • Marketplace为用户提供导航站点的逻辑。
  • 购物车跟踪用户放入购物车的内容和结帐流程。
  • 交易处理付款处理和发送收据。
  • 库存提供有关哪些图书有库存的数据。
  • 用户帐户管理用户注册和帐户详细信息,例如更改他们的密码。
  • 评论存储用户输入的图书评分和评论。

这些只是几个例子,并不是详尽的清单。但是,您可以看到其中的每一个都可能由自己的团队拥有,并且每个的逻辑都是相对独立的。此外,如果 Reviews 微服务的部署存在导致其崩溃的错误,那么尽管无法加载评论,用户仍然可以使用该站点并进行购买。

微服务与单体的权衡

微服务并不总是比将所有代码保存在一个应用程序中的单体应用更好。一般来说,尤其是在软件开发生命周期的开始阶段,单体应用会让你更快地行动。它们使共享代码和添加功能变得不那么复杂,并且只需部署一项服务,您就可以快速将您的应用程序提供给用户。

权衡是,随着复杂性的增加,所有这些都会逐渐使单体应用更难开发、部署更慢、更脆弱。实施单体应用可能会在前期为您节省时间和精力,但稍后它可能会再次困扰您。

在 Python 中实现微服务可能会在短期内花费你的时间和精力,但如果做得好,从长远来看,它可以让你更好地扩展。当然,当速度最有价值时,过早实施微服务可能会减慢您的速度。

典型的硅谷启动周期是从一个整体开始,以便在企业找到适合客户的产品时实现快速迭代。在公司有了成功的产品并雇佣了更多的工程师之后,是时候开始考虑微服务了。不要太早实施它们,但不要等待太久。

有关微服务与单体的权衡的更多信息,请观看 Sam Newman 和 Martin Fowler 的精彩讨论,何时使用微服务(以及何时不使用!)

示例微服务

在本节中,您将为您的 Online Books For You 网站定义一些微服务。你会定义一个API,他们和编写Python代码实现他们的微服务,当您浏览本教程。

为了使事情易于管理,您将只定义两个微服务:

  1. Marketplace将是一个非常小的网络应用程序,它向用户显示书籍列表。
  2. 推荐将是一个微服务,它提供用户可能感兴趣的书籍列表。

这是一个图表,显示了您的用户如何与微服务交互:

市场和推荐微服务架构

可以看到,用户将通过浏览器与 Marketplace 微服务进行交互,Marketplace 微服务将与 Recommendations 微服务进行交互。

考虑一下 Recommendations API。您希望推荐请求具有一些功能:

  • 用户 ID:您可以使用它来个性化推荐。但是,为简单起见,本教程中的所有建议都是随机的。
  • 图书类别:为了使 API 更有趣,您将添加图书类别,例如神秘、自助等。
  • 最大结果:您不想退回所有库存图书,因此您需要为请求添加一个限制。

响应将是书籍列表。每本书都会有以下数据:

  • 图书 ID:图书的唯一数字 ID。
  • 书名:您可以向用户显示的书名。

一个真正的网站会有更多的数据,但为了这个例子,你将限制功能的数量。

现在你可以更正式地定义这个 API,使用协议缓冲区的语法:

syntax = "proto3";
 2
 3enum BookCategory {
 4    MYSTERY = 0;
 5    SCIENCE_FICTION = 1;
 6    SELF_HELP = 2;
 7}
 8
 9message RecommendationRequest {
10    int32 user_id = 1;
11    BookCategory category = 2;
12    int32 max_results = 3;
13}
14
15message BookRecommendation {
16    int32 id = 1;
17    string title = 2;
18}
19
20message RecommendationResponse {
21    repeated BookRecommendation recommendations = 1;
22}
23
24service Recommendations {
25    rpc Recommend (RecommendationRequest) returns (RecommendationResponse);
26}

协议缓冲区文件声明您的 API。协议缓冲区由 Google 开发,提供了一种正式指定 API 的方法。起初这可能看起来有点神秘,所以这里是逐行细分:

  • 第 1 行指定文件使用proto3语法而不是旧proto2版本。

  • 第 3 行到第 7 行定义您的图书类别,并且每个类别还分配有一个数字 ID。

  • 第 9 到 13 行定义了您的 API 请求。Amessage包含字段,每个字段都具有特定类型。您正在使用int32,它是一个 32 位整数,用于user_IDmax_results字段。您还使用BookCategory上面定义的枚举作为category类型。除了每个字段都有一个名称之外,它还分配了一个数字字段 ID。你可以暂时忽略这个。

  • 第 15 到 18 行定义了一种新类型,您可以将其用于图书推荐。它有一个 32 位整数 ID 和一个基于字符串的标题。

  • 第 20 到 22 行定义了您的 Recommendations 微服务响应。注意repeated关键字,它表示响应实际上有一个BookRecommendation对象列表。

  • 第 24 到 26 行定义了 API的方法。您可以将其视为类上的函数或方法。它需要 aRecommendationRequest并返回 a RecommendationResponse

rpc代表远程过程调用。您很快就会看到,您可以像在 Python 中调用普通函数一样调用 RPC。但是 RPC 的实现是在另一台服务器上执行的,这就是远程过程调用的原因。

为什么是 RPC 和协议缓冲区?

好的,那么为什么要使用这种正式语法来定义 API?如果你想从一个微服务向另一个微服务发出请求,难道你不能只发出一个HTTP 请求并得到一个 JSON 响应吗?好吧,你可以这样做,但使用协议缓冲区有好处。

文档

使用协议缓冲区的第一个好处是它们为您的 API 提供了一个定义良好且自文档化的架构。如果您使用 JSON,那么您必须记录它包含的字段及其类型。与任何文档一样,您面临文档不准确、不完整或过时的风险。

当您使用协议缓冲区语言编写 API 时,您可以从中生成 Python 代码。您的代码永远不会与您的文档不同步。文档很好,但自文档化的代码更好。

验证

第二个好处是,当您从协议缓冲区生成 Python 代码时,您可以免费获得一些基本验证。例如,生成的代码不会接受错误类型的字段。生成的代码还内置了所有 RPC 样板。

如果您的 API 使用 HTTP 和 JSON,那么您需要编写一些代码来构造请求、发送请求、等待响应、检查状态代码以及解析和验证响应。使用协议缓冲区,您可以生成看起来像常规函数调用但在后台执行网络请求的代码。

您可以使用 HTTP 和 JSON 框架(例如SwaggerRAML)获得这些相同的好处。有关 Swagger 运行的示例,请查看Python REST APIs With Flask、Connexion 和 SQLAlchemy

那么是否有理由使用 gRPC 而不是其中一种替代方案?答案仍然是肯定的。

表现

gRPC 框架通常比使用典型的 HTTP 请求更有效。gRPC 建立在HTTP/2之上,它可以以线程安全的方式在长期连接上并行发出多个请求。连接设置相对较慢,因此执行一次并在多个请求之间共享连接可以节省时间。gRPC 消息也是二进制的,比 JSON 小。此外,HTTP/2 具有内置的标头压缩。

gRPC 内置了对流式请求和响应的支持。它将比基本的 HTTP 连接更优雅地管理网络问题,即使在长时间断开连接后也会自动重新连接。它还具有拦截器,您将在本教程的后面部分了解。您甚至可以为生成的代码实现插件,人们已经这样做以输出Python 类型提示。基本上,您可以免费获得许多出色的基础设施!

开发人员友好

许多人更喜欢 gRPC 而不是 REST 的最有趣的原因可能是您可以根据函数而不是 HTTP 动词和资源来定义您的 API 。作为一名工程师,您习惯于从函数调用的角度进行思考,这正是 gRPC API 的样子。

将功能映射到 REST API 通常很尴尬。您必须决定您的资源是什么、如何构建路径以及使用哪些动词。通常有多种选择,例如如何嵌套资源或是否使用 POST 或其他一些动词。REST 与 gRPC 可能会变成关于偏好争论。一种并不总是比另一种更好,因此请使用最适合您的用例的方法。

严格来说,protocol buffers是指两个微服务之间发送的数据的序列化格式。因此协议缓冲区类似于 JSON 或 XML,因为它们是格式化数据的方式。与 JSON 不同,协议缓冲区具有严格的架构,并且在通过网络发送时更加紧凑。

另一方面,RPC 基础设施实际上称为gRPC或 Google RPC。这更类似于 HTTP。事实上,如上所述,gRPC 是建立在 HTTP/2 之上的。

示例实现

在讨论了协议缓冲区之后,是时候看看它们能做什么了。术语协议缓冲区是一口口水,因此您将看到本教程中使用的常见速记protobuf

正如多次提到的,您可以从 protobufs 生成 Python 代码。该工具作为grpcio-tools软件包的一部分安装。

首先,定义您的初始目录结构:

.
├── protobufs/
│   └── recommendations.proto
|
└── recommendations/

protobufs/目录将包含一个名为recommendations.proto. 这个文件的内容就是上面的protobuf代码。为方便起见,您可以通过展开下面的可折叠部分来查看代码:

完整recommendations.proto代码显示隐藏

您将生成 Python 代码以在recommendations/目录中与它进行交互。首先,您必须安装grpcio-tools. 创建文件recommendations/requirements.txt并添加以下内容:

grpcio-tools ~= 1.30

要在本地运行代码,您需要将依赖项安装到虚拟环境中。以下命令将在 Windows 上安装依赖项:

C:\ python -m venv venv
C:\ venv\Scripts\activate.bat
(venv) C:\ python -m pip install -r requirements.txt

在 Linux 和 macOS 上,使用以下命令创建虚拟环境并安装依赖项:

$ python3 -m venv venv
$ source venv/bin/activate  # Linux/macOS only
(venv) $ python -m pip install -r requirements.txt

现在,要从 protobufs 生成 Python 代码,请运行以下命令:

$ cd recommendations
$ python -m grpc_tools.protoc -I ../protobufs --python_out=. \
         --grpc_python_out=. ../protobufs/recommendations.proto

这将从.proto文件中生成多个 Python文件。这是一个细分:

  • python -m grpc_tools.protoc 运行 protobuf 编译器,它将从 protobuf 代码生成 Python 代码。
  • -I ../protobufs告诉编译器在哪里可以找到您的 protobuf 代码导入的文件。您实际上并没有使用导入功能,但-I仍然需要该标志。
  • --python_out=. --grpc_python_out=.告诉编译器在哪里输出 Python 文件。您很快就会看到,它将生成两个文件,如果您愿意,您可以将每个文件放在带有这些选项的单独目录中。
  • ../protobufs/recommendations.proto 是 protobuf 文件的路径,它将用于生成 Python 代码。

如果您查看生成的内容,您将看到两个文件:

$ ls
recommendations_pb2.py recommendations_pb2_grpc.py

这些文件包括与 API 交互的 Python 类型和函数。编译器将生成调用 RPC 的客户端代码和实现 RPC 的服务器代码。您将首先查看客户端。

RPC 客户端

生成的代码只有主板才会喜欢。也就是说,它不是很漂亮的Python。这是因为它并不真正适合人类阅读。打开 Python shell 以查看如何与之交互:

>>>
>>> from recommendations_pb2 import BookCategory, RecommendationRequest
>>> request = RecommendationRequest(
...     user_id=1, category=BookCategory.SCIENCE_FICTION, max_results=3
... )
>>> request.category
1

可以看到 protobuf 编译器生成了与你的 protobuf 类型对应的 Python 类型。到现在为止还挺好。您还可以看到对字段进行了一些类型检查:

>>> request = RecommendationRequest(
...     user_id="oops", category=BookCategory.SCIENCE_FICTION, max_results=3
... )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'oops' has type str, but expected one of: int, long

这表明如果将错误的类型传递给 protobuf 字段之一,则会收到TypeError

一个重要的注意事项是所有字段proto3都是可选的,因此您需要验证它们是否都已设置。如果未设置,则数字类型默认为零,字符串默认为空字符串:

>>>
>>> request = RecommendationRequest(
...     user_id=1, category=BookCategory.SCIENCE_FICTION
... )
>>> request.max_results
0

0是因为这是未设置int字段的默认值。

虽然 protobuf 会为您进行类型检查,但您仍然需要验证实际值。因此,当您实现您的 Recommendations 微服务时,您应该验证所有字段是否具有良好的数据。无论您使用 protobufs、JSON 还是其他任何东西,这对于任何服务器都是如此。始终验证输入。

recommendations_pb2.py为您生成的文件包含类型定义。该recommendations_pb2_grpc.py文件包含客户端和服务器的框架。看一下创建客户端所需的导入:

>>>
>>> import grpc
>>> from recommendations_pb2_grpc import RecommendationsStub

导入grpc模块,该模块提供了一些用于设置与远程服务器的连接的功能。然后导入 RPC 客户端存根。之所以称为存根,是因为客户端本身没有任何功能。它调用远程服务器并将结果传回。

如果你回顾一下你的 protobuf 定义,那么你会看到最后的service Recommendations {...}部分。protobuf 编译器采用此微服务名称 ,Recommendations并将Stub其附加以形成客户端名称RecommendationsStub

现在您可以发出 RPC 请求:

>>> channel = grpc.insecure_channel("localhost:50051")
>>> client = RecommendationsStub(channel)
>>> request = RecommendationRequest(
...     user_id=1, category=BookCategory.SCIENCE_FICTION, max_results=3
... )
>>> client.Recommend(request)
Traceback (most recent call last):
  ...
grpc._channel._InactiveRpcError: <_InactiveRpcError of RPC that terminated with:
    status = StatusCode.UNAVAILABLE
    details = "failed to connect to all addresses"
    ...

localhost在端口 上创建到您自己的机器的连接50051。此端口是 gRPC 的标准端口,但您可以根据需要更改它。您现在将使用一个不安全的通道,它是未经身份验证和未加密的,但您将在本教程后面学习如何使用安全通道。然后您将此通道传递给您的存根以实例化您的客户端。

您现在可以调用Recommend您在Recommendations微服务上定义的方法。回想线25在protobuf的定义:rpc Recommend (...) returns (...)。这就是Recommend方法的来源。你会得到一个异常,因为实际上没有微服务在运行localhost:50051,所以你接下来要实现它!

现在您已经整理好了客户端,您将查看服务器端。

RPC 服务器

在控制台中测试客户端是一回事,但在那里实现服务器有点多。您可以让控制台保持打开状态,但您将在文件中实现微服务。

从导入和一些数据开始:

# recommendations/recommendations.py
 2from concurrent import futures
 3import random
 4
 5import grpc
 6
 7from recommendations_pb2 import (
 8    BookCategory,
 9    BookRecommendation,
10    RecommendationResponse,
11)
12import recommendations_pb2_grpc
13
14books_by_category = {
15    BookCategory.MYSTERY: [
16        BookRecommendation(id=1, title="The Maltese Falcon"),
17        BookRecommendation(id=2, title="Murder on the Orient Express"),
18        BookRecommendation(id=3, title="The Hound of the Baskervilles"),
19    ],
20    BookCategory.SCIENCE_FICTION: [
21        BookRecommendation(
22            id=4, title="The Hitchhiker's Guide to the Galaxy"
23        ),
24        BookRecommendation(id=5, title="Ender's Game"),
25        BookRecommendation(id=6, title="The Dune Chronicles"),
26    ],
27    BookCategory.SELF_HELP: [
28        BookRecommendation(
29            id=7, title="The 7 Habits of Highly Effective People"
30        ),
31        BookRecommendation(
32            id=8, title="How to Win Friends and Influence People"
33        ),
34        BookRecommendation(id=9, title="Man's Search for Meaning"),
35    ],
36}

此代码导入您的依赖项并创建一些示例数据。这是一个细分:

  • 第 2 行导入,futures因为 gRPC 需要一个线程池。稍后你会谈到这一点。
  • 第 3 行导入,random因为您将随机选择书籍进行推荐。
  • 第 14 行创建books_by_category 字典,其中键是书籍类别,值是该类别的书籍列表。在真正的推荐微服务中,书籍将存储在数据库中。

接下来,您将创建一个实现微服务功能的类:

class RecommendationService(
30    recommendations_pb2_grpc.RecommendationsServicer
31):
32    def Recommend(self, request, context):
33        if request.category not in books_by_category:
34            context.abort(grpc.StatusCode.NOT_FOUND, "Category not found")
35
36        books_for_category = books_by_category[request.category]
37        num_results = min(request.max_results, len(books_for_category))
38        books_to_recommend = random.sample(
39            books_for_category, num_results
40        )
41
42        return RecommendationResponse(recommendations=books_to_recommend)

您已经创建了一个具有实现RecommendRPC的方法的类。以下是详细信息:

  • 第 29 行定义了RecommendationService类。这是您的微服务的实现。请注意,您将RecommendationsServicer. 这是您需要做的与 gRPC 集成的一部分。

  • 第 32行在Recommend()您的类上定义了一个方法。这必须与您在 protobuf 文件中定义的 RPC 具有相同的名称。它也接受 aRecommendationRequest并返回 aRecommendationResponse就像在 protobuf 定义中一样。它还需要一个context参数。该上下文允许您设置的状态代码的响应。

  • 线33和34的使用abort()结束请求和状态代码设置NOT_FOUND,如果你得到意想不到的类别。由于 gRPC 建立在 HTTP/2 之上,因此状态码类似于标准的 HTTP 状态码。设置它允许客户端根据它收到的代码采取不同的行动。它还允许中间件,如监控系统,记录有多少请求有错误。

  • 第 36 到 40 行从给定类别中随机挑选一些书籍进行推荐。您确保将推荐数量限制为max_results。你min()用来确保你不要求更多的书,否则random.sample会出错。

  • 第 38 行 返回一个RecommendationResponse包含您的书籍推荐列表的对象。

请注意,在错误条件下引发异常会更好,而不是abort()像您在此示例中那样使用,但是响应不会正确设置状态代码。有一种方法可以解决这个问题,您将在本教程后面查看拦截器时进行介绍。

RecommendationService类定义你的微服务实现,但你仍然需要运行它。这就是serve()它的作用:

def serve():
42    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
43    recommendations_pb2_grpc.add_RecommendationsServicer_to_server(
44        RecommendationService(), server
45    )
46    server.add_insecure_port("[::]:50051")
47    server.start()
48    server.wait_for_termination()
49
50
51if __name__ == "__main__":
52    serve()

serve() 启动网络服务器并使用您的微服务类来处理请求:

  • 第 42 行创建了一个 gRPC 服务器。你告诉它使用10线程来处理请求,这对于这个演示来说完全是矫枉过正,但对于实际的 Python 微服务来说是一个很好的默认设置。
  • 第 43行将您的类与服务器相关联。这就像为请求添加一个处理程序。
  • 第 46 行告诉服务器在端口上运行50051。如前所述,这是 gRPC 的标准端口,但您可以使用任何您喜欢的端口。
  • 第 47 和 48 行调用server.start()server.wait_for_termination()启动微服务并等待它停止。在这种情况下停止它的唯一方法是在终端中键入Ctrl+C。在生产环境中,有更好的关闭方法,您将在稍后介绍。

在不关闭用于测试客户端的终端的情况下,打开一个新终端并运行以下命令:

$ python recommendations.py

这将运行 Recommendations 微服务,以便您可以在一些实际数据上测试客户端。现在返回到您用来测试客户端的终端,以便您可以创建通道存根。如果您将控制台保持打开状态,那么您可以跳过导入,但在此重复它们作为复习:

>>>
>>> import grpc
>>> from recommendations_pb2_grpc import RecommendationsStub
>>> channel = grpc.insecure_channel("localhost:50051")
>>> client = RecommendationsStub(channel)

现在您有了一个客户端对象,您可以发出请求:

>>> request = RecommendationRequest(
...    user_id=1, category=BookCategory.SCIENCE_FICTION, max_results=3)
>>> client.Recommend(request)
recommendations {
  id: 6
  title: "The Dune Chronicles"
}
recommendations {
  id: 4
  title: "The Hitchhiker\'s Guide To The Galaxy"
}
recommendations {
  id: 5
  title: "Ender\'s Game"
}

有用!您向您的微服务发出了 RPC 请求并得到了响应!请注意,您看到的输出可能会有所不同,因为推荐是随机选择的。

现在您已经实现了服务器,您可以实现 Marketplace 微服务并让它调用 Recommendations 微服务。如果您愿意,现在可以关闭 Python 控制台,但让 Recommendations 微服务保持运行。

捆绑在一起

为您的 Marketplace 微服务创建一个新marketplace/目录并marketplace.py在其中放置一个文件。您的目录树现在应如下所示:

.
├── marketplace/
│   ├── marketplace.py
│   ├── requirements.txt
│   └── templates/
│       └── homepage.html
|
├── protobufs/
│   └── recommendations.proto
|
└── recommendations/
    ├── recommendations.py
    ├── recommendations_pb2.py
    ├── recommendations_pb2_grpc.py
    └── requirements.txt

请注意marketplace/微服务代码的新目录requirements.txt和主页。所有将在下面描述。您可以暂时为它们创建空文件,稍后再填充它们。

您可以从微服务代码开始。Marketplace 微服务将是一个Flask应用程序,用于向用户显示网页。它将调用 Recommendations 微服务以获取要在页面上显示的图书推荐。

打开marketplace/marketplace.py文件并添加以下内容:

# marketplace/marketplace.py
 2import os
 3
 4from flask import Flask, render_template
 5import grpc
 6
 7from recommendations_pb2 import BookCategory, RecommendationRequest
 8from recommendations_pb2_grpc import RecommendationsStub
 9
10app = Flask(__name__)
11
12recommendations_host = os.getenv("RECOMMENDATIONS_HOST", "localhost")
13recommendations_channel = grpc.insecure_channel(
14    f"{recommendations_host}:50051"
15)
16recommendations_client = RecommendationsStub(recommendations_channel)
17
18
19@app.route("/")
20def render_homepage():
21    recommendations_request = RecommendationRequest(
22        user_id=1, category=BookCategory.MYSTERY, max_results=3
23    )
24    recommendations_response = recommendations_client.Recommend(
25        recommendations_request
26    )
27    return render_template(
28        "homepage.html",
29        recommendations=recommendations_response.recommendations,
30    )

您设置 Flask,创建一个 gRPC 客户端,并添加一个函数来呈现主页。这是一个细分:

  • 第 10 行创建了一个 Flask 应用程序来为用户呈现网页。
  • 第 12 到 16 行创建您的 gRPC 通道和存根。
  • 第 20 到 30 行创建render_homepage()在用户访问您的应用程序主页时调用。它返回一个从模板加载的 HTML 页面,其中包含三本科幻小说推荐。

注意:在此示例中,您将 gRPC 通道和存根创建为globals。通常全局变量是不可以的,但在这种情况下,例外是有保证的。

gRPC 通道保持与服务器的持久连接,以避免必须重复连接的开销。它可以处理许多并发请求,并会重新建立断开的连接。但是,如果您在每个请求之前创建一个新通道,那么 Python 将对其进行垃圾回收,并且您将无法获得持久连接的大部分好处。

您希望频道保持打开状态,这样您就无需为每个请求重新连接到推荐微服务。您可以将通道隐藏在另一个模块中,但由于在这种情况下您只有一个文件,因此您可以通过使用全局变量使事情变得更简单。

打开目录中的homepage.html文件marketplace/templates/并添加以下 HTML:

<!-- homepage.html -->
 2<!doctype html>
 3<html lang="en">
 4<head>
 5    <title>Online Books For You</title>
 6</head>
 7<body>
 8    <h1>Mystery books you may like</h1>
 9    <ul>
10    {% for book in recommendations %}
11        <li>{{ book.title }}</li>
12    {% endfor %}
13    </ul>
14</body>

这只是一个演示主页。完成后,它应该显示书籍推荐列表。

要运行此代码,您需要以下依赖项,您可以将其添加到marketplace/requirements.txt

flask ~= 1.1
grpcio-tools ~= 1.30
Jinja2 ~= 2.11
pytest ~= 5.4

Recommendations 和 Marketplace 微服务各有其自己的requirements.txt. 运行以下命令来更新您的虚拟环境:

$ python -m pip install -r marketplace/requirements.txt

现在您已经安装了依赖项,您还需要在marketplace/目录中为您的 protobuf 生成代码。为此,请在控制台中运行以下命令:

$ cd marketplace
$ python -m grpc_tools.protoc -I ../protobufs --python_out=. \
         --grpc_python_out=. ../protobufs/recommendations.proto

这与您之前运行的命令相同,因此这里没有任何新内容。在marketplace/recommendations/目录中拥有相同的文件可能会让人感到奇怪,但稍后您将看到如何在部署过程中自动生成这些文件。您通常不会将它们存储在 Git 等版本控制系统中。

要运行您的 Marketplace 微服务,请在您的控制台中输入以下内容:

$ FLASK_APP=marketplace.py flask run

您现在应该在两个单独的控制台中运行 Recommendations 和 Marketplace 微服务。如果您关闭了 Recommendations 微服务,请在另一个控制台中使用以下命令重新启动它:

$ cd recommendations
$ python recommendations.py

这将运行您的 Flask 应用程序,该应用程序默认在端口 上运行5000。继续并在浏览器中打开它并检查它:

市场主页

您现在有两个微服务在互相通信!但它们仍然只是在您的开发机器上。接下来,您将学习如何将这些应用到生产环境中。

您可以通过在运行Python 微服务的终端中键入Ctrl+C来停止它们。接下来您将在Docker 中运行它们,这就是它们在生产环境中的运行方式。

从零开始学python | 使用 gRPC 的 Python 微服务 II


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。