零基礎入門gRPC:從 0 實現一個Hello World

努力的小雨發表於2024-11-22

在之前講解 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 編碼和解碼請求和響應資料。

快速入門

新建專案

按照我們剛才討論的通訊流程,接下來我們將透過一個簡單的示例來實現這一過程。首先,我們需要建立一個新的專案,專案的名稱可以根據個人喜好進行命名。如圖所示:

image

專案結構

接下來,我們將在剛才建立的專案中進一步細化結構,我們整體的專案結構如下:

│ 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

跟著上面的目錄結構,我們需要建立子專案,如圖所示:

image

接下來,我們將配置父專案與各子專案之間的依賴關係,以確保它們能夠正確地協同工作。

專案依賴

父專案依賴如下:

<?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 助手來加速這一過程,簡單問下即可。如圖所示:

image

然後複製過來簡單改一下,程式碼如下:

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)、訊息類等。

image

服務端

接下來,如果在編譯或生成程式碼的過程中仍然遇到問題,或者對某些步驟存在疑問,完全可以繼續向 AI 助手尋求幫助。AI 助手可以為我們提供針對性的問題解決方案和除錯建議,無論是編譯錯誤、依賴問題,還是程式碼生成後的一些配置問題,都能快速給出指導。如圖所示:

image

在生成了相關程式碼後,我們可以直接將這些程式碼複製到 server 專案中,方便我們進行服務端的開發和整合。同樣地,當我們開始進行具體業務邏輯的實現時,AI 助手也能發揮重要作用。在實現服務端邏輯時,AI 助手不僅能自動補全程式碼,還可以基於專案的上下文和需求,智慧推薦最佳的實現方式。

image

最終程式碼如下,很簡單:

@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 服務配置,並開始監聽指定的埠。

image

客戶端

在客戶端部分,我們需要進行一些手動配置,雖然目前尚未找到能夠直接啟動並自動配置的註解或工具。在這個階段,客戶端程式碼的編寫相對簡單,主要是利用從 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);

    }
}

只需直接執行專案,即可成功啟動並完成服務的所有配置。與其他服務介面的呼叫方式類似,我們在這裡無需編寫任何額外的介面呼叫程式碼,看下執行結果,如圖所示:

image

總結

透過本文的講解,我們瞭解了 gRPC 作為一種高效的通訊協議在微服務架構中的應用,特別是在與 Nacos 整合時帶來的效能最佳化。gRPC 的高效性,得益於其基於 HTTP/2 協議和 Protobuf 的資料傳輸方式,使得跨服務通訊更加迅速且可靠。本文透過 HelloWorld 示例演示了從介面定義到服務端和客戶端的實現流程,充分展示了 gRPC 的強大功能和易用性。

掌握這一協議,不僅能夠提升服務間的通訊效率,也為開發更具擴充套件性的分散式系統奠定了基礎。最終,gRPC 作為微服務架構中的關鍵元件,其提供的效能最佳化和便捷性,必將在未來的專案中發揮重要作用。


我是努力的小雨,一名 Java 服務端碼農,潛心研究著 AI 技術的奧秘。我熱愛技術交流與分享,對開源社群充滿熱情。同時也是一位騰訊雲創作之星、阿里雲專家博主、華為云云享專家、掘金優秀作者。

💡 我將不吝分享我在技術道路上的個人探索與經驗,希望能為你的學習與成長帶來一些啟發與幫助。

🌟 歡迎關注努力的小雨!🌟

相關文章