Sehen wir uns an, wie Sie gRPC in Java implementieren.
gRPC (Google Remote Procedure Call): gRPC ist eine Open-Source-RPC-Architektur, die von Google entwickelt wurde, um eine Hochgeschwindigkeitskommunikation zwischen Microservices zu ermöglichen. gRPC ermöglicht es Entwicklern, in verschiedenen Sprachen geschriebene Dienste zu integrieren. gRPC verwendet das Protobuf-Messaging-Format (Protocol Buffers), ein hocheffizientes, stark gepacktes Messaging-Format zum Serialisieren strukturierter Daten.
Für einige Anwendungsfälle kann die gRPC-API effizienter sein als die REST API.
Versuchen wir, einen Server auf gRPC zu schreiben. Zuerst müssen wir mehrere schreiben .proto
Dateien, die Dienste und Modelle (DTO) beschreiben. Für einen einfachen Server verwenden wir ProfileService und ProfileDescriptor.
ProfileService sieht so aus:
syntax = "proto3";
package com.deft.grpc;
import "google/protobuf/empty.proto";
import "profile_descriptor.proto";
service ProfileService {
rpc GetCurrentProfile (google.protobuf.Empty) returns (ProfileDescriptor) {}
rpc clientStream (stream ProfileDescriptor) returns (google.protobuf.Empty) {}
rpc serverStream (google.protobuf.Empty) returns (stream ProfileDescriptor) {}
rpc biDirectionalStream (stream ProfileDescriptor) returns (stream ProfileDescriptor) {}
}
gRPC unterstützt eine Vielzahl von Client-Server-Kommunikationsoptionen. Wir werden sie alle aufschlüsseln:
- Normaler Serveraufruf – Anfrage/Antwort.
- Streaming vom Client zum Server.
- Streaming vom Server zum Client.
- Und natürlich der bidirektionale Stream.
Der ProfileService-Dienst verwendet den ProfileDescriptor, der im Importabschnitt angegeben ist:
syntax = "proto3";
package com.deft.grpc;
message ProfileDescriptor {
int64 profile_id = 1;
string name = 2;
}
- int64 ist Long für Java. Lassen Sie die Profil-ID gehören.
- Schnur – Genau wie in Java ist dies eine String-Variable.
Sie können Gradle oder Maven verwenden, um das Projekt zu erstellen. Es ist bequemer für mich, Maven zu verwenden. Und weiter wird der Code mit Maven sein. Dies ist wichtig genug, um zu sagen, dass die zukünftige Generation der .proto für Gradle etwas anders sein wird und die Build-Datei anders konfiguriert werden muss. Um einen einfachen gRPC-Server zu schreiben, benötigen wir nur eine Abhängigkeit:
<dependency>
<groupId>io.github.lognet</groupId>
<artifactId>grpc-spring-boot-starter</artifactId>
<version>4.5.4</version>
</dependency>
Es ist einfach unglaublich. Dieser Starter macht uns enorm viel Arbeit.
Das von uns erstellte Projekt sieht in etwa so aus:
Wir benötigen GrpcServerApplication, um die Spring Boot-Anwendung zu starten. Und GrpcProfileService, das Methoden aus dem .proto
Bedienung. Um protoc zu verwenden und Klassen aus geschriebenen .proto-Dateien zu generieren, fügen Sie protobuf-maven-plugin zu pom.xml hinzu. Der Build-Bereich sieht so aus:
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
<outputDirectory>${basedir}/target/generated-sources/grpc-java</outputDirectory>
<protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.38.0:exe:${os.detected.classifier}</pluginArtifact>
<clearOutputDirectory>false</clearOutputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
- protoSourceRoot – Angabe des Verzeichnisses, in dem sich die .proto-Dateien befinden.
- Ausgabe Verzeichnis – Wählen Sie das Verzeichnis aus, in dem die Dateien generiert werden sollen.
- clearOutputDirectory – ein Flag, das angibt, generierte Dateien nicht zu löschen.
In dieser Phase können Sie ein Projekt erstellen. Als nächstes müssen Sie zu dem Ordner gehen, den wir im Ausgabeverzeichnis angegeben haben. Die generierten Dateien werden dort sein. Jetzt können Sie nach und nach umsetzen GrpcProfileService.
Die Klassendeklaration sieht wie folgt aus:
@GRpcService
public class GrpcProfileService extends ProfileServiceGrpc.ProfileServiceImplBase
GRpcService annotation – Markiert die Klasse als grpc-service-Bean.
Da wir unseren Service erben von ProfileServiceGrpc, ProfileServiceImplBase, können wir die Methoden der Elternklasse überschreiben. Die erste Methode, die wir überschreiben werden, ist GetCurrentProfile:
@Override
public void getCurrentProfile(Empty request, StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> responseObserver) {
System.out.println("getCurrentProfile");
responseObserver.onNext(ProfileDescriptorOuterClass.ProfileDescriptor
.newBuilder()
.setProfileId(1)
.setName("test")
.build());
responseObserver.onCompleted();
}
Um dem Kunden zu antworten, müssen Sie den aufWeiter -Methode auf dem übergebenen StreamObserver. Senden Sie nach dem Senden der Antwort ein Signal an den Client, dass der Server seine Arbeit beendet hat onAbgeschlossen. Beim Senden einer Anfrage an den getCurrentProfile-Server lautet die Antwort:
{
"profile_id": "1",
"name": "test"
}
Als nächstes werfen wir einen Blick auf den Server-Stream. Bei diesem Messaging-Ansatz sendet der Client eine Anfrage an den Server, der Server antwortet dem Client mit einem Nachrichtenstrom. Es sendet beispielsweise fünf Anfragen in einer Schleife. Wenn das Senden abgeschlossen ist, sendet der Server eine Nachricht an den Client über den erfolgreichen Abschluss des Streams.
Die überschriebene Server-Stream-Methode sieht wie folgt aus:
@Override
public void serverStream(Empty request, StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> responseObserver) {
for (int i = 0; i < 5; i++) {
responseObserver.onNext(ProfileDescriptorOuterClass.ProfileDescriptor
.newBuilder()
.setProfileId(i)
.build());
}
responseObserver.onCompleted();
}
Somit erhält der Client fünf Nachrichten mit einer ProfileId, die der Antwortnummer entspricht.
{
"profile_id": "0",
"name": ""
}
{
"profile_id": "1",
"name": ""
}
…
{
"profile_id": "4",
"name": ""
}
Der Client-Stream ist dem Server-Stream sehr ähnlich. Erst jetzt sendet der Client einen Nachrichtenstrom und der Server verarbeitet sie. Der Server kann Nachrichten sofort verarbeiten oder auf alle Anfragen des Clients warten und diese dann verarbeiten.
@Override
public StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> clientStream(StreamObserver<Empty> responseObserver) {
return new StreamObserver<>() {
@Override
public void onNext(ProfileDescriptorOuterClass.ProfileDescriptor profileDescriptor) {
log.info("ProfileDescriptor from client. Profile id: {}", profileDescriptor.getProfileId());
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
Im Client-Stream müssen Sie die StreamObserver an den Client, an den der Server Nachrichten erhält. Die Methode onError wird aufgerufen, wenn im Stream ein Fehler aufgetreten ist. Zum Beispiel wurde es falsch beendet.
Um einen bidirektionalen Stream zu implementieren, ist es notwendig, die Erstellung eines Streams vom Server und dem Client zu kombinieren.
@Override
public StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> biDirectionalStream(
StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> responseObserver) {
return new StreamObserver<>() {
int pointCount = 0;
@Override
public void onNext(ProfileDescriptorOuterClass.ProfileDescriptor profileDescriptor) {
log.info("biDirectionalStream, pointCount {}", pointCount);
responseObserver.onNext(ProfileDescriptorOuterClass.ProfileDescriptor
.newBuilder()
.setProfileId(pointCount++)
.build());
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
In diesem Beispiel gibt der Server als Antwort auf die Nachricht des Clients ein Profil mit einem erhöhten zurück Punktzahl.
Fazit
Wir haben die grundlegenden Optionen für die Nachrichtenübermittlung zwischen einem Client und einem Server unter Verwendung von . behandelt gRPC: implementierter Server-Stream, Client-Stream, bidirektionaler Stream.
Der Artikel wurde von Sergey Golitsyn . geschrieben