بدء استخدام gRPC-Java

‫1. مقدمة

في هذا الدرس التطبيقي حول الترميز، ستستخدم gRPC-Java لإنشاء برنامج خادم وبرنامج عميل يشكّلان أساس تطبيق لربط المسارات مكتوب بلغة Java.

في نهاية هذا البرنامج التعليمي، سيكون لديك تطبيق عميل يتصل بخادم بعيد باستخدام gRPC للحصول على اسم أو عنوان بريدي للموقع الجغرافي الذي يقع عند إحداثيات معيّنة على الخريطة. قد يستخدم تطبيق متكامل تصميم العميل والخادم هذا لتعداد نقاط الاهتمام أو تلخيصها على طول مسار معيّن.

يتم تحديد واجهة برمجة التطبيقات الخاصة بالخادم في ملف Protocol Buffers، وسيتم استخدام هذا الملف لإنشاء رمز نموذجي لكل من العميل والخادم حتى يتمكّنا من التواصل مع بعضهما، ما يوفّر عليك الوقت والجهد في تنفيذ هذه الوظيفة.

لا يهتم هذا الرمز الذي تم إنشاؤه بتعقيدات الاتصال بين الخادم والعميل فحسب، بل أيضًا بتسلسل البيانات وإلغاء تسلسلها.

أهداف الدورة التعليمية

  • كيفية استخدام مخزن البروتوكولات المؤقت لتحديد واجهة برمجة تطبيقات الخدمة
  • كيفية إنشاء برنامج عميل وخادم يستندان إلى gRPC من تعريف Protocol Buffers باستخدام إنشاء الرموز البرمجية المبرمَج
  • فهم عملية التواصل بين العميل والخادم باستخدام gRPC

هذا الدرس التطبيقي حول الترميز مخصّص لمطوّري Java الجدد في gRPC أو الذين يريدون مراجعة gRPC، أو أي شخص آخر مهتم بإنشاء أنظمة موزّعة. لا يُشترط توفّر خبرة سابقة في gRPC.

2. قبل البدء

المتطلبات الأساسية

  • الإصدار 8 أو الإصدارات الأحدث من JDK

الحصول على الشفرة‏

ولكي لا تضطر إلى البدء من الصفر تمامًا، يوفّر لك هذا الدرس التطبيقي حول الترميز بنية أساسية لرمز المصدر الخاص بالتطبيق لتتمكّن من إكماله. ستوضّح لك الخطوات التالية كيفية إكمال التطبيق، بما في ذلك استخدام مكوّنات برنامج تجميع بروتوكول المخزن المؤقت لإنشاء رمز gRPC النموذجي.

أولاً، أنشئ دليل عمل الدرس التطبيقي وادخله:

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

نزِّل الدرس التطبيقي حول الترميز واستخرِجه:

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

بدلاً من ذلك، يمكنك تنزيل ملف ‎ .zip الذي يحتوي على دليل الدرس العملي فقط وفك ضغطه يدويًا.

يتوفّر رمز المصدر المكتمل على GitHub إذا أردت تخطّي كتابة عملية التنفيذ.

3- تحديد الخدمة

تتمثّل خطوتك الأولى في تحديد خدمة gRPC للتطبيق وطريقة RPC وأنواع رسائل الطلبات والاستجابات باستخدام بروتوكول المخازن المؤقتة. ستوفّر خدمتك ما يلي:

  • طريقة استدعاء إجراء عن بُعد (RPC) تُسمّى GetFeature ينفّذها الخادم ويستدعيها العميل.
  • نوعا الرسائل Point وFeature اللذان يمثّلان بنى البيانات المتبادلة بين العميل والخادم عند استخدام طريقة GetFeature يقدّم العميل إحداثيات الخريطة كـ Point في طلب GetFeature إلى الخادم، ويردّ الخادم بـ Feature مطابق يصف أي شيء يقع في تلك الإحداثيات.

سيتم تحديد طريقة RPC هذه وأنواع الرسائل الخاصة بها في ملف src/main/proto/routeguide/route_guide.proto الخاص برمز المصدر المقدَّم.

يُشار إلى Protocol Buffers عادةً باسم protobufs. لمزيد من المعلومات عن مصطلحات gRPC، يُرجى الاطّلاع على المفاهيم الأساسية والبنية ودورة الحياة في gRPC.

بما أنّنا ننشئ رمز Java في هذا المثال، حدّدنا خيار الملف java_package واسمًا لفئة Java في .proto:

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

أنواع الرسائل

في ملف routeguide/route_guide.proto الخاص بالرمز المصدر، حدِّد أولاً نوع الرسالة Point. يمثّل Point زوج إحداثيات خط العرض وخط الطول على الخريطة. في هذا الدرس العملي، استخدِم أعدادًا صحيحة للإحداثيات:

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

الرقمان 1 و2 هما رقما تعريف فريدان لكل حقل من الحقول في بنية message.

بعد ذلك، حدِّد Feature نوع الرسالة. يستخدم Feature الحقل string للاسم أو العنوان البريدي الخاص بمكان محدّد من خلال Point:

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

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

طريقة الخدمة

يحتوي ملف route_guide.proto على بنية service باسم RouteGuide تحدّد طريقة واحدة أو أكثر توفّرها خدمة التطبيق.

أضِف الطريقة rpc داخل تعريف RouteGuide.GetFeature كما هو موضّح سابقًا، ستبحث هذه الطريقة عن اسم أو عنوان موقع جغرافي من مجموعة إحداثيات معيّنة، لذا اطلب من GetFeature عرض Feature لـ Point معيّن:

service RouteGuide {
  // Definition of the service goes here

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

هذه طريقة أحادية لاستدعاء الإجراء عن بُعد: استدعاء إجراء بسيط عن بُعد يرسل فيه العميل طلبًا إلى الخادم وينتظر تلقّي ردّ، تمامًا مثل استدعاء دالة محلية.

‫4. إنشاء رمز العميل والخادم

بعد ذلك، نحتاج إلى إنشاء واجهات خادم وعميل gRPC من تعريف خدمة .proto. وننفّذ ذلك باستخدام برنامج تجميع بروتوكول buffer protoc مع مكوّن إضافي خاص بلغة Java في gRPC. عليك استخدام برنامج التجميع proto3 (الذي يتوافق مع كل من بنية proto2 وproto3) من أجل إنشاء خدمات gRPC.

عند استخدام Gradle أو Maven، يمكن لمكوّن protoc الإضافي للإنشاء إنشاء الرمز البرمجي اللازم كجزء من عملية الإنشاء. يمكنك الرجوع إلى ملف README الخاص بـ grpc-java لمعرفة كيفية إنشاء الرمز من ملفات .proto الخاصة بك.

لقد وفّرنا بيئة Gradle وإعداداتها في رمز المصدر الخاص بالتجربة العملية لإنشاء هذا المشروع.

داخل دليل grpc-java-getting-started، نفِّذ الأمر التالي:

$ chmod +x gradlew
$ ./gradlew generateProto

يتم إنشاء الفئات التالية من تعريف الخدمة:

  • Feature.java وPoint.java وغيرها التي تحتوي على جميع رموز المخزن المؤقت للبروتوكول لتعبئة أنواع رسائل الطلبات والردود وتسلسلها واستردادها
  • RouteGuideGrpc.java الذي يحتوي (بالإضافة إلى بعض الرموز المفيدة الأخرى) على فئة أساسية للخوادم RouteGuide لتنفيذها، RouteGuideGrpc.RouteGuideImplBase، مع جميع الطرق المحددة في خدمة RouteGuide وفئات التعليمات البرمجية البديلة للعملاء لاستخدامها.

5- تنفيذ الخادم

لنلقِ نظرة أولاً على كيفية إنشاء خادم RouteGuide. تتضمّن عملية إنجاز خدمة RouteGuide لمهامها جزأين:

  • تنفيذ واجهة الخدمة التي تم إنشاؤها من تعريف الخدمة، والتي تنفّذ "العمل" الفعلي لخدمتنا
  • تشغيل خادم gRPC للاستماع إلى الطلبات من العملاء وإرسالها إلى تنفيذ الخدمة المناسب

تنفيذ RouteGuide

كما ترى، يحتوي الخادم على فئة RouteGuideService توسّع الفئة المجردة RouteGuideGrpc.RouteGuideImplBase التي تم إنشاؤها:

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

لقد قدّمنا الملفَّين التاليَين لتهيئة الخادم بالميزات:

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

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

لنلقِ نظرة على عملية تنفيذ بسيطة لبروتوكول RPC بالتفصيل.

Unary RPC

تنفّذ RouteGuideService جميع طرق الخدمة لدينا. في هذه الحالة، يكون الرمز هو GetFeature() فقط، ويستقبل رسالة Point من العميل ويعرض في رسالة Feature معلومات الموقع الجغرافي المطابقة من قائمة الأماكن المعروفة.

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

تتطلّب الطريقة getFeature() مَعلمتَين:

  • Point: الطلب
  • StreamObserver<Feature>: أداة مراقبة الاستجابة، وهي واجهة خاصة يمكن للخادم استخدامها للاتصال بها عند تلقّي استجابته.

لعرض ردّنا على العميل وإكمال المكالمة، اتّبِع الخطوات التالية:

  1. ننشئ عنصر استجابة Feature ونملأه لإرجاعه إلى العميل، كما هو محدّد في تعريف الخدمة. في هذا المثال، يتم ذلك في طريقة checkFeature() خاصة منفصلة.
  2. نستخدم طريقة onNext() في مراقب الاستجابة لعرض Feature.
  3. نستخدم طريقة onCompleted() في مراقب الاستجابة لتحديد أنّنا انتهينا من التعامل مع إجراء RPC.

بدء الخادم

بعد تنفيذ جميع طرق الخدمة، علينا بدء تشغيل خادم gRPC حتى يتمكّن العملاء من استخدام خدمتنا. نضمِّن في الرمز النموذجي إنشاء الكائن ServerBuilder:

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

ننشئ الخدمة في الدالة الإنشائية:

  1. حدِّد المنفذ الذي نريد استخدامه للاستماع إلى طلبات العميل باستخدام طريقة forPort() الخاصة بأداة الإنشاء (سيتم استخدام عنوان أحرف البدل).
  2. أنشئ مثيلاً لفئة تنفيذ الخدمة RouteGuideService وأدخِله في طريقة addService() الخاصة بأداة الإنشاء.
  3. استدعِ الدالة build() في أداة الإنشاء لإنشاء خادم استدعاء إجراء عن بُعد (RPC) لخدمتنا.

يوضّح المقتطف التالي كيفية إنشاء عنصر ServerBuilder.

/** 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));
  }

يوضّح المقتطف التالي كيفية إنشاء عنصر خادم لخدمة RouteGuide.

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

نفِّذ طريقة بدء تستدعي start على الخادم الذي أنشأناه أعلاه.

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

نفِّذ طريقة لانتظار اكتمال الخادم حتى لا يتم الخروج منه على الفور.

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

كما ترى، ننشئ خادمنا ونبدأ تشغيله باستخدام ServerBuilder.

في الطريقة الرئيسية، ننفّذ ما يلي:

  1. أنشئ مثيلاً من RouteGuideServer.
  2. اتّصِل على start() لتفعيل خادم استدعاء إجراء عن بُعد (RPC) لخدمتنا.
  3. انتظِر إلى أن يتم إيقاف الخدمة من خلال الاتصال بالرقم blockUntilShutdown().
 public static void main(String[] args) throws Exception {
    RouteGuideServer server = new RouteGuideServer(8980);
    server.start();
    server.blockUntilShutdown();
  }

6. إنشاء العميل

في هذا القسم، سنتعرّف على كيفية إنشاء برنامج لخدمة RouteGuide.

إنشاء نسخة من عنصر زائف

لاستدعاء طرق الخدمة، يجب أولاً إنشاء رمز بديل. هناك نوعان من الرموز الصورية، ولكننا نحتاج إلى استخدام الرمز الصوري الحاصِر فقط في هذا الدرس العملي. وهما:

  • رمز حظر/تزامن يطلب إجراء مكالمة RPC وينتظر رد الخادم، ثم يعرض إما استجابة أو يثير استثناءً.
  • دالة غير حظر/غير متزامنة تنفّذ طلبات غير حظر إلى الخادم، ويتم عرض الردّ بشكل غير متزامن. يمكنك إجراء أنواع معيّنة من مكالمات البث فقط باستخدام رمز stub غير متزامن.

علينا أولاً إنشاء قناة gRPC، ثم استخدام القناة لإنشاء عنصر نائب.

كان بإمكاننا استخدام ManagedChannelBuilder مباشرةً لإنشاء القناة.

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

ولكن لنستخدِم طريقة مساعدة تأخذ سلسلة تحتوي على hostname:port.

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

يمكننا الآن استخدام القناة لإنشاء رمز حظر مؤقت. في هذا الدرس البرمجي، لا تتوفّر لدينا سوى طلبات RPC الحظر، لذا نستخدم طريقة newBlockingStub المتوفّرة في فئة RouteGuideGrpc التي أنشأناها من .proto.

blockingStub = RouteGuideGrpc.newBlockingStub(channel);

طُرق خدمة المكالمات

لنلقِ نظرة الآن على كيفية استدعاء طرق الخدمة.

Simple RPC

إنّ استدعاء إجراء RPC بسيط GetFeature لا يختلف كثيرًا عن استدعاء إجراء محلي.

ننشئ كائنًا من بروتوكول طلبات مؤقتة ونملأه (في حالتنا Point)، ثم نمرّره إلى الطريقة getFeature() في رمز الحظر المؤقت، ونحصل على Feature.

في حال حدوث خطأ، يتم ترميزه على شكل Status، ويمكننا الحصول عليه من StatusRuntimeException.

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;
}

تسجّل الرموز النموذجية رسالة تحتوي على المحتوى استنادًا إلى ما إذا كانت هناك ميزة في النقطة المحدّدة أم لا.

7. ننصحكم بتجربتها.

  1. داخل دليل start_here، نفِّذ الأمر التالي:
$ ./gradlew installDist

سيؤدي ذلك إلى تجميع الرمز البرمجي وتعبئته في ملف jar وإنشاء البرامج النصية التي تشغّل المثال. سيتم إنشاؤها في دليل build/install/start_here/bin/. البرنامجان النصيان هما: route-guide-server وroute-guide-client.

يجب أن يكون الخادم قيد التشغيل قبل بدء تشغيل العميل.

  1. شغِّل الخادم:
$ ./build/install/start_here/bin/route-guide-server
  1. شغِّل العميل:
$ ./build/install/start_here/bin/route-guide-client

ستظهر لك نتيجة مشابهة لما يلي، مع حذف الطوابع الزمنية لتوضيح الصورة:

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. الخطوات التالية