Erste Schritte mit gRPC-Python

1. Einführung

In diesem Codelab verwenden Sie gRPC-Python, um einen Client und einen Server zu erstellen, die die Grundlage einer in Python geschriebenen Routenplanungsanwendung bilden.

Am Ende des Tutorials haben Sie einen Client, der über gRPC eine Verbindung zu einem Remote-Server herstellt, um den Namen oder die Postanschrift des Objekts an bestimmten Koordinaten auf einer Karte abzurufen. Eine vollwertige Anwendung könnte dieses Client-Server-Design verwenden, um POIs entlang einer Route aufzulisten oder zusammenzufassen.

Der Dienst wird in einer Protocol Buffers-Datei definiert, die zum Generieren von Boilerplate-Code für den Client und den Server verwendet wird, damit sie miteinander kommunizieren können. So sparen Sie Zeit und Aufwand bei der Implementierung dieser Funktion.

Dieser generierte Code kümmert sich nicht nur um die Komplexität der Kommunikation zwischen Server und Client, sondern auch um die Serialisierung und Deserialisierung von Daten.

Lerninhalte

  • Wie Sie Protocol Buffers zum Definieren einer Dienst-API verwenden.
  • Hier erfahren Sie, wie Sie einen gRPC-basierten Client und Server aus einer Protocol Buffers-Definition mithilfe der automatischen Codegenerierung erstellen.
  • Kenntnisse der Client-Server-Kommunikation mit gRPC

Dieses Codelab richtet sich an Python-Entwickler, die neu in gRPC sind oder eine Auffrischung von gRPC wünschen, sowie an alle anderen, die sich für die Entwicklung verteilter Systeme interessieren. Es sind keine Vorkenntnisse in gRPC erforderlich.

2. Hinweis

Voraussetzungen

  • Python 3.9 oder höher. Wir empfehlen Python 3.13. Plattformspezifische Installationsanleitungen finden Sie unter Python-Einrichtung und ‑Verwendung. Alternativ können Sie mit Tools wie uv oder pyenv eine Nicht-System-Python-Version installieren.
  • pip zum Installieren von Python-Paketen.
  • venv zum Erstellen virtueller Python-Umgebungen.

Die Pakete ensurepip und venv sind Teil der Python-Standardbibliothek und in der Regel standardmäßig verfügbar.

Einige Debian-basierte Distributionen (einschließlich Ubuntu) schließen sie jedoch bei der Weitergabe von Python aus. Führen Sie Folgendes aus, um die Pakete zu installieren:

sudo apt install python3-pip python3-venv

Code abrufen

Um Ihnen den Einstieg zu erleichtern, wird in diesem Codelab ein vorgefertigtes Quellcode-Gerüst bereitgestellt. In den folgenden Schritten wird beschrieben, wie Sie die Anwendung fertigstellen, einschließlich der gRPC-Code-Generierung mit dem grpc_tools.protoc-Compiler-Plug-in für Protocol Buffer.

grpc-codelabs

Der Gerüstquellcode für dieses Codelab ist im Verzeichnis codelabs/grpc-python-getting-started/start_here verfügbar. Wenn Sie den Code nicht selbst implementieren möchten, finden Sie den vollständigen Quellcode im Verzeichnis completed.

Erstellen Sie zuerst das Arbeitsverzeichnis für das Codelab und wechseln Sie in dieses Verzeichnis:

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

Laden Sie das Codelab herunter und extrahieren Sie es:

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

Alternativ können Sie die ZIP-Datei herunterladen, die nur das Codelab-Verzeichnis enthält, und sie manuell entpacken.

3. Dienst definieren

Als Erstes müssen Sie den gRPC-Dienst der Anwendung, die zugehörige RPC-Methode sowie die Anfrage- und Antwortnachrichtentypen mit der Protocol Buffers-Schnittstellendefinitionssprache definieren. Ihr Dienst bietet Folgendes:

  • Eine RPC-Methode namens GetFeature, die vom Server implementiert und vom Client aufgerufen wird.
  • Die Nachrichtentypen Point und Feature sind Datenstrukturen, die beim Verwenden der GetFeature-Methode zwischen dem Client und dem Server ausgetauscht werden. Der Client stellt Kartenkoordinaten als Point in seiner GetFeature-Anfrage an den Server bereit und der Server antwortet mit einem entsprechenden Feature, das beschreibt, was sich an diesen Koordinaten befindet.

Diese RPC-Methode und ihre Nachrichtentypen werden alle in der Datei protos/route_guide.proto des bereitgestellten Quellcodes definiert.

Protocol Buffers werden allgemein als Protobuf bezeichnet. Weitere Informationen zur gRPC-Terminologie finden Sie unter Core concepts, architecture, and lifecycle.

Mitteilungstypen

Definieren Sie zuerst den Nachrichtentyp Point in der Datei protos/route_guide.proto des Quellcodes. Ein Point stellt ein Paar aus Breiten- und Längengrad auf einer Karte dar. Verwenden Sie für dieses Codelab Ganzzahlen für die Koordinaten:

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

Die Zahlen 1 und 2 sind eindeutige ID-Nummern für die einzelnen Felder in der message-Struktur.

Als Nächstes definieren Sie den Nachrichtentyp Feature. Bei einem Feature wird ein string-Feld für den Namen oder die Postanschrift von etwas an einem Standort verwendet, der durch ein Point angegeben wird:

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

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

Servicemethode

Die Datei route_guide.proto hat eine service-Struktur mit dem Namen RouteGuide, die eine oder mehrere Methoden definiert, die vom Dienst der Anwendung bereitgestellt werden.

Fügen Sie die Methode rpc GetFeature in die Definition von RouteGuide ein. Wie bereits erwähnt, wird mit dieser Methode der Name oder die Adresse eines Orts anhand einer bestimmten Menge von Koordinaten ermittelt. Lassen Sie also von GetFeature ein Feature für ein bestimmtes Point zurückgeben:

service RouteGuide {
  // Definition of the service goes here

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

Dies ist eine unäre RPC-Methode: ein einfacher RPC, bei dem der Client eine Anfrage an den Server sendet und auf eine Antwort wartet, genau wie bei einem lokalen Funktionsaufruf.

4. Client- und Servercode generieren

Als Nächstes generieren Sie den Boilerplate-gRPC-Code für Client und Server aus der Datei .proto mit dem Protokollpuffer-Compiler.

Für die Generierung von gRPC-Python-Code haben wir grpcio-tools erstellt. Dazu gehören:

  1. Der reguläre protoc-Compiler, der Python-Code aus message-Definitionen generiert.
  2. gRPC-Protobuf-Plug-in, das Python-Code (Client- und Server-Stubs) aus den service-Definitionen generiert.

Wir installieren das Python-Paket grpcio-tools mit pip. Erstellen wir eine neue virtuelle Python-Umgebung (venv), um die Abhängigkeiten Ihres Projekts von den Systempaketen zu isolieren:

python3 -m venv --upgrade-deps .venv

So aktivieren Sie die virtuelle Umgebung in der Bash-/Zsh-Shell:

source .venv/bin/activate

Informationen zu Windows und nicht standardmäßigen Shells finden Sie in der Tabelle unter https://docs.python.org/3/library/venv.html#how-venvs-work.

Installieren Sie als Nächstes grpcio-tools (dadurch wird auch das Paket grpcio installiert):

pip install grpcio-tools

Verwenden Sie den folgenden Befehl, um den Python-Boilerplate-Code zu generieren:

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

Dadurch werden die folgenden Dateien für die Schnittstellen generiert, die wir in route_guide.proto definiert haben:

  1. route_guide_pb2.py enthält den Code, der dynamisch Klassen erstellt, die aus den message-Definitionen generiert werden.
  2. route_guide_pb2.pyi ist eine Stub-Datei oder „Type Hint“-Datei, die aus den message-Definitionen generiert wird. Sie enthält nur die Signaturen ohne Implementierung. Stub-Dateien können von IDEs verwendet werden, um eine bessere automatische Vervollständigung und Fehlererkennung zu ermöglichen.
  3. route_guide_pb2_grpc.py wird aus den service-Definitionen generiert und enthält gRPC-spezifische Klassen und Funktionen.

Der gRPC-spezifische Code enthält:

  1. RouteGuideStub, die von einem gRPC-Client zum Aufrufen von RouteGuide-RPCs verwendet werden kann.
  2. RouteGuideServicer, das die Schnittstelle für Implementierungen des RouteGuide-Dienstes definiert.
  3. Die add_RouteGuideServicer_to_server-Funktion wird verwendet, um einen RouteGuideServicer auf einem gRPC-Server zu registrieren.

5. Dienst erstellen

Sehen wir uns zuerst an, wie Sie einen RouteGuide-Server erstellen. Das Erstellen und Ausführen eines RouteGuide-Servers lässt sich in zwei Aufgaben unterteilen:

  • Implementieren der Dienstschnittstelle, die aus unserer Dienstdefinition generiert wurde, mit Funktionen, die die eigentliche „Arbeit“ des Dienstes ausführen.
  • Einen gRPC-Server an einem bestimmten Port ausführen, um auf Anfragen von Clients zu warten und Antworten zu übertragen.

Sie finden den ursprünglichen RouteGuide-Server unter start_here/route_guide_server.py.

RouteGuide implementieren

route_guide_server.py hat eine RouteGuideServicer-Klasse, die von der generierten Klasse route_guide_pb2_grpc.RouteGuideServicer abgeleitet wird:

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

RouteGuideServicer implementiert alle RouteGuide-Dienstmethoden.

Sehen wir uns eine einfache RPC-Implementierung genauer an. Die Methode GetFeature ruft eine Point vom Client ab und gibt die entsprechenden Featureinformationen aus ihrer Datenbank in Feature zurück.

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

Der Methode wird eine route_guide_pb2.Point-Anfrage für den RPC und ein grpc.ServicerContext-Objekt übergeben, das RPC-spezifische Informationen wie Zeitüberschreitungslimits enthält. Es wird eine route_guide_pb2.Feature-Antwort zurückgegeben.

Server starten

Nachdem Sie alle RouteGuide-Methoden implementiert haben, müssen Sie einen gRPC-Server starten, damit Clients Ihren Dienst verwenden können:

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()

Die Servermethode start() ist nicht blockierend. Ein neuer Thread wird instanziiert, um Anfragen zu verarbeiten. Der Thread, der server.start() aufruft, hat in der Zwischenzeit oft nichts anderes zu tun. In diesem Fall können Sie server.wait_for_termination() aufrufen, um den aufrufenden Thread sauber zu blockieren, bis der Server beendet wird.

6. Client erstellen

In diesem Abschnitt sehen wir uns an, wie wir einen Client für unseren RouteGuide-Dienst erstellen. Den ursprünglichen Clientcode finden Sie in start_here/route_guide_client.py.

Stub erstellen

Um Dienstmethoden aufzurufen, müssen wir zuerst einen Stub erstellen.

Wir instanziieren die Klasse RouteGuideStub des Moduls route_guide_pb2_grpc, die aus unserem .proto in der Datei route_guide_client.py generiert wurde.

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

Dienstmethoden aufrufen

Für RPC-Methoden, die eine einzelne Antwort zurückgeben (sogenannte response-unary-Methoden), unterstützt gRPC Python sowohl synchrone (blockierende) als auch asynchrone (nicht blockierende) Semantik für den Kontrollfluss.

Einfache RPC

Definieren wir zuerst eine Point, mit der der Dienst aufgerufen werden kann. Dazu muss lediglich ein Objekt aus dem route_guide_pb2-Paket mit einigen Properties instanziiert werden:

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

Ein synchroner Aufruf des einfachen RPC GetFeature ist fast so einfach wie der Aufruf einer lokalen Methode. Der RPC-Aufruf wartet auf die Antwort des Servers und gibt entweder eine Antwort zurück oder löst eine Ausnahme aus. Wir können die Methode so aufrufen und die Antwort ansehen:

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

Sie können die Felder des Feature-Objekts untersuchen und das Ergebnis der Anfrage ausgeben:

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. Jetzt ausprobieren

Führen Sie den Server aus:

python route_guide_server.py

Aktivieren Sie die virtuelle Umgebung in einem anderen Terminal noch einmal und führen Sie dann den Client aus:

python route_guide_client.py

Die Ausgabe sieht so aus (Zeitstempel wurden der Übersichtlichkeit halber weggelassen):

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. Nächste Schritte