Java-gRPC von Scratch

Sehen wir uns an, wie Sie gRPC in Java implementieren.
gRPC (Google Remote Procedure Call): gRPC ist eine von Google entwickelte Open-Source-RPC-Architektur, um eine Hochgeschwindigkeitskommunikation zwischen Mikrodiensten zu ermöglichen. gRPC ermöglicht Entwicklern die Integrationate Dienstleistungen, die in verschiedenen Sprachen verfasst sind. gRPC verwendet das Protobuf-Nachrichtenformat (Protocol Buffers), ein hocheffizientes, hochgepacktes Nachrichtenformat zur Serialisierung 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 beschreiben (DTO). Für einen einfachen Server verwenden wir ProfileService und ProfileDescriptoder.
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 ProfiDer Dienst leService verwendet die ProfileDescriptoder, was 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 profiIch gehöre dazu.
- 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 Projekt, das wir erstellen werdenate wird etwa so aussehen:
Wir benötigen GrpcServerApplication, um die Spring Boot-Anwendung zu starten. Und GrpcProfileService, der Methoden aus dem implementiert .proto
Service. Um protoc und gener zu verwendenate Klassen aus geschriebenen .proto-Dateien, fügen Sie protobuf-maven-plugin zu pom.xml hinzu. Der Build-Abschnitt sieht folgendermaßen 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 befindenated.
- Ausgabe Verzeichnis – Wählen Sie das Verzeichnis aus, in dem die Dateien gespeichert werden sollenated.
- clearOutputDirectory – eine Markierung, die angibt, dass das Gener nicht gelöscht werden sollated-Dateien.
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. Das Genated-Dateien werden dort sein. Jetzt können Sie Ihren Abschluss machenally implementieren 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 getCurrentProfile Server, die Antwort wird sein:
{
"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 a ProfileId, gleich der Antwortnummer.
{
"profile_id": "0",
"name": ""
}
{
"profile_id": "1",
"name": ""
}
…
{
"profile_id": "4",
"name": ""
}
Der Client-Stream ist dem Server-Stream sehr ähnlich. Erst jetzt überträgt der Client einen Nachrichtenstrom an den Server processEs sind sie. Der Server kann process Nachrichten sofortately oder warten Sie auf alle Anfragen des Clients und dann process Them.
@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 empfängt. Die Methode onError wird aufgerufen, wenn im Stream ein Fehler aufgetreten ist. Zum Beispiel ist es terminated falsch.
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 eine zurück profile mit einer erhöhten Punktzahl.
Schlussfolgerung
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