開始使用 gRPC-Python

1. 簡介

在本程式碼研究室中,您將使用 gRPC-Python 建立用戶端和伺服器,做為以 Python 編寫的路線對應應用程式基礎。

完成本教學課程後,您將擁有一個用戶端,可使用 gRPC 連線至遠端伺服器,取得地圖上特定座標位置的名稱或郵寄地址。完整的應用程式可能會使用這種用戶端/伺服器設計,列舉或摘要說明路線上的興趣點。

服務定義於 Protocol Buffers 檔案中,用於產生用戶端和伺服器的樣板程式碼,以便彼此通訊,節省您實作該功能的時間和精力。

這段產生的程式碼不僅會處理伺服器與用戶端之間複雜的通訊,也會處理資料序列化和還原序列化。

課程內容

  • 如何使用通訊協定緩衝區定義服務 API。
  • 如何使用自動程式碼生成功能,從通訊協定緩衝區定義建構以 gRPC 為基礎的用戶端和伺服器。
  • 瞭解如何透過 gRPC 進行用戶端與伺服器之間的通訊。

這個程式碼研究室適用於剛接觸 gRPC 或想複習 gRPC 的 Python 開發人員,以及有興趣建構分散式系統的任何人。不需要有 gRPC 相關經驗。

2. 事前準備

軟硬體需求

  • Python 3.9 以上版本。建議使用 Python 3.13。如需特定平台的安裝說明,請參閱「Python 設定和使用方式」。或者,您也可以使用 uv pyenv 等工具,安裝非系統 Python。
  • pip 安裝 Python 套件。
  • venv 建立 Python 虛擬環境。

ensurepipvenv 套件是 Python 標準程式庫的一部分,通常預設會提供。

不過,部分以 Debian 為基礎的發行版本 (包括 Ubuntu) 會選擇在重新發布 Python 時排除這些檔案。如要安裝套件,請執行:

sudo apt install python3-pip python3-venv

取得程式碼

為簡化學習過程,本程式碼研究室提供預先建構的原始碼架構,協助您踏出第一步。下列步驟將引導您完成應用程式,包括使用 grpc_tools.protoc Protocol Buffer 編譯器外掛程式產生 gRPC 程式碼。

grpc-codelabs

本程式碼研究室的架構原始碼位於 codelabs/grpc-python-getting-started/start_here 目錄。如果您不想自行導入程式碼,完成的原始碼會放在 completed 目錄中。

首先,請建立程式碼研究室工作目錄,然後 cd 到該目錄:

mkdir grpc-python-getting-started && cd grpc-python-getting-started

下載並擷取程式碼研究室:

curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/v1.tar.gz \
  | tar xvz --strip-components=4 \
  grpc-codelabs-1/codelabs/grpc-python-getting-started/start_here

或者,您也可以下載只包含 Codelab 目錄的 .zip 檔案,然後手動解壓縮。

3. 定義服務

首先,請使用通訊協定緩衝區介面定義語言,定義應用程式的 gRPC 服務、RPC 方法,以及要求和回應訊息類型。你的服務將提供:

  • 伺服器實作且用戶端呼叫的 RPC 方法,稱為 GetFeature
  • 使用 GetFeature 方法時,訊息類型 PointFeature 是用戶端與伺服器之間交換的資料結構。用戶端會在傳送至伺服器的 GetFeature 要求中,以 Point 形式提供地圖座標,而伺服器會回覆相應的 Feature,說明這些座標所指的位置。

這個 RPC 方法及其訊息型別都會在提供的原始碼 protos/route_guide.proto 檔案中定義。

通訊協定緩衝區通常稱為 protobuf。如要進一步瞭解 gRPC 術語,請參閱 gRPC 的「核心概念、架構和生命週期」。

訊息類型

在原始碼的 protos/route_guide.proto 檔案中,請先定義 Point 訊息型別。Point 代表地圖上的經緯度座標組合。在本程式碼研究室中,請使用整數做為座標:

message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

數字 12message 結構中每個欄位的專屬 ID 編號。

接著,定義 Feature 訊息類型。Feature 會使用 string 欄位,指定 Point 所指定位置的名稱或郵寄地址:

message Feature {
  // The name or address of the feature.
  string name = 1;

  // The point where the feature is located.
  Point location = 2;
}

服務方法

route_guide.proto 檔案具有名為 RouteGuideservice 結構,可定義應用程式服務提供的一或多個方法。

RouteGuide 定義中新增 rpc 方法 GetFeature。如先前所述,這個方法會根據一組指定座標查閱地點名稱或地址,因此請讓 GetFeature 針對指定 Point 傳回 Feature

service RouteGuide {
  // Definition of the service goes here

  // Obtains the feature at a given position.
  rpc GetFeature(Point) returns (Feature) {}
}

這是 unary RPC 方法:簡單的 RPC,用戶端會將要求傳送至伺服器,並等待伺服器傳回回應,就像呼叫本機函式一樣。

4. 產生用戶端和伺服器程式碼

接著,使用通訊協定緩衝區編譯器,從 .proto 檔案產生用戶端和伺服器的樣板 gRPC 程式碼。

我們建立了 grpcio-tools,用於生成 gRPC Python 程式碼。It includes:

  1. 可從 message 定義產生 Python 程式碼的標準 protoc 編譯器。
  2. gRPC protobuf 外掛程式,可從 service 定義產生 Python 程式碼 (用戶端和伺服器存根)。

我們將使用 pip 安裝 grpcio-tools Python 套件。讓我們建立新的 Python 虛擬環境 (venv),將專案的依附元件與系統套件區隔開來:

python3 -m venv --upgrade-deps .venv

如要在 bash/zsh 殼層中啟動虛擬環境,請執行下列指令:

source .venv/bin/activate

如為 Windows 和非標準殼層,請參閱 https://docs.python.org/3/library/venv.html#how-venvs-work 中的表格。

接著,請安裝 grpcio-tools (這也會安裝 grpcio 套件):

pip install grpcio-tools

使用下列指令產生 Python 樣板程式碼:

python -m grpc_tools.protoc --proto_path=./protos  \
 --python_out=. --pyi_out=. --grpc_python_out=. \
 ./protos/route_guide.proto

這會為我們在 route_guide.proto 中定義的介面產生下列檔案:

  1. route_guide_pb2.py 包含動態建立類別的程式碼,這些類別是從 message 定義產生。
  2. route_guide_pb2.pyi 是從 message 定義產生的「存根檔案」或「型別提示檔案」。其中只包含沒有實作的簽章。IDE 可使用 Stub 檔案,提供更完善的自動完成和錯誤偵測功能。
  3. route_guide_pb2_grpc.py 是從 service 定義產生,包含 gRPC 專屬的類別和函式。

gRPC 專屬程式碼包含:

  1. RouteGuideStubgRPC 用戶端可使用此檔案叫用 RouteGuide RPC。
  2. RouteGuideServicer,定義 RouteGuide 服務實作的介面。
  3. add_RouteGuideServicer_to_server 函式,用於向 gRPC 伺服器註冊 RouteGuideServicer

5. 建立服務

首先,我們來看看如何建立 RouteGuide 伺服器。建立及執行 RouteGuide 伺服器可分為兩項工作:

  • 實作從服務定義產生的服務介面,並使用可執行服務實際「工作」的函式。
  • 在特定通訊埠執行 gRPC 伺服器,監聽來自用戶端的要求並傳輸回應。

您可以在 start_here/route_guide_server.py 中找到初始 RouteGuide 伺服器。

實作 RouteGuide

route_guide_server.py 具有 RouteGuideServicer 類別,可將產生的類別 route_guide_pb2_grpc.RouteGuideServicer 子類別化:

# RouteGuideServicer provides an implementation
# of the methods of the RouteGuide service.
class RouteGuideServicer(route_guide_pb2_grpc.RouteGuideServicer):

RouteGuideServicer 會實作所有 RouteGuide 服務方法。

現在來詳細瞭解簡單的 RPC 實作方式。方法 GetFeature 會從用戶端取得 Point,並從資料庫中傳回 Feature 的對應功能資訊。

def GetFeature(self, request, context):
    feature = get_feature(self.db, request)
    if feature is None:
        return route_guide_pb2.Feature(name="", location=request)
    else:
        return feature

這個方法會傳遞 RPC 的 route_guide_pb2.Point 請求,以及提供 RPC 專屬資訊 (例如逾時限制) 的 grpc.ServicerContext 物件。系統會傳回 route_guide_pb2.Feature 回應。

啟動伺服器

實作所有 RouteGuide 方法後,下一步是啟動 gRPC 伺服器,讓用戶端實際使用您的服務:

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    route_guide_pb2_grpc.add_RouteGuideServicer_to_server(
        RouteGuideServicer(),
        server,
    )
    listen_addr = "localhost:50051"
    server.add_insecure_port(listen_addr)
    print(f"Starting server on {listen_addr}")
    server.start()
    server.wait_for_termination()

伺服器 start() 方法不會封鎖。系統會例項化新執行緒來處理要求。呼叫 server.start() 的執行緒通常不會有其他工作要做。在這種情況下,您可以呼叫 server.wait_for_termination(),乾淨地封鎖呼叫執行緒,直到伺服器終止為止。

6. 建立用戶端

在本節中,我們將說明如何為 RouteGuide 服務建立用戶端。您可以在 start_here/route_guide_client.py 中查看初始用戶端程式碼。

建立存根

如要呼叫服務方法,我們必須先建立 stub

我們會在 route_guide_client.py 檔案中,從 .proto 內例項化 route_guide_pb2_grpc 模組的 RouteGuideStub 類別。

channel = grpc.insecure_channel("localhost:50051")
stub = route_guide_pb2_grpc.RouteGuideStub(channel)

呼叫服務方法

對於傳回單一回應的 RPC 方法 (稱為「回應一元」方法),gRPC Python 同時支援同步 (封鎖) 和非同步 (非封鎖) 控制流程語意。

簡單 RPC

首先,請定義要用來呼叫服務的 Point。這應該很簡單,只要從 route_guide_pb2 套件例項化物件,並提供一些屬性即可:

point = route_guide_pb2.Point(latitude=412346009, longitude=-744026814)

同步呼叫簡單的 RPC GetFeature,幾乎與呼叫本機方法一樣簡單。RPC 呼叫會等待伺服器回應,並傳回回應或引發例外狀況。我們可以呼叫方法並查看回應,如下所示:

feature = stub.GetFeature(point)
print(feature)

您可以檢查 Feature 物件的欄位,並輸出要求的結果:

if feature.name:
    print(f"Feature called '{feature.name}' at {format_point(feature.location)}")
else:
    print(f"Found no feature at at {format_point(feature.location)}")

7. 立即試用

執行伺服器:

python route_guide_server.py

在其他終端機中再次啟動虛擬環境,然後執行用戶端:

python route_guide_client.py

您會看到類似以下的輸出內容,為求清楚起見,時間戳記已省略:

name: "16 Old Brook Lane, Warwick, NY 10990, USA"
location {
  latitude: 412346009
  longitude: -744026814
}

Feature called '16 Old Brook Lane, Warwick, NY 10990, USA' at latitude: 412346009, longitude: -744026814

8. 後續步驟