在之前講解 Nacos 註冊中心的過程中,我曾簡要提到過 gRPC,主要是因為 Nacos 的最新版已經採用了 gRPC 作為其核心通訊協議。這一變化帶來了顯著的效能最佳化,尤其在心跳檢測、健康檢查等介面的訊息傳輸上,gRPC 可以有效減少網路負擔和延遲,從而提高系統的整體效率。
所以,今天我們將簡要了解一下 gRPC 這種通訊協議是如何運作的,並透過一個簡單的 HelloWorld 示例來展示它的基本使用方式。
gRPC
gRPC(全稱:gRPC Remote Procedure Call)是一個由 Google 開發的高效能、開源的遠端過程呼叫(RPC)框架,它基於 HTTP/2 協議,並且採用了 Protocol Buffers(Protobuf)作為介面定義語言。gRPC 旨在簡化和最佳化微服務架構中的服務間通訊,提供高效、可靠的通訊機制,適用於大規模分散式系統。
gRPC 的通訊流程大致如下:
- 介面定義:開發者使用 Protobuf 定義服務介面(.proto 檔案)。該檔案描述了服務的 RPC 方法、請求和響應訊息型別。
- 程式碼生成:使用 Protobuf 編譯器(protoc)根據 .proto 檔案生成對應語言的客戶端和伺服器程式碼。
- 客戶端呼叫:客戶端透過 gRPC 客戶端 API 呼叫遠端方法,傳送請求並接收響應。gRPC 客戶端和伺服器之間的通訊是透明的,客戶端只需要像呼叫本地方法一樣呼叫遠端方法。
- 伺服器實現:伺服器端實現 .proto 檔案中定義的 RPC 方法,並透過 gRPC 框架處理客戶端的請求。
- 通訊協議:gRPC 使用 HTTP/2 協議進行高效的通訊,基於 Protobuf 編碼和解碼請求和響應資料。
快速入門
新建專案
按照我們剛才討論的通訊流程,接下來我們將透過一個簡單的示例來實現這一過程。首先,我們需要建立一個新的專案,專案的名稱可以根據個人喜好進行命名。如圖所示:
專案結構
接下來,我們將在剛才建立的專案中進一步細化結構,我們整體的專案結構如下:
│ pom.xml
│
├─grpc-api
│ │ .gitignore
│ │ pom.xml
│ │
│ ├─src
│ │ ├─main
│ │ │ ├─java
│ │ │ │ └─org
│ │ │ │ └─xiaoyu
│ │ │ │ │ Main.java
│ │ │ │ └─test
│ │ │ └─proto
│ │ │ hello.proto
├─grpc-client
│ │ pom.xml
│ ├─src
│ │ ├─main
│ │ │ ├─java
│ │ │ │ └─org
│ │ │ │ └─xiaoyu
│ │ │ │ │ Main.java
│ │ │ └─resources
│ │ │ application.yml
└─grpc-server
│ pom.xml
├─src
│ ├─main
│ │ ├─java
│ │ │ └─org
│ │ │ └─xiaoyu
│ │ │ │ Main.java
│ │ │ └─service
│ │ │ HelloWorldController.java
│ │ └─resources
│ │ application.yaml
跟著上面的目錄結構,我們需要建立子專案,如圖所示:
接下來,我們將配置父專案與各子專案之間的依賴關係,以確保它們能夠正確地協同工作。
專案依賴
父專案依賴如下:
<?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.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>org.xiaoyu</groupId>
<artifactId>grpc-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>grpc-client</module>
<module>grpc-server</module>
<module>grpc-api</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.51.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.51.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.51.0</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>3.7.1</version>
</dependency>
<dependency>
<groupId>com.googlecode.protobuf-java-format</groupId>
<artifactId>protobuf-java-format</artifactId>
<version>1.4</version>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</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.12.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.34.1:exe:${os.detected.classifier}</pluginArtifact>
<!--設定grpc生成程式碼到指定路徑-->
<outputDirectory>${project.basedir}/src/main/java</outputDirectory>
<!--生成程式碼前是否清空目錄-->
<clearOutputDirectory>false</clearOutputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 設定多個原始檔夾 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<!-- 新增主原始碼目錄 -->
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.basedir}/src/main/gen</source>
<source>${project.basedir}/src/main/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
client專案依賴如下:
<?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.xiaoyu</groupId>
<artifactId>grpc-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>grpc-client</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.xiaoyu</groupId>
<artifactId>grpc-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
server的專案依賴如下:
<?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.xiaoyu</groupId>
<artifactId>grpc-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>grpc-server</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.xiaoyu</groupId>
<artifactId>grpc-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
proto介面定義
接下來,我們需要生成客戶端和服務端的介面定義,並基於這些介面定義自動生成相關的程式碼。介面定義是整個 gRPC 通訊的核心,它將明確服務端提供的 API 介面及其對應的訊息格式,這為客戶端和服務端之間的通訊提供了基礎。
為了高效地生成這些介面程式碼,我們可以藉助一些自動化工具和 AI 助手來加速這一過程,簡單問下即可。如圖所示:
然後複製過來簡單改一下,程式碼如下:
syntax = "proto3";
import "google/protobuf/any.proto";
package org.xiaoyu.test;
option java_multiple_files = true;
option java_package = "org.xiaoyu.test";
option objc_class_prefix = "HelloWorld";
// 定義服務
service Greeter {
// 定義一個 SayHello 方法,接收一個 HelloRequest 型別的請求,並返回一個 HelloReply 型別的響應
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// 定義請求訊息型別
message HelloRequest {
string name = 1;
}
// 定義響應訊息型別
message HelloReply {
string message = 1;
}
完成介面定義後,我們只需透過執行 mvn compile
命令,直接對 API 專案進行編譯,即可自動生成與介面定義相關的所有程式碼類。這個過程將會根據我們在 .proto
檔案中定義的 gRPC 服務和訊息結構,自動生成相應的客戶端和服務端程式碼,包括 Java 類、存根(stub)、訊息類等。
服務端
接下來,如果在編譯或生成程式碼的過程中仍然遇到問題,或者對某些步驟存在疑問,完全可以繼續向 AI 助手尋求幫助。AI 助手可以為我們提供針對性的問題解決方案和除錯建議,無論是編譯錯誤、依賴問題,還是程式碼生成後的一些配置問題,都能快速給出指導。如圖所示:
在生成了相關程式碼後,我們可以直接將這些程式碼複製到 server
專案中,方便我們進行服務端的開發和整合。同樣地,當我們開始進行具體業務邏輯的實現時,AI 助手也能發揮重要作用。在實現服務端邏輯時,AI 助手不僅能自動補全程式碼,還可以基於專案的上下文和需求,智慧推薦最佳的實現方式。
最終程式碼如下,很簡單:
@GrpcService
public class HelloWorldController extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
responseObserver.onNext(HelloReply.newBuilder().setMessage("xiaoyu: Hello " + request.getName()).build());
responseObserver.onCompleted();
}
}
服務端配置
我們需要簡單配置一下服務端的監聽介面,如下:
grpc:
server:
port: 9090
剩下的步驟就是啟動我們已經整合了 gRPC 服務的 Spring 專案。當我們執行專案時,Spring Boot 應用會自動載入並初始化相關的 gRPC 服務配置,並開始監聽指定的埠。
客戶端
在客戶端部分,我們需要進行一些手動配置,雖然目前尚未找到能夠直接啟動並自動配置的註解或工具。在這個階段,客戶端程式碼的編寫相對簡單,主要是利用從 API 專案生成的客戶端程式碼來完成對服務端的請求呼叫。儘管這部分無法完全自動化,我們可以透過直接使用生成的客戶端程式碼來手動構建請求物件,併發起 gRPC 呼叫,從而實現與服務端的通訊。如下:
public class Main {
public static void main(String[] args) {
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 9090)
.usePlaintext()
.build();
// 建立一元式阻塞式存根
GreeterGrpc.GreeterBlockingStub blockingStub = GreeterGrpc.newBlockingStub(channel);
// 建立請求物件
HelloRequest request = HelloRequest.newBuilder()
.setName("World")
.build();
// 傳送請求資訊並接收響應
HelloReply response = blockingStub.sayHello(request);
// 處理資訊響應
System.out.println("Received response: " + response);
}
}
只需直接執行專案,即可成功啟動並完成服務的所有配置。與其他服務介面的呼叫方式類似,我們在這裡無需編寫任何額外的介面呼叫程式碼,看下執行結果,如圖所示:
總結
透過本文的講解,我們瞭解了 gRPC 作為一種高效的通訊協議在微服務架構中的應用,特別是在與 Nacos 整合時帶來的效能最佳化。gRPC 的高效性,得益於其基於 HTTP/2 協議和 Protobuf 的資料傳輸方式,使得跨服務通訊更加迅速且可靠。本文透過 HelloWorld 示例演示了從介面定義到服務端和客戶端的實現流程,充分展示了 gRPC 的強大功能和易用性。
掌握這一協議,不僅能夠提升服務間的通訊效率,也為開發更具擴充套件性的分散式系統奠定了基礎。最終,gRPC 作為微服務架構中的關鍵元件,其提供的效能最佳化和便捷性,必將在未來的專案中發揮重要作用。
我是努力的小雨,一名 Java 服務端碼農,潛心研究著 AI 技術的奧秘。我熱愛技術交流與分享,對開源社群充滿熱情。同時也是一位騰訊雲創作之星、阿里雲專家博主、華為云云享專家、掘金優秀作者。
💡 我將不吝分享我在技術道路上的個人探索與經驗,希望能為你的學習與成長帶來一些啟發與幫助。
🌟 歡迎關注努力的小雨!🌟