iT邦幫忙

2024 iThome 鐵人賽

DAY 12
0

最近工作剛好使用到 gRPC,趁這個機會來了解一下他和 Restful API 的 diff。

gRPC vs REST

  • gRPC:由 Google 開發,是一個跨平台的開源高效能遠端程序呼叫框架,基於 HTTP/2 協議,使用 Protocol Buffers(Protobuf)作為資料格式,適合需要高效率通訊和低延遲的微服務架構。
  • REST(ful):Representational State Transfer 是一種設計風格,通常基於 HTTP 1.1 協議,通常使用 JSON 格式,簡單易用,適合公開 API 或需要與多樣設備交互的場景。

gRPC 和 REST 相似之處

  • client/server 架構:
    • gRPC 和 REST 走的都是典型的 client/server 架構。
  • 使用 HTTP :
    • REST: 通常是使用 HTTP/1.1,當不限 HTTP/1。
    • gRPC: 使用 HTTP/2 作為傳輸層。
  • 支援多種程式語言:
    • REST:
      • 能使用 HTTP 請求的語言都能使用,交換資料用 JSON 檔,也是可以跨不同領域的交換格式。
    • gRPC:
      • 使用 Protobuf 作為介面定義語言(IDL)。不同語言的 gRPC 客戶端和 Server 端可以直接通訊。
  • 無狀態(Statelessness):
    • 兩者的請求都是無狀態的,每次 request 都是獨立的, server 不需要記錄任何過去的請求或客戶端的狀態。

適合使用的場景

  • gRPC:適合內部系統通訊、需要高性能和低延遲的應用,gRPC 使用 Protobuf 進行二進位資料序列化,使其成為需要低延遲和高吞吐量的應用程式的更好選擇。
    • 微服務架構: gRPC 特別適合用於微服務之間彼此的的高效率通訊,能夠處理高併發和低延遲的需求。
    • 即時性的應用: 如線上聊天、遊戲等。
    • 跨語言系統: 在需要不同程式語言之間互操作的情況下,gRPC 可以用相同的 proto 轉成不同程式語言的版本。
  • REST:適合公開 API 或需要廣泛兼容性的場景,因為它與大多數程式語言和平台相容,可以較為輕鬆的整合。

gRPC 缺點

  • gRPC 的問題處理
    • 雙方依賴性:
      • gRPC 的客戶端和服務端都依賴於相同的 Protobuf 定義。如果需要對 Protobuf 介面更改,則必須同時更新客戶端和伺服器端。這種雙向依賴性在需要和多個客戶或廠商時溝通時會讓開發變得複雜;版本更新時也需要注意兼容。
    • Debug 困難:
      • gRPC 的 request 和 response 都是二進制,相較於 JSON 檔,較為麻煩。
    • 版本兼容較困難:
      • 因為新版本改動了一些資料格式,在兼容過去的版本會比較麻煩。

gRPC 實際流程

https://ithelp.ithome.com.tw/upload/images/20240926/20150927tOL6zH3Noh.png
圖片來源

在這個例子中,

  1. 客戶端對 order service 先發出 REST request。
  2. 接著客戶端會發出 gRPC 請求給 client stub。
  3. client stub 會把資料封裝成二進制傳給底層傳輸層。
  4. 透過 HTTP/2 傳送給 Payment service。
  5. 伺服器的傳輸層會將二進制的資料傳給 server 的 stub ,我們可以叫他 Skeleton,他會幫 payment service 解碼,並回傳結果給 Client 。

因為 HTTP 的升級和較簡單的資料格式,有可能比 JSON 快到五倍之多。

Client/Skeleton(server) stub

是由 proto 所生成的代理程式碼,可以想像成是 interface,讓我們不用管底層的傳輸或資料序列化,可以直接來呼叫 gRPC 遠程的服務。

gRPC 範例

我們用 python 來建立一個範例
首先,先建立一個 example.proto

syntax = "proto3";

package example;

service ExampleService {
    rpc Add (AddRequest) returns (AddResponse);
}

message AddRequest {
    int32 a = 1; # 這是序列化 不是賦值
    int32 b = 2; # 這是序列化 不是賦值
}

message AddResponse {
    int32 result = 1; # 這是序列化 不是賦值
}

接著用 grpc_tools 產生兩個檔案

 python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. example.proto
  • example_pb2: proto 中定義的資料結構
  • example_pb2_grpc: 包含 gRPC 會用到的服務和 client/server stub

我們就可以利用這兩個檔案來寫出 server 和 client 端。

  • server.py
import grpc
from concurrent import futures
import example_pb2
import example_pb2_grpc

class ExampleServiceServicer(example_pb2_grpc.ExampleServiceServicer):
    def Add(self, request, context):
        # 實現 Add 服務
        result = request.a + request.b
        return example_pb2.AddResponse(result=result)

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    example_pb2_grpc.add_ExampleServiceServicer_to_server(ExampleServiceServicer(), server)
    server.add_insecure_port('[::]:50051')
    print("gRPC server running on port 50051")
    server.start()
    server.wait_for_termination()

if __name__ == '__main__':
    serve()

  • client.py
import grpc
import example_pb2
import example_pb2_grpc

def run():
    # 連接 gRPC server 
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = example_pb2_grpc.ExampleServiceStub(channel)
        # 發送 Add 請求
        response = stub.Add(example_pb2.AddRequest(a=3, b=4))
        print(f"Add result: {response.result}")

if __name__ == '__main__':
    run()

我們先執行 server.py ,接著執行 client.py ,就可以看到我們的結果囉!

Add result: 7

Reference


上一篇
Day-11 | 簡單上手 Nginx (2)
系列文
埋藏在後端工程下的地雷與寶藏12
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言