本文要点:
我们可以基于交互和通信方式将微服务分为两类:外部微服务和内部微服务。
RESTful API 是面向外部微服务的事实上的通信技术(REST 的无处不在和丰富的生态系统对其取得持续的成功起着至关重要的作用)。
gRPC 是远程过程调用(RPC)API 范式的一种新的实现,在内部微服务间的同步通信方面发挥重要作用。
在这篇文章里,我们将通过真实的微服务案例来研究 gRPC 的关键概念,了解将 gRPC 作为服务间通信的好处及其用法。
很多主流的编程语言都支持 gRPC。我们将使用 Ballerina 和 Go 作为编程语言来探讨示例。
在现代微服务架构中,我们可以基于微服务的交互和通信方式将微服务分为两种。第一种是直接暴露给消费者的面向外部的微服务。它们主要是基于 HTTP 的 API,这些 API 使用基于常规文本的消息负载(JSON、XML 等,针对外部开发人员进行了优化),并使用 REST 作为事实上的通信技术。
REST 的无处不在和丰富的生态系统对这些面向外部的微服务取得成功起着至关重要的作用。OpenAPI提供了定义良好的规范,用于描述、生成、使用和可视化这些 REST API。API 管理系统可以很好地与这些 API 配合使用,并提供安全性、速率限制、缓存和实现业务需求。GraphQL可以替代基于 HTTP 的 REST API,但不在本文讨论范围之内。
另一种是内部微服务,不与外部系统或外部开发人员通信。这些微服务之间相互交互,以便完成给定的任务。内部微服务使用同步或异步通信。在很多情况下,我们可以看到内部微服务使用同步的基于 HTTP 的 REST API,但这并不是最好的技术。在本文中,我们将仔细探究如何利用二进制协议(例如 gRPC)作为服务间通信的优化通信协议。
gRPC 是什么?
gRPC 是用于服务间通信的相对较新的一种远程过程调用(RPC)API 范式。与其他 RPC 一样,它允许位于不同服务器上的应用程序之间相互调用方法,就好像它们是本地对象一样。与 Thrift 和 Avro 等其他二进制协议一样,gRPC 使用接口描述语言(IDL)来定义服务契约。gRPC 使用 HTTP/2(最新的网络传输协议)作为默认传输协议,与基于 HTTP/1.1 的 REST 相比,gRPC 更快、更强大。
你可以使用 Protocol Buffers 来定义 gRPC 服务契约,每个服务定义指定方法数量,这些方法包含了期望的输入和输出消息以及参数和返回类型的数据结构。使用主流编程语言提供的工具,基于 Protocol Buffers 文件生成服务器端框架和客户端代码(存根)。
一个实际的 gRPC 微服务案例
图 1:一个在线零售商店微服务架构图片段
微服务架构的一个主要好处是可以使用最合适的编程语言来构建不同的服务,而不是只使用一种语言来构建所有服务。图 1 展示了在线零售商店微服务架构的一部分,其中使用 Ballerina 实现了四个微服务(在本文的其余部分用 Ballerina 代称),使用 Go 实现了其他一些功能。由于主流的编程语言都支持 gRPC,因此当我们在定义服务契约时,可以指定一种合适的编程语言。
清单 1:Order 微服务的服务契约(order.proto)
Order 微服务根据购物项目和数量返回小计金额。在这里,我使用 Ballerina gRPC 工具分别生成 gRPC 服务端样板代码和客户端代码。
生成的 OrderService 服务器端样板代码如下:
清单 2:生成的样板代码片段(OrderService_sample_service.bal)
gRPC 的 rpc 映射到 Ballerina 的 service 类型,gRPC 的 rpc 映射到 Ballerina 的 resource function,gRPC 的 message 映射到 Ballerina 的 record 类型。
我为 Order 微服务创建了一个单独的 Ballerina 项目,并使用生成的 OrderService 样板代码来实现gRPC服务。
一元阻塞(Unary Blocking)
OrderService 被 Cart 微服务调用。我们可以使用下面的 Ballerina 命令来生成客户端存根和代码。
生成的客户端存根同时具有阻塞和非阻塞远程方法。这个示例代码演示了 gRPC 一元服务如何与 gRPC 阻塞客户端交互。
清单 3:生成的阻塞模式远程对象代码片段
Ballerina 的远程方法抽象与 gRPC 客户端存根完美契合,你可以看到 UpdateOrder调用代码是多么干净和整洁。
Checkout 微服务对所有从 Cart 微服务收到的临时订单进行聚合,并生成最终的账单。在本例中,我们将以订单消息流的形式发送所有临时订单。
清单 4:Checkout 微服务的服务契约(checkout.proto)
你可以使用 ballerina grpc 命令为 checkout.proto 生成样板代码。
gRPC 客户端流
Cart 微服务将消息流作为流对象参数,可以使用循环来访问,以便处理客户端发送的每一个消息。请看下面的实现:
清单 5:CheckoutService 实现代码片段(CheckoutService_sample_service.bal)
一旦客户端流结束,将返回 grpc:EOS 错误,这个错误可用来确定何时使用调用者对象向客户端发送最终响应消息(聚合总数)。
我们来看一下 Cart 微服务的实现。Cart 微服务有两个 REST API——一个用于向购物车添加商品,另一个用于执行最终的结账。在向购物车添加商品时,通过 gRPC 调用 Order 微服务,并将其保存在内存中,它将获得一个带有每个商品小计金额的临时订单。调用 Checkout 微服务将把所有保存在内存中的临时订单以 gRPC 流的形式发送给 Checkout 微服务,并返回需要支付的总额。Ballerina 使用内置的 Stream 类型和 Client Object 抽象来实现 gRPC 客户端流。图 2 演示了 Ballerina 的客户端流是如何工作的。
图 2:Ballerina gRPC 客户端流
CheckoutService 客户端流的完整实现可以在 Cart 微服务的checkout resource function中找到。最后,在结账过程中,通过 gRPC 调用使用 Go 实现的 Stock 微服务,并扣除已售商品,更新库存。
清单 6:Stock 微服务的服务契约(stock.proto)
在这个场景中,UpdateStock 服务作为外部 API(通过 REST API 来调用),也可以通过 gRPC 进行服务间调用。grpc-gateway是 protoc 的一个插件,它读取 gRPC 服务定义并生成一个反向代理服务器,将 RESTful JSON API 转换为 gRPC。
图 3:grpc-gateway
你可以借助 grpc-gateway 同时提供 gRPC 和 REST 风格的 API。
使用以下命令生成Golang gRPC存根:
使用以下命令生成Golang grpc-gateway代码:
使用以下命令生成stock.swagger.json:
运行示例代码
拉取microservices-with-grpc代码库,并参看 READEME.md。
结论
gRPC 虽然还年轻,但其快速增长的生态系统和社区肯定会对微服务的发展产生影响。由于 gRPC 是一种开放标准,所有主流编程语言都支持它,这使得它非常适合被用在多语言微服务环境中。作为一种常规做法,我们可以将 gRPC 用于内部微服务之间的同步通信。借助 grpc-gateway 等新兴技术,我们还可以将其包装成 REST 风格的 API。除了本文讨论的内容之外,gRPC 特性(如超时、取消、通道和xDS支持)将为开发人员构建高效的微服务提供强大的功能和灵活性。
更多资源
要了解更多有 Ballerina gRPC 的资料,请浏览以下链接:
Go 提供了全面的 gRPC 支持,我们可以扩展这些微服务,通过使用 gRPC 拦截器、超时、取消和渠道等来增强安全性、健壮性和弹性。请参阅grpc-go代码库,其中有很多有关这些概念的示例。
推荐视频:
Generating Unified APIs with Protocol Buffers and gRPC
Writing REST Services for the gRPC curious
Using gRPC for Long-lived and Streaming RPCs
作者介绍:
Lakmal Warusawithana 是 WSO2 的高级开发总监。2005 年,Lakmal 与其他人共同创立了 thinkCube,这是为电信运营商量身定制的下一代协同云计算产品的先驱。他监督整个工程过程,特别关注 thinkCube 解决方案的可扩展性和服务交付。在创立 thinkCube 之前,Lakmal 在 ITABS 工作了 4 年,这是一家专门从事 Linux 服务器部署的公司,提供易于使用的定制服务器管理界面。
原文链接:
Building Effective Microservices with gRPC, Ballerina, and Go
评论