gRPC對比REST,在Spring Boot 中使用gRPC
1 為什么選擇 gRPC
gRPC是一種高性能的先進RPC(遠程過程調(diào)用)框架,是開源的,并且兼容不同的環(huán)境。它使用協(xié)議緩沖區(qū)作為消息交換格式。
不同語言中的 gRPC 客戶端和服務器通信示例
gRPC可以讓客戶端代碼像調(diào)用本地對象方法一樣輕松地調(diào)用位于不同計算機上的服務器應用程序的方法,從而簡化了開發(fā)分布式應用程序和服務的過程。
2 gRPC VS REST(簡要比較)
主要的區(qū)別在于:
- 協(xié)議:gRPC 使用 HTTP/2,但通常 REST 使用 HTTP/1.1(下面進行比較)。簡而言之,HTTP/2 比 HTTP/1.1 快得多,效率更高。
- 數(shù)據(jù)格式:REST 通常使用 JSON,而 gRPC 使用協(xié)議緩沖區(qū)。
- API 格式:gRPC 的 API 范式是 RPC(遠程過程調(diào)用),而 REST 基于表現(xiàn)層狀態(tài)轉(zhuǎn)移模型。
- 流式傳輸:雖然 gRPC 支持雙向流式傳輸,但 REST 僅限于請求-響應模式。
圖片
3 項目結(jié)構(gòu)
- grpc-proto:Demo 項目的 gRPC proto 文件
- grpc-server:Spring Boot 中的 gRPC 服務器項目
- grpc-client:Spring Boot 中的 gRPC 客戶端項目
4 grpc-proto 項目
syntax = "proto3";
package com.imertyildiz.grpcproto;
option java_multiple_files = true;
message HelloWorldRequest{
string requestMessage = 1;
string clientName = 2;
}
message HelloWorldResponse{
string responseMessage = 1;
}
service HelloWorldService {
rpc HelloWorld(HelloWorldRequest) returns (HelloWorldResponse);
}
這里創(chuàng)建了一個簡單的 .proto 文件,包括服務、方法和消息定義。
使用 protobuf-maven-plugin 將服務器和客戶端代碼生成集成到 Maven 構(gòu)建系統(tǒng)中。
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>${protobuf-maven-plugin.version}</version>
<configuration>
<protocArtifact>
com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>
io.grpc:protoc-gen-grpc-java:${io.grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<id>client-code-generation</id>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>server-code-generation</id>
<goals>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
結(jié)果是,當項目通過 mvn package 命令編譯時,服務器和客戶端代碼都會生成。
但是,我們應該將項目 JAR 安裝到本地 Maven 倉庫中,以便 grpc-client 和 grpc-server 項目可以包含此項目 JAR。
因此,我們應該調(diào)用 mvn install 命令。
mvn install 后生成的源代碼
我們將在 grpc-server 和 grpc-client 項目中使用的服務和請求對象已創(chuàng)建并安裝在本地 Maven 倉庫中。
5 grpc-server 項目
使用 grpc-spring-boot-starter 的服務器庫,它通過注解簡化了客戶端和服務器的定義。下面是 proto 項目和 starter 庫的一部分 pom.xml。
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.imertyildiz</groupId>
<artifactId>grpcproto</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
該庫在應用程序啟動時啟動 gRPC 服務器,并監(jiān)聽端口:9090(默認值)。如果我們想更改端口,可以通過 application.properties 文件更改,例如:grpc.server.port=8000。
當我們?yōu)閿U展自動生成的 gRPC 服務定義的類使用 @GrpcService 注解時,該庫會將服務注冊到 gRPC 服務器上。
下面是實現(xiàn)代碼:
package com.imertyildiz.grpcserver.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.imertyildiz.grpcproto.HelloWorldRequest;
import com.imertyildiz.grpcproto.HelloWorldResponse;
import com.imertyildiz.grpcproto.HelloWorldServiceGrpc;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
@GrpcService
public class GreeterServer extends HelloWorldServiceGrpc.HelloWorldServiceImplBase {
private static final Logger logger = LoggerFactory.getLogger(GreeterServer.class);
@Override
public void helloWorld(HelloWorldRequest request, StreamObserver<HelloWorldResponse> responseObserver) {
HelloWorldResponse setResponseMessage = HelloWorldResponse.newBuilder()
.setResponseMessage("Hello " + request.getClientName() + " !!!").build();
logger.info(String.format("%1s sent a message: %1s", request.getClientName(),request.getRequestMessage()));
responseObserver.onNext(setResponseMessage);
responseObserver.onCompleted();
}
}
由于此 POC 只記錄了來自請求的客戶端名稱,因此服務器只是記錄了傳入消息。
6 grpc-client 項目
同樣,使用 grpc-spring-boot-starter 的客戶端庫。我們通過 @GrpcClient("grpc-server") 定義 gRPC 客戶端。該注解帶有命名目標服務器的參數(shù)。我們應該在 application.properties 文件中配置目標服務器地址。創(chuàng)建的文件如下所示:
grpc.client.grpc-server.address=static://localhost:8000
grpc.client.grpc-server.negotiation-type=plaintext
grpc.server.port=8001
@GrpcClient 注解中的目標服務器名稱參數(shù)在這里用于配置地址和端口信息。
客戶端代碼如下:
package com.imertyildiz.grpcclient.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.imertyildiz.grpcproto.HelloWorldRequest;
import com.imertyildiz.grpcproto.HelloWorldResponse;
import com.imertyildiz.grpcproto.HelloWorldServiceGrpc.HelloWorldServiceBlockingStub;
import net.devh.boot.grpc.client.inject.GrpcClient;
@Service
public class GreeterClient {
private static final Logger logger = LoggerFactory.getLogger(GreeterClient.class);
@GrpcClient("grpc-server")
private HelloWorldServiceBlockingStub helloWorldServiceStub;
public void sayHello(String sender, String message) {
HelloWorldRequest helloWorldRequest = HelloWorldRequest.newBuilder().setClientName(sender)
.setRequestMessage(message).build();
HelloWorldResponse helloWorldResponse = this.helloWorldServiceStub.helloWorld(helloWorldRequest);
logger.info(String.format("Server sent a response: %1s", helloWorldResponse.getResponseMessage()));
}
}
在我們?yōu)樽詣由傻姆兆⑨?BlockingStub 對象之后,它就可以使用了。我們發(fā)送消息并獲取響應,然后記錄響應。
從主函數(shù)中觸發(fā)請求函數(shù)。代碼如下:
@SpringBootApplication
public class GrpcClientApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(GrpcClientApplication.class, args);
GreeterClient greeterClientService = applicationContext.getBean(GreeterClient.class);
greeterClientService.sayHello("Client", "Hello Server !!!");
}
}
我們來看看結(jié)果:
首先啟動了服務器,然后啟動了客戶端。結(jié)果如下:
gRPC 服務器的日志
gRPC 客戶端的日志
總的來說,本文創(chuàng)建了簡單的 Demo 項目,展示了在 Spring Boot、Java 中 gRPC 客戶端和服務器的實現(xiàn)和通信,以及通過 protobuf 編譯器生成客戶端和服務器代碼的單獨 proto 項目。