在过去,软件应用程序是作为一个巨大的整体构建的,并且仍在进行中。然而,最近,微服务体系结构已经成为开发软件应用程序的流行选择。在微服务体系结构中,微服务通常需要相互通信。与传统的RESTfulWebAPI相比,基于gRPC的RPC框架可以更好地替代微服务通信。
什么是微服务体系结构?
微服务体系结构由许多(通常是数百个)小型、自治、自包含的服务组成。微服务是围绕业务能力构建的。微服务体系结构的一些重要特征包括:
- 微服务是围绕业务能力建模的。
- 微服务是可独立部署的。
- 微服务封装了它所拥有的数据。在这种情况下,如果一个微服务需要从另一个微服务获取数据,那么它应该调用API。
- 微服务的规模应该很小。
微服务进程间通信
基于微服务的软件系统要求应用程序使用进程间通信机制相互通信。微服务可以像其他服务一样相互通信
- 同步通信(请求-应答):在此模式中,服务调用另一个服务公开的API并等待响应。API必须具有定义良好的语义和版本控制。
- 异步通信:在这种模式中,服务发送消息,而不等待另一个服务的响应。并且,一个或多个服务可以处理该消息。
实现请求-应答式通信的最常见方式是使用RESTful服务,通常作为HTTP上的JSON负载实现。然而,RESTful服务可能非常庞大、低效,并且对许多用例都有限制。RESTful服务的庞大性是因为大多数实现依赖于JSON(一种基于文本的编码格式)。与二进制格式相比,JSON格式使用了大量的空间。
JSON有二进制编码格式(MessagePack、BSON、SMILE等);然而,它们的采用并不是很普遍。
使用基于JSON的API的另一个问题是对模式的支持是可选的。应用程序宣传API的最常见方式是使用OpenAPI规范,但这并没有与RESTful架构风格紧密集成。OpenAPI规范和实现在适当的时候出现漂移是很常见的。
为什么gRPC用于微服务通信?
为了解决与RESTful服务相关的问题,我们需要一个比RESTful服务更具伸缩性和效率的现代进程间通信系统。进入gRPC,一个现代的开源RPC框架。
gRPC框架基于二进制编码格式协议缓冲区,并在HTTP/2之上实现。它是强类型、多语言的,并提供客户端和服务器端流媒体。
在gRPC中,客户机应用程序可以直接调用另一台计算机上的分布式服务器应用程序上的方法,就像它是本地方法一样。
所以,问题是,您是否应该将我们所有的API迁移到使用gRPC?
可能没有!!
在回答之前,让我们先了解一些有关API的事实。有两种类型的API:
- 公共API:客户端应用程序使用的这些API通常是浏览器、移动应用程序或其他应用程序。例如,GitHub公共API(https://docs.github.com/en/free-pro-team@latest/rest)。
- 私有API:这些API不对外公开,主要用于应用程序内的服务间通信。
使用公共API的事实标准是HTTP上的其余标准。对于私有API,您可以考虑使用gRPC;但是,您必须了解引入新技术堆栈与gRPC好处之间的权衡。此外,在基于浏览器的应用程序上支持gRPC并非易事。
一种可能的基于gRPC的体系结构是——在微服务前面设置API网关/BFF,并使用gRPC进行所有内部通信。
gRPC的优势
使用gRPC有许多优点。这就是为什么它被Netflix和Dropbox等公司广泛采用的原因。让我们了解一些优势。
与基于JSON的RESTful API相比速度更快
与传统的基于JSON的RESTAPI相比,gRPC的速度要快得多。gRPC基于二进制协议缓冲区格式,与基于文本的格式(如JSON)相比,该格式非常轻量级。
例如,对于下面的有效负载,JSON编码需要81个字节,协议缓冲区中的相同有效负载需要33个字节(来源——Martin Kelpmann的第4章——设计数据密集型应用程序。如果你没有读过这本书,你就错过了一些东西!!)
{
"serName": "Martin",
"favoriteNumber": 1337,
"interests": [" daydreaming", "hacking"]
}
可以在协议缓冲区之上实现RESTful API,但为什么要这样做呢?
使gRPC更快的另一个原因是它是在HTTP/2之上实现的。多快?检查HTTP/1.1与HTTP/2的比较。
强类型和定义良好的接口
gRPC引入了定义良好的服务接口和强类型,帮助我们避免运行时错误。对于客户端应用程序,这与调用本地方法一样好。例如,您可以将gRPC服务定义为:
service ProductService {
rpc getProduct(GetProductRequest) returns (GetProductResponse);
}
此外,您可以通过gRPC客户端将其称为:
var productResponse = productServiceBlockingStub.getProduct(productRequest);
如您所见,调用gRPC服务器与调用本地方法一样好。
多语言文字
gRPC支持多种编程语言。您可以选择用您认为合适的编程语言编写微服务,而不必担心互操作性问题。
流支持
gRPC支持客户端和服务器端流。在应用程序中启用这些功能与更改服务定义一样简单。例如,只需使用stream
关键字声明响应即可启用服务器端流。
service ProductService {
rpc getProduct(GetProductRequest) returns (stream GetProductResponse);
}
其他特征
gRPC内置了对弹性、身份验证、加密、压缩、负载平衡和错误处理的支持。GRPC是云计算基础基金(CNCF)的骄傲成员,许多现代框架为GRPC提供了本地支持。
代码示例
您可以在GitHub上找到本文的工作代码示例。运行代码、克隆存储库并将grpc示例作为Gradle项目导入。要生成项目并生成客户端和服务器存根,请运行命令gradlew clean build
。您可以通过运行ProductServer
的main方法在IDE中启动gRPC服务器。gRPC服务器在本地主机8081上运行。要执行RPC,请从订单服务运行ProductClient
的main
方法。
gRPC客户端和服务器的实现
在开始之前,让我们试着理解gRPC的一些重要概念。gRPC应用程序由三个重要组件组成:
- 协议缓冲区:它定义服务器应用程序公开的消息和服务。gRPC服务以协议缓冲区(Protobuf)消息的形式发送和接收数据。
- 服务器:服务器应用程序实现并公开RPC服务。
- 客户端:客户端应用程序使用服务器公开的RPC服务。
什么是协议缓冲区?
协议缓冲区是Google用于序列化结构化数据的语言中立、平台中立、可扩展的机制——比如XML,但更小、更快、更简单。您只需定义一次数据的结构化方式,然后就可以使用生成的特殊源代码轻松地在各种数据流之间以及使用各种语言编写和读取结构化数据。
协议缓冲区只不过是接口定义语言(IDL),用于在gRPC中定义API契约。在gRPC中,您可以在.proto
文件中定义API契约。
您可以通过声明消息和服务来定义GRPCAPI。例如,如果订单服务(gRPC客户端)通过传递productId
调用产品服务(gRPC服务器)获取有关产品的信息,那么您可以在协议缓冲区中将服务定义定义为:
syntax = "proto3";
package dev.techdozo.product.api;
message GetProductRequest {
string productId = 1;
}
message GetProductResponse {
string name = 1;
string description = 2;
double price = 3;
}
service ProductService {
rpc getProduct(GetProductRequest) returns (GetProductResponse);
}
让我们了解proto文件的不同元素。
协议报文
消息是在客户端和服务器之间交换的二进制数据结构。消息和服务在.proto文件中声明。如果需要,可以为消息和服务创建单独的文件。例如,resources.proto用于消息,service.proto
用于服务定义。
字段编号(如name=1
)用于标识二进制编码数据中的字段。这意味着您不能将字段号从一个版本更改为另一个版本。此外,这有助于向后和向前兼容性——客户机和服务可以忽略他们不知道的字段号。
服务
服务是服务器公开的远程方法。客户端可以使用生成的存根调用服务器上的远程方法。在上面的示例中,产品服务公开RPC方法RPC getProduct(GetProductRequest)returns(GetProductResponse)
;。此RPC调用以GetProductResponse
的形式返回有关产品的信息。
代码生成
您可以使用protoc编译器生成客户端和服务器代码。protoc编译器支持多种不同语言的代码生成。
对于我们的示例,我们将使用Protobuf Gradle插件以java生成源代码。protocolbuffer插件组装Protobuf编译器(protoc
)命令行,并使用它从proto文件生成Java源文件。生成的java源文件应添加到源集中,以便可以与java源一起编译。
sourceSets {
main {
java {
srcDirs 'build/generated/source/proto/main/grpc'
srcDirs 'build/generated/source/proto/main/java'
}
}
}
运行命令gradlew build
在build/generated/source/proto/main/grpc
和build/generated/source/proto/main/java
目录中生成源代码。
gRPC服务器
gRPC服务器实现在proto文件中定义的服务,并将这些服务公开为RPC API。在本例中,我们将产品microservice视为gRPC服务器,并将order microservice视为gRPC客户端。
在我们的示例中,gRPC服务器公开一个RPC方法RPC getProduct(GetProductRequest) returns (stream GetProductResponse)
。
实现服务定义
生成服务器存根后,可以实现RPC服务ProductService
,如service.proto
文件中所定义。
要实现业务逻辑,应该重写自动生成的抽象类ProductServiceGrpc.ProductServiceImplBase
中的getProduct(..)
方法。
public class ProductService extends ProductServiceGrpc.ProductServiceImplBase {
private final ProductRepository productRepository;
public ProductService() {
this.productRepository = new ProductRepository();
}
@Override
public void getProduct(
GetProductRequest request, StreamObserver<GetProductResponse> responseObserver) {
var productId = request.getProductId();
Optional<ProductInfo> productInfo = productRepository.get(productId);
if (productInfo.isPresent()) {
var product = productInfo.get();
var getProductResponse =
GetProductResponse.newBuilder()
.setName(product.getName())
.setDescription(product.getDescription())
.setPrice(product.getPrice())
.build();
responseObserver.onNext(getProductResponse);
responseObserver.onCompleted();
} else {
responseObserver.onError(new StatusException(Status.NOT_FOUND));
}
}
}
上述代码从ProductRepository
获取产品信息,并通过传递getProductResponse
调用StreamObserver
上的onNext()
。onCompleted()
方法通知流成功完成。
如果出现错误,可以通过传递适当的错误代码来调用onError()
方法。
注册服务
要公开RPC服务ProductService
,可以创建gRPC服务器实例,并通过调用addService
方法注册该服务。服务器侦听指定的端口并将所有请求发送到相关服务。
public ProductServer(int port) {
this.port = port;
var productService = new ProductService();
this.server = ServerBuilder.forPort(port).addService(productService).build();
}
实现gRPC客户端存根
实现gRPC客户机需要做的第一件事是使用服务器(产品服务)应用程序中定义的proto文件生成客户机存根。
public class ProductClient {
private final ProductServiceGrpc.ProductServiceBlockingStub productServiceBlockingStub;
public ProductClient(String host, int port) {
var managedChannel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
this.productServiceBlockingStub = ProductServiceGrpc.newBlockingStub(managedChannel);
}
public void call() {
var productRequest = GetProductRequest.newBuilder().setProductId("apple-123").build();
var productResponse = productServiceBlockingStub.getProduct(productRequest);
log.info("Received Product from server, info {}", productResponse);
}
public static void main(String[] args) {
var client = new ProductClient("0.0.0.0", 8081);
client.call();
}
}
您可以创建一个gRPC通道,将服务器地址和端口指定为ManagedChannelBuilder.forAddress(host, port).usePlaintext().build()
。通道表示到要执行RPC的端点的虚拟连接。
您可以使用新创建的通道创建客户端存根,如下所示:
var managedChannel = ManagedChannelBuilder.forAddress(host,port).usePlaintext().build();
var productServiceBlockingStub = ProductServiceGrpc.newBlockingStub(managedChannel);
有两种类型的客户端存根:
- Blocking:BlockingStub,它等待收到服务器响应。
- Non Blocking:NonBlockingStub,它不等待服务器响应,而是注册一个观察者来接收响应。
这里的明文表示我们正在客户端和服务器之间建立一个不安全的连接。
总结
基于gRPC的RPC框架是微服务应用程序中进程间通信的理想选择。gRPC服务不仅比RESTful服务更快,而且它们是强类型的。协议缓冲区是用于交换数据的二进制格式,用于定义gRPC API。gRPC支持多种编程语言。使用代码生成工具可以更容易地在gRPC中生成客户端和服务器存根。
原文地址:https://techdozo.dev/grpc-for-microservices-communication/
除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2452.html
暂无评论