SpringBoot+gRPC构建可扩展的微服务

哥们看看码农 2024-08-08 20:13:00

本文是一份详尽且深入的指南,旨在帮助读者理解并实现将 gRPC 通过 Maven 集成到 SpringBoot 项目中的全过程。文章首先以高度概括的方式探讨了 gRPC 的理论基础,为读者提供了对其核心概念的清晰认识。随后,我们将转向更为具体的实践层面,展示 gRPC 在实际应用中的多种实现方式。

虽然全文篇幅较长,但我们强烈建议您耐心阅读,以确保您能够全面掌握 gRPC 的技术细节和应用场景。通过本文的学习,您将不仅能够理解 gRPC 的工作原理,还能在您的 SpringBoot 项目中成功集成和应用这一强大的通信协议,从而提升系统的性能和效率。

什么是gRPC?

gRPC 是最初由 Google 开发的开源远程过程调用 (RPC) 框架。它是云原生计算基金会 (CNCF) 的一部分,旨在实现微服务架构中的服务之间高效、稳健的通信。以下是 gRPC 的一些关键功能和概念:

1. 协议缓冲区(Protobuf):

gRPC 使用 Protocol Buffers 作为其接口定义语言 (IDL)。Protobuf 是一种与语言无关、与平台无关的可扩展机制,用于序列化结构化数据,类似于 XML 或 JSON,但更小、更快、更简单。.proto文件用于定义服务方法和消息格式。

2.基于HTTP/2:

gRPC 利用 HTTP/2 作为其传输协议,它允许对 HTTP/1.x 进行许多改进,例如通过单个 TCP 连接复用多个请求、二进制帧和高效的错误处理。

3、四种服务方式:

Unary RPC :客户端发送单个请求并获得单个响应。Server Streaming RPC :客户端发送请求并获取响应流。Client Streaming RPC :客户端将一系列消息流式传输到服务器,然后服务器发回单个响应。Bidirectional Streaming RPC :客户端和服务器都独立地向对方发送消息流。

在这个实际的实现中,我们将看到一元实现它等于发送单个请求和响应。我们现在知道 gRPC 使用ProtocolBuffer来定义服务。首先让我们从定义接口定义开始,然后让我们看看项目结构和脚手架。下面的项目结构中的每个模块都是一个 Maven 模块。

模块 3(原型服务):

proto-service模块负责保存与 proto 文件相关的所有内容,并将它们编译成 gRPC 相关的存根和接口。

让我们在src/main/proto文件夹中创建一个Product.proto文件并复制以下内容。

/** * @author vaslabs(M K Pavan Kumar) * @medium (https://medium.com/@manthapavankumar11) */syntax = "proto3";option java_multiple_files = true;package com.vaslabs.proto;message Product { int32 product_id = 1; string name = 2; string description = 4; float price = 3; int32 category_id = 5;}message ProductList{ repeated Product product = 1;}message Category { int32 category_id = 1; string name = 2;}service ProductService { //unary - synchronous //request-response stype [not streaming] rpc getProductById(Product) returns(Product){} rpc getProductByCategoryId(Category) returns(ProductList){}}

提供的 proto 文件采用 Protocol Buffers 版本 3 ( proto3 ) 的语法编写,是与产品和类别相关的 gRPC 服务的结构化定义。以下是其组成部分的简要说明:

文件头:

syntax = "proto3"; :指定文件使用proto3语法,Protocol Buffers的最新版本。

选项:

option java_multiple_files = true; :指示 Protocol Buffers 编译器为每种消息类型生成单独的 Java 文件,而不是单个文件。

包裹声明:

package com.vaslabs.proto; :定义包名称,有助于防止不同项目之间的名称冲突。

消息定义:

message Product :定义Product消息,其中包含产品 ID、名称、描述、价格和类别 ID 字段。每个字段都有一个唯一的标签号(例如, product_id = 1 )。message ProductList :定义一个ProductList消息,该消息可以包含多个Product消息。 repeated表示它是Product对象的列表。message Category :表示具有类别 ID 和名称的类别。

服务定义:

service ProductService :声明一个名为ProductService服务。rpc getProductById(Product) returns(Product){} :定义一元(单个请求-响应)方法getProductById ,该方法将Product消息作为输入并返回Product消息。rpc getProductByCategoryId(Category) returns(ProductList){} :另一个一元方法getProductByCategoryId将Category消息作为输入并返回ProductList消息。

RPC 类型:

RPC 方法是一元的,这意味着它们遵循简单的请求-响应模式,无需流式传输。

让我们使用 maven 插件运行“mvncompile”来编译并获取 gRPC 风格生成的源代码以获取源代码。

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.vaslabs</groupId> <artifactId>springboot-grpc-demo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>proto-service</artifactId> <properties> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-stub</artifactId> <version>1.56.1</version> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-protobuf</artifactId> <version>1.54.2</version> </dependency> <dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency> </dependencies> <build> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.6.1</version> </extension> </extensions> <plugins> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.6.1</version> <configuration> <protocArtifact> com.google.protobuf:protoc:3.17.3:exe:${os.detected.classifier} </protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact> io.grpc:protoc-gen-grpc-java:1.53.0:exe:${os.detected.classifier} </pluginArtifact> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build></project>模块 2(产品服务)

这是保存原型服务的实现的服务,接收原型请求并将原型响应发送给调用它的客户端。该服务由grpc-spring-boot-starter提供支持

将以下内容复制到该模块的 pom.xml 中。观察 pom 文件,我们已将proto-service模块添加为依赖项,因为我们将在此模块中实现 proto-services。

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.vaslabs</groupId> <artifactId>springboot-grpc-demo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>product-service</artifactId> <properties> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.vaslabs</groupId> <artifactId>proto-service</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>net.devh</groupId> <artifactId>grpc-spring-boot-starter</artifactId> <version>2.14.0.RELEASE</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>

让我们在src/main/java中创建一个ProductServiceImpl.java类。观察该类扩展了由protoc生成的基类ProductServiceGrpc.ProductServiceImplBase

package org.vaslabs;import com.vaslabs.proto.Category;import com.vaslabs.proto.Product;import com.vaslabs.proto.ProductList;import com.vaslabs.proto.ProductServiceGrpc;import io.grpc.stub.StreamObserver;import net.devh.boot.grpc.server.service.GrpcService;import java.util.List;@GrpcServicepublic ProductServiceImpl extends ProductServiceGrpc.ProductServiceImplBase { @Override public void getProductById(Product request, StreamObserver<Product> responseObserver) { InMemoryData.getProducts() .stream() .filter(product -> product.getProductId() == request.getProductId()) .findFirst() .ifPresent(responseObserver::onNext); responseObserver.onCompleted(); } @Override public void getProductByCategoryId(Category request, StreamObserver<ProductList> responseObserver) { List<Product> products = InMemoryData.getProducts() .stream() .filter(product -> product.getCategoryId() == request.getCategoryId()) .toList(); ProductList productList = ProductList.newBuilder().addAllProduct(products).build(); responseObserver.onNext(productList); responseObserver.onCompleted(); }}

使用主类,我们可以启动服务ProductServiceApplication.java ,如下所示。

package org.vaslabs;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic ProductServiceApplication { public static void main(String[] args) { SpringApplication.run(ProductServiceApplication.class, args); }}

如果要更改默认的 gRPC 服务端口,请在src/main/resources/application.yaml中保留以下内容。

grpc: server: port: 9090模块 1(产品-客户端)

该模块是一个纯Spring Boot Web客户端,它将以Http形式侦听Web请求,并使用proto请求与产品服务通信并接收proto响应,然后转换为DTO并将其作为Http响应发送给调用客户端。

让我们将以下内容复制到pom.xml,该模块由grpc-client-spring-boot-starter提供支持,以发出启用 grpc 的请求和响应。

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.vaslabs</groupId> <artifactId>springboot-grpc-demo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>product-client</artifactId> <properties> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.vaslabs</groupId> <artifactId>proto-service</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>net.devh</groupId> <artifactId>grpc-client-spring-boot-starter</artifactId> <version>2.14.0.RELEASE</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.30</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>3.1.2</version> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build></project>

让我们创建一个服务,它将使用生成的存根与 grpc 服务进行通信,如下所示。 src/main/java/org/vaslabs/service/ProductServiceGRPC.java

package org.vaslabs.service;import com.vaslabs.proto.Category;import com.vaslabs.proto.Product;import com.vaslabs.proto.ProductList;import com.vaslabs.proto.ProductServiceGrpc;import net.devh.boot.grpc.client.inject.GrpcClient;import org.springframework.stereotype.Service;import java.util.List;import static org.vaslabs.service.helpers.DtoMappingHelper.mapProductListToProductDTO;import static org.vaslabs.service.helpers.DtoMappingHelper.mapProductToProductDTO;@Servicepublic ProductServiceRPC { @GrpcClient("grpc-product-service") ProductServiceGrpc.ProductServiceBlockingStub productServiceBlockingStub; public org.vaslabs.dto.Product getProductById(int id) { Product product = Product.newBuilder().setProductId(id).build(); Product response = productServiceBlockingStub.getProductById(product); return mapProductToProductDTO(response); } public List<org.vaslabs.dto.Product> getProductByCategoryId(int id) { Category category = Category.newBuilder().setCategoryId(id).build(); ProductList response = productServiceBlockingStub.getProductByCategoryId(category); return mapProductListToProductDTO(response); }}

一旦收到原始响应,我们将使用以下辅助方法将其转换为 DTO。 **/helpers/DtoMappingHelper.java

package org.vaslabs.service.helpers;import com.vaslabs.proto.ProductList;import org.vaslabs.dto.Product;import java.util.ArrayList;import java.util.List;public DtoMappingHelper { public static List<org.vaslabs.dto.Product> mapProductListToProductDTO(ProductList productList) { List<Product> products = new ArrayList<>(); productList.getProductList().forEach(product -> { Product product1 = getProduct(); product1.setId(product.getProductId()); product1.setCategoryId(product.getCategoryId()); product1.setName(product.getName()); product1.setDescription(product.getDescription()); product1.setPrice(product.getPrice()); products.add(product1); }); return products; } public static org.vaslabs.dto.Product mapProductToProductDTO(com.vaslabs.proto.Product product) { Product product1 = getProduct(); product1.setId(product.getProductId()); product1.setCategoryId(product.getCategoryId()); product1.setName(product.getName()); product1.setDescription(product.getDescription()); product1.setPrice(product.getPrice()); return product1; } private static Product getProduct(){ return new Product(); }}

**/dto/Product.java

package org.vaslabs.dto;import lombok.Data;import lombok.Getter;import lombok.Setter;@Data@Setter@Getterpublic Product { private int id; private int categoryId; private String name; private String description; private float price;}

让我们创建一个名为**/ProductController.java的控制器,如下所示。

package org.vaslabs.controller;import org.springframework.http.MediaType;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RestController;import org.vaslabs.dto.Product;import org.vaslabs.service.ProductServiceRPC;import java.util.List;@RestControllerpublic ProductController { private final ProductServiceRPC productServiceRPC; public ProductController(ProductServiceRPC productServiceRPC) { this.productServiceRPC = productServiceRPC; } @GetMapping("/product/{id}") public ResponseEntity<Product> getProductById(@PathVariable String id){ return ResponseEntity.ok().body(productServiceRPC.getProductById(Integer.parseInt(id))); } @GetMapping(value = "/product/category/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<List<Product>> getProductByCategoryId(@PathVariable String id){ return ResponseEntity.ok().body(productServiceRPC.getProductByCategoryId(Integer.parseInt(id))); }}

在此模块的资源文件夹中,请将以下内容添加到application.yaml文件中

grpc: client: grpc-product-service: address: static://localhost:9090 negotiationType: plaintext

为此,存根将创建一个名为grpc-product-service通道来与服务进行通信。

执行:

分别运行产品服务和产品客户端,如果下面的所有内容应该是您应该看到的输出。

我使用 HTTPie 来测试服务。

总结

在微服务架构的背景下,采用 gRPC 和 Protobuf 进行服务间通信已被证明是提升效率、稳健性和跨语言互操作性的关键策略。gRPC 以其基于 HTTP/2 的通信机制和对多种编程语言的广泛支持,极大地促进了高性能和低延迟的服务交互,这与微服务架构的分布式特性完美契合。

Protobuf(Protocol Buffers)通过提供一种紧凑的二进制序列化格式,进一步强化了这一优势,确保服务之间交换的数据不仅体积小,而且序列化和反序列化的速度极快。这种高效的序列化机制使得数据传输更为迅速,同时也减少了网络带宽的占用,对于构建现代、高效且有效的微服务生态系统至关重要。

综上所述,gRPC 与 Protobuf 的结合,不仅为微服务架构提供了一种强大的通信工具,还为构建高性能、低延迟的分布式系统奠定了坚实的基础。这种组合无疑成为了推动微服务生态系统发展的强大动力。

来源:https://spring4all.com/forum-post/7462.html

0 阅读:0

哥们看看码农

简介:感谢大家的关注