跳至內容
返回

gRPC是甚麼?什麼時候可以使用它?

技術
發佈於:  at  08:00 上午
··· 瀏覽

gRPC 是什麼?

gRPC 是 Google 開源的 RPC(Remote Procedure Call)框架。核心想法是讓你呼叫另一台服務的方法時,寫起來像呼叫本地方法,網路傳輸、序列化和反序列化都由框架處理。

傳統的服務間傳輸資料大致要像這樣子

// curl版本
curl -X POST localhost:8080/api/orders \
  -H 'Content-Type: application/json' \
  -d '{"userId":"u123","amount":3000}'
// java code
    HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://httpbin.org/get"))
                .GET()
                .build();

    HttpResponse<String> response = client.send(
            request,
            HttpResponse.BodyHandlers.ofString()
    );

這種方式是我們熟悉的,我們要發送請求前必須要知道對象的Domain、甚麼Path、甚麼HTTP方法、以及規範上要帶的HTTP Header、RequestBody等等。

但gRPC不這樣搞。

要使用gRPC之前,我們需要先寫一個 .proto 檔定義介面,這是一份請求方與回應方的規格書,又叫Interface Definition Language(IDL)。內容如下:

syntax = "proto3";

service WalletService {
  rpc GetBalance (BalanceRequest) returns (BalanceResponse);
}

message BalanceRequest {
  string user_id = 1;
}

message BalanceResponse {
  int64 balance = 1;
  string currency = 2;
}

用 Java 後端的概念來理解的話

完成之後,我們需要利用protobuf-maven-plugin這個工具產生(我使用Java舉例),放入pom.xml之後,啟動compile之後,就會自動產生以下檔案: protobuf 編譯後產生的 gRPC 檔案結構 當中 WalletServiceGrpc.java 的用途是 提供server用的 ImplBase,以及提供client用的 Stub。BalanceRequest與BalanceResponse就是請求與回復DTO

接著,Server端需要撰寫一個WalletServiceImpl去繼承WalletServiceGrpc.WalletServiceImplBase,然後我們可以在子類別中撰寫商業邏輯,詳細的怎麼傳輸、細節部份我們全部都交給框架就好。這是範例的code

public class WalletServiceImpl extends WalletServiceGrpc.WalletServiceImplBase {

    @Override
    public void getBalance(BalanceRequest request, StreamObserver<BalanceResponse> responseObserver) {
        BalanceResponse response = BalanceResponse.newBuilder()
                .setBalance(3000L)
                .setCurrency("TWD")
                .build();

        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

Server端寫好實作之後,我們還需要建立一個專用的port去專門服務gRPC的請求。這是範例的code。

        int port = 9090;
        Server server = ServerBuilder.forPort(port)
                .addService(new WalletServiceImpl())
                .build();

        server.start();
        System.out.println("gRPC server started, listening on port " + port);

        Runtime.getRuntime().addShutdownHook(new Thread(server::shutdown));
        server.awaitTermination();

如此一來Server的宣告就完成了,接下來我們來看Client端吧。

Client 端要做的事很少,建立連線、拿到 Stub,就可以像呼叫本地方法一樣使用了。

ManagedChannel channel = ManagedChannelBuilder
        .forAddress("localhost", 9090)
        .usePlaintext() // 本機測試不加密
        .build();

WalletServiceGrpc.WalletServiceBlockingStub stub = WalletServiceGrpc.newBlockingStub(channel);

BalanceResponse response = stub.getBalance(
        BalanceRequest.newBuilder().setUserId("u123").build());

跟一開始的 HTTP 版本比較,這裡完全沒有 Path、HTTP Method、Header,也沒有自己寫 DTO。我們只是呼叫了 getBalance(),剩下的都交給框架處理。

結論

目前gRPC的使用主要還是在後端微服務之間的資料溝通,它的好處是對延遲敏感,吞吐量大的系統來說比較合適,且可以跨語言,例如說你可以跟Python Team的人員共用一個protobuf,降低一些理解的歧異。

至於不適合的點就是,它不適合對外,對外服務主流還是REST API的方式,它也不適合直接給瀏覽器呼叫,例如說需要 HTTP cache 的場景,由於gRPC都是使用POST方法,瀏覽器快取會用不上,在使用起來比較麻煩。

還有一點就是Load balancing 要注意:它使用的 HTTP/2 是長連線,傳統 L4 load balancer 會把所有 request 導到同一台機器,簡單來說 L4 LB 可能會在gRPC傳輸上失效,實際要多留意這一部分。它需要 L7 LB(Envoy、Istio)或 client-side load balancing。

除此之外,gRPC也帶來了除錯比較難的議題,因為gRPC會將請求轉為二進位內容傳輸,這些工具的學習成本也是一個問題。

好,這樣一來對於gRPC大家應該有一個基礎的認識了,下一輪我要接著進入測試,測試gRPC這種犧牲可讀性換取效能的方法,到底可以換取多少的優勢。

那麼這次的文章就到這裡,讓我們在下一個猴年馬月再見吧。


建議修改
在以下平台分享此文章:

下一篇
後端仔的效能優化實戰(4) Slow Query找出效能瓶頸