本文共 4135 字,大约阅读时间需要 13 分钟。
早期的微服务实现主要使用REST架构作为事实上的通信技术。通常,RESTful服务对于面向外部的服务会很有用,这些服务直接暴露给消费者。它们是基于传统的文本消息传递(JSON、XML、基于HTTP的CVS等),但这些消息主要是面向人类的,并不是服务间通信的理想选择。
除了使用基于文本的消息传递协议,我们还可以使用针对服务间通信而优化的二进制协议。云原生计算基金会(CNCF)的gRPC(gRPC远程过程调用)就是服务间通信的一个理想选择,因为它使用protobuf作为服务间通信的二进制数据交换格式。
当我们使用不同的技术和编程语言构建多个微服务时,需要有一个标准的方法来定义服务接口和底层消息交换格式。gRPC提供了一种简洁而强大的使用protobuf来定义服务合约的方法。因此,gRPC可能是构建内部微服务间通信最可行的解决方案。
在本文中,我们将仔细探究为什么gRPC是构建微服务间通信的绝佳选择。
在使用gRPC时,客户端可以直接调用不同机器上的服务器应用程序,就好像是在调用本地对象一样。gRPC以传统的远程过程调用(RPC)技术为基础,但是在现代技术栈(如HTTP2、protobuf等)之上实现的,以确保能够提供最大的互操作性。
gRPC本身支持使用gRPC接口定义语言(IDL)来定义服务合约。因此,作为服务定义的一部分,你可以指定可远程调用的方法以及参数和返回类型的数据结构。
下图画出了gRPC与在线零售应用程序中的应用,这个应用程序是库存和产品搜索服务的一部分。库存服务的合约是使用gRPC IDL定义的,在inventory.proto文件中指定。库存服务的开发人员首先定义好所有的业务功能,然后根据proto文件生成服务端框架代码。类似地,可以使用相同的proto文件生成客户端存根代码。
由于gRPC与编程语言无关,你可以使用异构语言来构建服务和客户端。在这个例子中,我们使用Ballerina()生成服务端代码,使用Java生成客户端代码。你可以参考GitHub上的。
库存(inventory.proto)的服务合约如下所示:
syntax = \u0026quot;proto3\u0026quot;;package grpc_service;import \u0026quot;google/protobuf/wrappers.proto\u0026quot;;service InventoryService { rpc getItemByName(google.protobuf.StringValue) returns (Items); rpc getItemByID(google.protobuf.StringValue) returns (Item); rpc addItem(Item) returns (google.protobuf.BoolValue);}message Items { string itemDesc = 1; repeated Item items = 2;}message Item { string id = 1; string name = 2; string description = 3;}
服务合约易于理解,可以在客户端和服务之间共享。如果服务合约发生任何更改,则必须重新生成服务和客户端代码。
例如,下面是为Ballerina生成的gRPC服务代码。对于在gRPC服务中定义的每个操作,都会生成相应的Ballerina代码。(Ballerina提供了开箱即用的功能来生成服务或客户端代码,“ballerina grpc –input inventory.proto –output service-skeleton –mode service”或“ballerina grpc –input inventory.proto –output bal-client –mode client”)。
import ballerina/grpc;import ballerina/io;endpoint grpc:Listener listener { host:\u0026quot;localhost\u0026quot;, port:9000};@grpc:ServiceConfigservice InventoryService bind listener { getItemByName(endpoint caller, string value) { // Implementation goes here. // You should return a Items } getItemByID(endpoint caller, string value) { // Creating a dummy inventory item Item requested_item; requested_item.id = value; requested_item.name = \u0026quot;Sample Item \u0026quot; + value ; requested_item.description = \u0026quot;Sample Item Desc for \u0026quot; + value; _ = caller-\u0026gt;send(requested_item); _ = caller-\u0026gt;complete(); } addItem(endpoint caller, Item value) { // Implementation goes here. // You should return a boolean }}
同样,从库存服务的gRPC服务定义生成产品搜索服务客户端(一个Spring Boot Java服务)。你可以使用maven插件为Spring Boot/Java服务生成客户端存根(客户端代码嵌在Spring Boot服务中)。调用生成的客户端存根的代码如下所示。
package mfe.ch03.grpc;import com.google.protobuf.StringValue;import io.grpc.ManagedChannel;import io.grpc.ManagedChannelBuilder;public class InventoryClient { public static void main(String[] args) { ManagedChannel channel = ManagedChannelBuilder.forAddress(\u0026quot;127.0.0.1\u0026quot;, 9000) .usePlaintext() .build(); InventoryServiceGrpc.InventoryServiceBlockingStub stub = InventoryServiceGrpc.newBlockingStub(channel); Inventory.Item item = stub.getItemByID(StringValue.newBuilder().setValue(\u0026quot;123\u0026quot;).build()); System.out.println(\u0026quot;Response : \u0026quot; + item.getDescription()); }}
当客户端调用服务时,客户端gRPC库使用protobuf封装远程过程调用,然后通过HTTP2发送出去。在服务器端,请求被解封,并且通过protobuf执行相应的过程调用。响应遵循类似的流程,从服务器端发送到客户端。
gRPC的主要优点是你的服务代码或客户端代码不需要去解析JSON或其他基于文本的消息格式。网络上传输的内容是二进制格式,会被组装成对象。此外,当我们需要处理多个微服务并确保和维护互操作性时,通过IDL定义服务接口是一个强大的功能。
基于微服务的应用程序由多种服务组成,并使用了多种编程语言。你可以根据业务用例选择最合适的技术来构建你的服务。gRPC在这种多语言架构中起着非常重要的作用。我们将进一步扩展之前的在线零售用例。如下图所示,产品搜索服务与多个其他服务通信,这些服务使用gRPC作为通信协议。因此,我们可以为每个服务定义服务合约:库存、电子产品、服装等。现在,如果你想要使用多语言架构,可以使用不同的实现技术来生成服务框架代码。
下图显示了使用Ballerina的库存服务、使用Go语言的电子服务和使用Vert.x(Java)的服装服务。客户端也可以为每个服务合约生成存根。
仔细看一下上图中的微服务通信风格,可以看出,gRPC被用在所有的内部通信上,而面向外部的通信主要基于REST或GraphQL。当我们使用REST进行面向外部的通信时,大多数外部客户端可以将服务作为API(可以利用API定义技术,如Open API),因为大多数外部客户端都知道如何与HTTP RESTful服务通信。此外,我们可以使用诸如GraphQL之类的技术让消费者根据特定的客户需求来查询服务,这是gRPC无法提供的。
因此,作为一般实践,我们可以使用gRPC进行内部微服务之间的同步通信,而其他同步消息传递技术(如RESTful服务和GraphQL)更适合面向外部的服务。
转载地址:http://fqhga.baihongyu.com/