Sıfırdan Java gRPC

Yayınlanan: 2021-07-16

Java'da gRPC'nin nasıl uygulanacağını keşfedelim.

gRPC (Google Uzaktan Yordam Çağrısı): gRPC, mikro hizmetler arasında yüksek hızlı iletişimi sağlamak için Google tarafından geliştirilen açık kaynaklı bir RPC mimarisidir. gRPC, geliştiricilerin farklı dillerde yazılmış hizmetleri entegre etmesine olanak tanır. gRPC, yapılandırılmış verileri seri hale getirmek için oldukça verimli, yüksek oranda paketlenmiş bir mesajlaşma formatı olan Protobuf mesajlaşma formatını (Protokol Tamponları) kullanır.

Bazı kullanım durumları için gRPC API, REST API'sinden daha verimli olabilir.

gRPC üzerinde bir sunucu yazmaya çalışalım. İlk olarak, servisleri ve modelleri (DTO) tanımlayan birkaç .proto dosyası .proto gerekiyor. Basit bir sunucu için ProfileService ve ProfileDescriptor kullanacağız.

ProfileService şöyle görünür:

 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, çeşitli istemci-sunucu iletişim seçeneklerini destekler. Hepsini parçalayacağız:

  • Normal sunucu çağrısı – istek/yanıt.
  • İstemciden sunucuya akış.
  • Sunucudan istemciye akış.
  • Ve tabii ki, çift yönlü akış.

ProfileService hizmeti, içe aktarma bölümünde belirtilen ProfileDescriptor'ı kullanır:

 syntax = "proto3"; package com.deft.grpc; message ProfileDescriptor { int64 profile_id = 1; string name = 2; }
  • int64 , Java için Uzundur . Profil kimliğinin ait olmasına izin verin.
  • String – tıpkı Java'da olduğu gibi, bu bir string değişkenidir.

Projeyi oluşturmak için Gradle veya maven kullanabilirsiniz. Maven kullanmak benim için daha uygun. Ve ayrıca maven kullanan kod olacak. Bu söylenecek kadar önemli çünkü Gradle için .proto'nun gelecek nesli biraz farklı olacak ve derleme dosyasının farklı şekilde yapılandırılması gerekecek. Basit bir gRPC sunucusu yazmak için yalnızca bir bağımlılığa ihtiyacımız var:

 <dependency> <groupId>io.github.lognet</groupId> <artifactId>grpc-spring-boot-starter</artifactId> <version>4.5.4</version> </dependency>

Bu inanılmaz. Bu marş bizim için muazzam miktarda iş yapıyor.

Oluşturacağımız proje şuna benzeyecek:

Spring Boot uygulamasını başlatmak için GrpcServerApplication'a ihtiyacımız var. Ve .proto hizmetinden yöntemleri uygulayacak olan .proto . Protokolü kullanmak ve yazılı .proto dosyalarından sınıflar oluşturmak için, pom.xml dosyasına protobuf-maven-plugin ekleyin. Yapı bölümü şöyle görünecek:

 <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 – .proto dosyalarının bulunduğu dizini belirtir.
  • outputDirectory – dosyaların oluşturulacağı dizini seçin.
  • clearOutputDirectory – oluşturulan dosyaların temizlenmediğini belirten bir bayrak.

Bu aşamada bir proje oluşturabilirsiniz. Ardından çıktı dizininde belirttiğimiz klasöre gitmeniz gerekiyor. Oluşturulan dosyalar orada olacak. Artık yavaş yavaş GrpcProfileService uygulayabilirsiniz.

Sınıf bildirimi şöyle görünecektir:

 @GRpcService public class GrpcProfileService extends ProfileServiceGrpc.ProfileServiceImplBase

GRpcService ek açıklaması – Sınıfı bir grpc-service bean olarak işaretler.

Hizmetimizi ProfileServiceGrpc , ProfileServiceImplBase öğesinden devraldığımız için üst sınıfın yöntemlerini geçersiz kılabiliriz. Geçersiz kılacağımız ilk yöntem 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(); }

İstemciye yanıt vermek için, geçirilen StreamObserver'da onNext yöntemini çağırmanız gerekir. Yanıtı gönderdikten sonra, istemciye sunucunun Tamamlandı üzerinde çalışmayı bitirdiğine dair bir sinyal gönderin. getCurrentProfile sunucusuna bir istek gönderirken yanıt şöyle olacaktır:

 { "profile_id": "1", "name": "test" }

Ardından, sunucu akışına bir göz atalım. Bu mesajlaşma yaklaşımıyla, istemci sunucuya bir istek gönderir, sunucu istemciye bir mesaj akışıyla yanıt verir. Örneğin, bir döngüde beş istek gönderir. Gönderme tamamlandığında, sunucu istemciye akışın başarıyla tamamlandığı hakkında bir mesaj gönderir.

Geçersiz kılınan sunucu akışı yöntemi şöyle görünecektir:

 @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(); }

Böylece, müşteri, yanıt numarasına eşit bir ProfileId ile beş mesaj alacaktır.

 { "profile_id": "0", "name": "" } { "profile_id": "1", "name": "" } … { "profile_id": "4", "name": "" }

İstemci akışı, sunucu akışına çok benzer. Ancak şimdi istemci bir mesaj akışı iletir ve sunucu bunları işler. Sunucu mesajları hemen işleyebilir veya istemciden gelen tüm istekleri bekleyip ardından işleyebilir.

 @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(); } }; }

İstemci akışında, StreamObserver'ı sunucunun mesajları alacağı istemciye döndürmeniz gerekir. Akışta bir hata meydana gelirse onError yöntemi çağrılır. Örneğin, yanlış sonlandırıldı.

Çift yönlü bir akış uygulamak için, sunucu ve istemciden bir akış oluşturmayı birleştirmek gerekir.

 @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(); } }; }

Bu örnekte, istemcinin mesajına yanıt olarak, sunucu artan pointCount ile bir profil döndürecektir .

Çözüm

Bir istemci ve bir sunucu arasında gRPC kullanarak mesajlaşma için temel seçenekleri ele aldık : uygulanan sunucu akışı, istemci akışı, çift yönlü akış.

Makale Sergey Golitsyn tarafından yazılmıştır.