gRPC (gRPC Remote Procedure Calls) ist ein modernes, leistungsfähiges Framework für die Kommunikation zwischen verteilten Systemen. Es baut auf dem HTTP/2-Protokoll auf und nutzt Protocol Buffers (Protobuf) als Interface Definition Language (IDL) sowie als Serialisierungsmechanismus. In diesem Kapitel wird die detaillierte Funktionsweise von gRPC erläutert, einschließlich der zugrunde liegenden Technologien, der Architektur und der Kommunikationsmodelle.
Protocol Buffers (Protobuf)
Protobuf ist ein binäres Serialisierungsformat, das eine effiziente und plattformunabhängige Datenübertragung ermöglicht. Es dient als Interface Definition Language (IDL) für die Definition von Diensten und Nachrichtenstrukturen in gRPC.
.proto)
definieren die Struktur der Nachrichten und die Dienste mit ihren
Methoden.Beispiel einer Protobuf-Datei:
syntax = "proto3";
package com.example.bookservice;
service BookService {
rpc GetBook(GetBookRequest) returns (GetBookResponse);
rpc ListBooks(ListBooksRequest) returns (stream ListBooksResponse);
}
message GetBookRequest {
int32 id = 1;
}
message GetBookResponse {
Book book = 1;
}
message ListBooksRequest {}
message ListBooksResponse {
Book book = 1;
}
message Book {
int32 id = 1;
string title = 2;
string author = 3;
}HTTP/2
gRPC nutzt HTTP/2 als Transportprotokoll, was verschiedene Vorteile gegenüber HTTP/1.1 bietet:
Die gRPC-Architektur besteht aus mehreren Schlüsselkomponenten:
Client
Der Client initiiert die Kommunikation, indem er RPC-Aufrufe an den Server sendet. Er verwendet die generierten Stub-Klassen, um Methoden des definierten Dienstes aufzurufen.
Server
Der Server implementiert die definierten Dienste und Methoden. Er empfängt RPC-Aufrufe vom Client, verarbeitet sie und sendet die entsprechenden Antworten zurück.
Stub
Stubs sind clientseitige Proxy-Klassen, die die Methoden des Dienstes kapseln. Sie abstrahieren die Netzwerkkommunikation und ermöglichen es dem Entwickler, Methodenaufrufe wie lokale Methoden zu behandeln.
Protobuf
Protobuf-Dateien definieren die Struktur der Nachrichten und die Dienste. Sie werden verwendet, um sowohl den Client als auch den Server zu generieren.
gRPC unterstützt verschiedene Kommunikationsmodelle, die unterschiedliche Anforderungen an die Datenübertragung und Interaktion zwischen Client und Server erfüllen:
Unary RPC
Ein einfaches Anfrage-Antwort-Muster, bei dem der Client eine Anfrage sendet und der Server eine einzelne Antwort zurückgibt.
Server Streaming RPC
Der Client sendet eine einzelne Anfrage und erhält eine stream von Antworten vom Server.
Diagramm: Server Streaming RPC
sequenceDiagram
participant Client
participant Server
Client->>Server: ListBooks(ListBooksRequest)
Server-->>Client: ListBooksResponse (Stream)Client Streaming RPC
Der Client sendet eine stream von Anfragen an den Server und erhält eine einzelne Antwort zurück.
Bidirektionales Streaming RPC
Sowohl der Client als auch der Server können gleichzeitig streams von Nachrichten senden und empfangen.
Die Integration von gRPC in Spring Boot erfolgt durch die Verwendung
von Bibliotheken wie grpc-spring-boot-starter, die eine
nahtlose Einbindung und Konfiguration von gRPC-Servern und -Clients
ermöglichen.
Protobuf-Datei definieren
Erstellen Sie eine .proto-Datei, die die Dienste und
Nachrichten definiert. Ein Beispiel wurde bereits in den Grundlagen
gezeigt.
Abhängigkeiten hinzufügen
Fügen Sie die notwendigen Abhängigkeiten zu Ihrer
pom.xml hinzu:
<dependencies>
<!-- gRPC und Protobuf Abhängigkeiten -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.42.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.42.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.42.1</version>
</dependency>
<!-- Spring Boot Starter für gRPC -->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-spring-boot-starter</artifactId>
<version>2.12.0.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Protobuf Maven Plugin -->
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.19.1:exe:${os.detected.classifier}</protocArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>Generierung der Protobuf-Klassen
Nach dem Hinzufügen der Protobuf-Maven-Plugin-Konfiguration wird der
Code aus der .proto-Datei automatisch generiert, wenn das
Projekt gebaut wird.
Service-Implementierung erstellen
Implementieren Sie den definierten Dienst, indem Sie die generierte Stub-Klasse erweitern und die Methoden überschreiben.
Beispiel: BookServiceImpl.java
package com.example.bookservice;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@GrpcService
public class BookServiceImpl extends BookServiceGrpc.BookServiceImplBase {
private final ConcurrentHashMap<Integer, Book> bookRepository = new ConcurrentHashMap<>();
private final AtomicInteger idCounter = new AtomicInteger(1);
@Override
public void getBook(GetBookRequest request, StreamObserver<GetBookResponse> responseObserver) {
Book book = bookRepository.get(request.getId());
if (book != null) {
GetBookResponse response = GetBookResponse.newBuilder().setBook(book).build();
responseObserver.onNext(response);
responseObserver.onCompleted();
} else {
responseObserver.onError(new Throwable("Buch nicht gefunden"));
}
}
@Override
public void listBooks(ListBooksRequest request, StreamObserver<ListBooksResponse> responseObserver) {
for (Book book : bookRepository.values()) {
ListBooksResponse response = ListBooksResponse.newBuilder().setBook(book).build();
responseObserver.onNext(response);
}
responseObserver.onCompleted();
}
@Override
public StreamObserver<AddBooksRequest> addBooks(final StreamObserver<AddBooksResponse> responseObserver) {
return new StreamObserver<AddBooksRequest>() {
int successCount = 0;
@Override
public void onNext(AddBooksRequest addBooksRequest) {
Book book = addBooksRequest.getBook();
int id = idCounter.getAndIncrement();
Book newBook = Book.newBuilder()
.setId(id)
.setTitle(book.getTitle())
.setAuthor(book.getAuthor())
.build();
bookRepository.put(id, newBook);
successCount++;
}
@Override
public void onError(Throwable t) {
// Fehlerbehandlung
}
@Override
public void onCompleted() {
AddBooksResponse response = AddBooksResponse.newBuilder()
.setSuccessCount(successCount)
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
};
}
@Override
public StreamObserver<UpdateBooksRequest> updateBooks(final StreamObserver<UpdateBooksResponse> responseObserver) {
return new StreamObserver<UpdateBooksRequest>() {
@Override
public void onNext(UpdateBooksRequest updateBooksRequest) {
Book book = updateBooksRequest.getBook();
if (bookRepository.containsKey(book.getId())) {
bookRepository.put(book.getId(), book);
UpdateBooksResponse response = UpdateBooksResponse.newBuilder()
.setStatus("Erfolgreich aktualisiert")
.build();
responseObserver.onNext(response);
} else {
UpdateBooksResponse response = UpdateBooksResponse.newBuilder()
.setStatus("Buch nicht gefunden")
.build();
responseObserver.onNext(response);
}
}
@Override
public void onError(Throwable t) {
// Fehlerbehandlung
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
}Erläuterung des Codes:**
- **@GrpcService**: Kennzeichnet die Klasse als gRPC-Service, der von Spring Boot erkannt und registriert wird.
- **BookServiceImpl**: Implementiert die Methoden des definierten `BookService`.
- **bookRepository**: Eine einfache In-Memory-Datenbank zur Speicherung der Bücher.
- **getBook**: Implementiert den Unary RPC zum Abrufen eines Buches nach ID.
- **listBooks**: Implementiert den Server Streaming RPC zum Auflisten aller Bücher.
- **addBooks**: Implementiert den Client Streaming RPC zum Hinzufügen mehrerer Bücher.
- **updateBooks**: Implementiert den Bidirektionalen Streaming RPC zum Aktualisieren von Büchern.
gRPC-Client erstellen
Um den gRPC-Server zu nutzen, muss ein Client erstellt werden, der die generierten Stub-Klassen verwendet, um RPC-Aufrufe zu tätigen.
Beispiel: BookClient.java
package com.example.bookclient;
import com.example.bookservice.*;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class BookClient {
private final BookServiceGrpc.BookServiceBlockingStub blockingStub;
private final BookServiceGrpc.BookServiceStub asyncStub;
public BookClient(String host, int port) {
ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port)
.usePlaintext() // Für Entwicklungszwecke; in der Produktion SSL/TLS verwenden
.build();
blockingStub = BookServiceGrpc.newBlockingStub(channel);
asyncStub = BookServiceGrpc.newStub(channel);
}
public void getBook(int id) {
GetBookRequest request = GetBookRequest.newBuilder().setId(id).build();
try {
GetBookResponse response = blockingStub.getBook(request);
System.out.println("Buch: " + response.getBook().getTitle() + " von " + response.getBook().getAuthor());
} catch (Exception e) {
System.err.println("Fehler beim Abrufen des Buches: " + e.getMessage());
}
}
public void listBooks() {
ListBooksRequest request = ListBooksRequest.newBuilder().build();
try {
blockingStub.listBooks(request).forEachRemaining(response -> {
System.out.println("Buch: " + response.getBook().getTitle() + " von " + response.getBook().getAuthor());
});
} catch (Exception e) {
System.err.println("Fehler beim Auflisten der Bücher: " + e.getMessage());
}
}
public void addBooks() throws InterruptedException {
final CountDownLatch finishLatch = new CountDownLatch(1);
StreamObserver<AddBooksResponse> responseObserver = new StreamObserver<AddBooksResponse>() {
@Override
public void onNext(AddBooksResponse value) {
System.out.println("Erfolgreich hinzugefügt: " + value.getSuccessCount() + " Bücher");
}
@Override
public void onError(Throwable t) {
System.err.println("Fehler beim Hinzufügen der Bücher: " + t.getMessage());
finishLatch.countDown();
}
@Override
public void onCompleted() {
finishLatch.countDown();
}
};
StreamObserver<AddBooksRequest> requestObserver = asyncStub.addBooks(responseObserver);
try {
for (int i = 1; i <= 3; i++) {
Book book = Book.newBuilder()
.setTitle("Neues Buch " + i)
.setAuthor("Autor " + i)
.build();
AddBooksRequest request = AddBooksRequest.newBuilder().setBook(book).build();
requestObserver.onNext(request);
Thread.sleep(100); // Simuliert Verzögerung
}
} catch (RuntimeException e) {
requestObserver.onError(e);
throw e;
}
requestObserver.onCompleted();
if (!finishLatch.await(1, TimeUnit.MINUTES)) {
System.err.println("addBooks kann nicht abgeschlossen werden");
}
}
public static void main(String[] args) throws InterruptedException {
BookClient client = new BookClient("localhost", 9090);
client.addBooks();
client.listBooks();
client.getBook(1);
}
}Erläuterung des Codes:
usePlaintext()
verwendet; in der Produktion sollte SSL/TLS verwendet werden.Diagramm: gRPC-Kommunikationsfluss
sequenceDiagram
participant Client
participant Server
Client->>Server: GetBook(GetBookRequest)
Server-->>Client: GetBookResponse
Client->>Server: ListBooks(ListBooksRequest)
Server-->>Client: ListBooksResponse (Stream)
Client->>Server: AddBooks(AddBooksRequest) (Stream)
Server-->>Client: AddBooksResponse
Client->>Server: UpdateBooks(UpdateBooksRequest) (Stream)
Server-->>Client: UpdateBooksResponse (Stream)
Verwendung von Protobuf-Schemata
Optimierung der Leistung
Sicherheitsimplementierung
Fehlerbehandlung
Monitoring und Logging
Versionierung von Diensten
Skalierung und Load Balancing
Dokumentation
gRPC ist ein modernes RPC-Framework, das auf HTTP/2 und Protocol Buffers basiert und eine effiziente, plattformübergreifende Kommunikation zwischen verteilten Systemen ermöglicht. Es unterstützt verschiedene Kommunikationsmodelle, bietet hohe Leistung und umfassende Sprachunterstützung. Durch die Integration mit Spring Boot können Entwickler gRPC-Dienste nahtlos implementieren und von den Vorteilen moderner Infrastrukturtechnologien profitieren. Das Verständnis der Funktionsweise von gRPC ist essenziell für die Entwicklung leistungsfähiger und skalierbarer Microservices-Architekturen.