gRPC-Java'yı kullanmaya başlama

1. Giriş

Bu codelab'de, Java ile yazılmış bir rota eşleme uygulamasının temelini oluşturan bir istemci ve sunucu oluşturmak için gRPC-Java'yı kullanacaksınız.

Eğitimin sonunda, haritada belirli koordinatlarda bulunan yerin adını veya posta adresini almak için gRPC kullanarak uzak bir sunucuya bağlanan bir istemciniz olacak. Tam teşekküllü bir uygulama, bir rota üzerindeki önemli yerleri listelemek veya özetlemek için bu istemci-sunucu tasarımını kullanabilir.

Sunucunun API'si, istemci ve sunucu için standart kod oluşturmak üzere kullanılacak bir Protocol Buffers dosyasında tanımlanır. Böylece istemci ve sunucu birbirleriyle iletişim kurabilir. Bu işlevselliği uygulamak için harcayacağınız zamandan ve emekten tasarruf edersiniz.

Oluşturulan bu kod, yalnızca sunucu ile istemci arasındaki iletişimin karmaşıklıklarını değil, aynı zamanda veri serileştirme ve seri durumdan çıkarma işlemlerini de ele alır.

Neler öğreneceksiniz?

  • Hizmet API'sini tanımlamak için Protocol Buffers'ı kullanma.
  • Otomatik kod oluşturma kullanarak bir Protokol Arabellek tanımından gRPC tabanlı istemci ve sunucu oluşturma.
  • gRPC ile istemci-sunucu iletişimi hakkında bilgi sahibi olmanız gerekir.

Bu codelab, gRPC'ye yeni başlayan veya gRPC ile ilgili bilgilerini tazelemek isteyen Java geliştiricilerin yanı sıra dağıtılmış sistemler oluşturmakla ilgilenen herkes için hazırlanmıştır. Daha önce gRPC deneyimi gerekmez.

2. Başlamadan önce

Ön koşullar

  • JDK 8 veya sonraki sürümler

Kodu alın

Bu codelab, tamamen sıfırdan başlamanız gerekmemesi için uygulamanın kaynak kodunun tamamlayabileceğiniz bir iskeletini sağlar. Aşağıdaki adımlarda, protokol arabellek derleyici eklentilerini kullanarak standart gRPC kodu oluşturma da dahil olmak üzere uygulamanın nasıl tamamlanacağı gösterilmektedir.

Öncelikle codelab çalışma dizinini oluşturun ve bu dizine gidin:

mkdir grpc-java-getting-started && cd grpc-java-getting-started

Codelab'i indirip ayıklayın:

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-java-getting-started/start_here

Alternatif olarak, yalnızca codelab dizinini içeren .zip dosyasını indirebilir ve manuel olarak sıkıştırmasını açabilirsiniz.

Bir uygulamayı yazmayı atlamak isterseniz tamamlanmış kaynak kodunu GitHub'da bulabilirsiniz.

3. Hizmeti tanımlama

İlk adımınız, Protocol Buffers'ı kullanarak uygulamanın gRPC hizmetini, RPC yöntemini, istek ve yanıt mesajı türlerini tanımlamaktır. Hizmetinizde sunulacaklar:

  • Sunucunun uyguladığı ve istemcinin çağırdığı GetFeature adlı bir RPC yöntemi.
  • GetFeature yöntemi kullanılırken istemci ile sunucu arasında değiştirilen veri yapıları olan Point ve Feature mesaj türleri. İstemci, sunucuya gönderdiği GetFeature isteğinde harita koordinatlarını Point olarak sağlar ve sunucu, bu koordinatlarda bulunan her şeyi açıklayan ilgili bir Feature ile yanıt verir.

Bu RPC yöntemi ve mesaj türleri, sağlanan kaynak kodun src/main/proto/routeguide/route_guide.proto dosyasında tanımlanır.

Protocol Buffers genellikle protobuf olarak bilinir. gRPC terminolojisi hakkında daha fazla bilgi için gRPC'nin Temel kavramlar, mimari ve yaşam döngüsü başlıklı makalesine bakın.

Bu örnekte Java kodu oluşturduğumuz için java_package dosya seçeneğini ve .proto içinde Java sınıfı için bir ad belirledik:

option java_package = "io.grpc.examples.routeguide";
option java_outer_classname = "RouteGuideProto";

Mesaj türleri

Kaynak kodun routeguide/route_guide.proto dosyasında önce Point mesaj türünü tanımlayın. Point, haritadaki bir enlem-boylam koordinat çiftini temsil eder. Bu codelab'de koordinatlar için tam sayıları kullanın:

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

1 ve 2 sayıları, message yapısındaki her alan için benzersiz kimlik numaralarıdır.

Ardından, Feature mesaj türünü tanımlayın. Bir Feature, Point ile belirtilen bir konumdaki bir şeyin adı veya posta adresi için string alanını kullanır:

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

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

Hizmet yöntemi

route_guide.proto dosyasında, uygulamanın hizmeti tarafından sağlanan bir veya daha fazla yöntemi tanımlayan RouteGuide adlı bir service yapısı bulunur.

rpc yöntemini RouteGuide tanımının içine ekleyin.GetFeature Daha önce açıklandığı gibi, bu yöntem belirli bir koordinat kümesinden bir konumun adını veya adresini arar. Bu nedenle, belirli bir Point için GetFeature işlevinin Feature döndürmesini sağlayın:

service RouteGuide {
  // Definition of the service goes here

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

Bu, tekli bir UPÇ yöntemidir: İstemcinin sunucuya istek gönderdiği ve yanıtın geri gelmesini beklediği basit bir UPÇ'dir (yerel bir işlev çağrısı gibi).

4. İstemci ve sunucu kodu oluşturma

Ardından, .proto hizmet tanımımızdan gRPC istemci ve sunucu arayüzlerini oluşturmamız gerekir. Bu işlemi, özel bir gRPC Java eklentisiyle birlikte protokol arabelleği derleyicisini protoc kullanarak yaparız. gRPC hizmetleri oluşturmak için proto3 derleyicisini (hem proto2 hem de proto3 söz dizimini destekler) kullanmanız gerekir.

Gradle veya Maven kullanılırken protoc derleme eklentisi, derlemenin bir parçası olarak gerekli kodu oluşturabilir. Kendi .proto dosyalarınızdan nasıl kod oluşturacağınız hakkında bilgi edinmek için grpc-java README dosyasına bakabilirsiniz.

Bu projeyi oluşturmak için codelab'in kaynak kodunda bir Gradle ortamı ve yapılandırması sağladık.

grpc-java-getting-started dizininde aşağıdaki komutu çalıştırın:

$ chmod +x gradlew
$ ./gradlew generateProto

Aşağıdaki sınıflar hizmet tanımımızdan oluşturulur:

  • Feature.java, Point.java ve diğerleri, istek ve yanıt mesajı türlerimizi doldurmak, serileştirmek ve almak için gereken tüm protokol arabelleği kodunu içerir.
  • RouteGuideGrpc.java, RouteGuide sunucularının uygulaması için bir temel sınıf (diğer bazı yararlı kodlarla birlikte), RouteGuideGrpc.RouteGuideImplBase, RouteGuide hizmetinde tanımlanan tüm yöntemlerle ve istemcilerin kullanması için sap sınıfları içerir.

5. Sunucuyu uygulama

Öncelikle RouteGuide sunucuyu nasıl oluşturduğumuza bakalım. RouteGuide hizmetimizin işini yapması için iki bölüm vardır:

  • Hizmet tanımımızdan oluşturulan ve hizmetimizin asıl "işini" yapan hizmet arayüzünü uygulama.
  • İstemcilerden gelen istekleri dinlemek ve bunları doğru hizmet uygulamasına göndermek için bir gRPC sunucusu çalıştırma.

RouteGuide'ı uygulama

Gördüğünüz gibi, sunucumuzda oluşturulan RouteGuideGrpc.RouteGuideImplBase soyut sınıfını genişleten bir RouteGuideService sınıfı var:

private static class RouteGuideService extends RouteGuideGrpc.RouteGuideImplBase {
...
}

Sunucunuzu özelliklerle başlatmak için aşağıdaki 2 dosyayı sağladık:

./src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java

./src/main/resources/io/grpc/examples/routeguide/route_guide_db.json

Basit bir RPC uygulamasını ayrıntılı olarak inceleyelim.

Unary RPC

RouteGuideService, tüm hizmet yöntemlerimizi uygular. Bu durumda, yalnızca GetFeature(), istemciden Point mesajı alır ve bilinen yerlerin listesinden ilgili konum bilgilerini Feature mesajıyla döndürür.

@Override
public void getFeature(Point request, StreamObserver<Feature> responseObserver) {
  responseObserver.onNext(checkFeature(request));
  responseObserver.onCompleted();
}

getFeature() yöntemi iki parametre alır:

  • Point: İstek.
  • StreamObserver<Feature>: Sunucunun yanıtıyla birlikte çağıracağı özel bir arayüz olan yanıt gözlemcisi.

Yanıtımızı istemciye döndürmek ve görüşmeyi tamamlamak için:

  1. Hizmet tanımımızda belirtildiği gibi, istemciye döndürülecek bir Feature yanıt nesnesi oluşturup bu nesneyi doldururuz. Bu örnekte, bunu ayrı bir özel checkFeature() yönteminde yapıyoruz.
  2. Feature değerini döndürmek için yanıt gözlemcisinin onNext() yöntemini kullanırız.
  3. RPC ile ilgilenmeyi bitirdiğimizi belirtmek için yanıt gözlemcisinin onCompleted() yöntemini kullanırız.

Sunucuyu başlatma

Tüm hizmet yöntemlerimizi uyguladıktan sonra, istemcilerin hizmetimizi gerçekten kullanabilmesi için bir gRPC sunucusu başlatmamız gerekir. Standart metnimize ServerBuilder nesnesinin oluşturulmasını dahil ediyoruz:

ServerBuilder.forPort(port), port, RouteGuideUtil.parseFeatures(featureFile)

Hizmeti oluşturucuda oluşturuyoruz:

  1. Oluşturucunun forPort() yöntemini kullanarak istemci isteklerini dinlemek için kullanmak istediğimiz bağlantı noktasını belirtin (joker karakter adresi kullanılır).
  2. Hizmet uygulama sınıfımızın RouteGuideService bir örneğini oluşturun ve bunu oluşturucunun addService() yöntemine iletin.
  3. Hizmetimiz için bir RPC sunucusu oluşturmak üzere oluşturucuda build() işlevini çağırın.

Aşağıdaki snippet'te ServerBuilder nesnesinin nasıl oluşturulduğu gösterilmektedir.

/** Create a RouteGuide server listening on {@code port} using {@code featureFile} database. */
public RouteGuideServer(int port, URL featureFile) throws IOException {
    this(Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()),
        port, RouteGuideUtil.parseFeatures(featureFile));
  }

Aşağıdaki snippet'te, RouteGuide hizmetimiz için nasıl sunucu nesnesi oluşturduğumuz gösterilmektedir.

/** Create a RouteGuide server using serverBuilder as a base and features as data. */
public RouteGuideServer(ServerBuilder<?> serverBuilder, int port, Collection<Feature> features) {
  this.port = port;
  server = serverBuilder.addService(new RouteGuideService(features))
      .build();
}

Yukarıda oluşturduğumuz sunucuda start işlevini çağıran bir başlangıç yöntemi uygulayın.

public void start() throws IOException {
  server.start();
  logger.info("Server started, listening on " + port);
}

Sunucunun tamamlamasını beklemek için bir yöntem uygulayın, böylece hemen çıkmaz.

/** Await termination on the main thread since the grpc library uses daemon threads. */
private void blockUntilShutdown() throws InterruptedException {
  if (server != null) {
    server.awaitTermination();
  }
}

Gördüğünüz gibi, sunucumuzu ServerBuilder kullanarak oluşturup başlatıyoruz.

Ana yöntemde:

  1. RouteGuideServer örneği oluşturun.
  2. Hizmetimiz için bir RPC sunucusu etkinleştirmek üzere start() numaralı telefonu arayın.
  3. blockUntilShutdown() numaralı telefonu arayarak hizmetin durdurulmasını bekleyin.
 public static void main(String[] args) throws Exception {
    RouteGuideServer server = new RouteGuideServer(8980);
    server.start();
    server.blockUntilShutdown();
  }

6. İstemciyi oluşturma

Bu bölümde, RouteGuide hizmetimiz için istemci oluşturma konusunu ele alacağız.

Sap oluşturma

Hizmet yöntemlerini çağırmak için önce bir taslak oluşturmamız gerekir. İki tür saplama vardır ancak bu codelab için yalnızca engelleme saplamasını kullanmamız gerekir. İki tür vardır:

  • Bir RPC çağrısı yapan ve sunucunun yanıt vermesini bekleyen, yanıt döndüren veya istisna oluşturan bir engelleyici/eşzamanlı saplama.
  • Yanıtın eşzamansız olarak döndürüldüğü, sunucuya engellemeyen çağrılar yapan engellemeyen/eşzamansız bir saplama. Yalnızca eşzamansız saplamayı kullanarak belirli türlerde yayın aramaları yapabilirsiniz.

Öncelikle bir gRPC kanalı oluşturmamız ve ardından bu kanalı kullanarak saplamamızı oluşturmamız gerekir.

Kanalı oluşturmak için doğrudan ManagedChannelBuilder kullanabilirdik.

ManagedChannelBuilder.forAddress(host, port).usePlaintext().build

Ancak hostname:port içeren bir dize alan bir yardımcı yöntem kullanalım.

Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()).build();

Artık engelleme saplamamızı oluşturmak için kanalı kullanabiliriz. Bu codelab'de yalnızca engelleme RPC'leri olduğundan .proto'ımızdan oluşturduğumuz RouteGuideGrpc sınıfında sağlanan newBlockingStub yöntemini kullanıyoruz.

blockingStub = RouteGuideGrpc.newBlockingStub(channel);

Hizmet yöntemlerini arama

Şimdi de hizmet yöntemlerimizi nasıl çağırdığımıza bakalım.

Simple RPC

Basit RPC GetFeature'yi çağırmak, yerel bir yöntemi çağırmak kadar kolaydır.

Bir istek protokol arabelleği nesnesi (bizim durumumuzda Point) oluşturup doldururuz, bunu engelleme saplamamızdaki getFeature() yöntemine iletiriz ve Feature yanıtını alırız.

Hata oluşursa Status olarak kodlanır. Bu kodu StatusRuntimeException üzerinden alabiliriz.

Point request = Point.newBuilder().setLatitude(lat).setLongitude(lon).build();

Feature feature;
try {
  feature = blockingStub.getFeature(request);
} catch (StatusRuntimeException e) {
  logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
  return;
}

Standart metin, belirtilen noktada bir özellik olup olmadığına bağlı olarak içeriği içeren bir mesajı kaydeder.

7. Yenilikleri inceleyin.

  1. start_here dizininde aşağıdaki komutu çalıştırın:
$ ./gradlew installDist

Bu işlem, kodunuzu derler, jar dosyası olarak paketler ve örneği çalıştıran komut dosyalarını oluşturur. Bu dosyalar build/install/start_here/bin/ dizininde oluşturulur. Komut dosyaları: route-guide-server ve route-guide-client.

İstemci başlatılmadan önce sunucunun çalışıyor olması gerekir.

  1. Sunucuyu çalıştırın:
$ ./build/install/start_here/bin/route-guide-server
  1. İstemciyi çalıştırın:
$ ./build/install/start_here/bin/route-guide-client

Aşağıdakine benzer bir çıkış görürsünüz. Zaman damgaları, netlik için çıkarılmıştır:

INFO: *** GetFeature: lat=409,146,138 lon=-746,188,906
INFO: Found feature called "Berkshire Valley Management Area Trail, Jefferson, NJ, USA" at 40.915, -74.619
INFO: *** GetFeature: lat=0 lon=0
INFO: Found no feature at 0, 0

8. Sırada ne var?