شروع کار با gRPC-Java

1. مقدمه

در این کد لبه، شما از gRPC-Java برای ایجاد یک سرویس گیرنده و سرور استفاده خواهید کرد که پایه و اساس برنامه نقشه برداری مسیر نوشته شده در جاوا را تشکیل می دهد.

در پایان آموزش، یک کلاینت خواهید داشت که با استفاده از gRPC به یک سرور راه دور متصل می شود تا نام یا آدرس پستی آنچه در مختصات خاصی روی نقشه قرار دارد را دریافت کند. یک برنامه کاربردی کاملاً پیشرفته ممکن است از این طراحی سرویس گیرنده-سرور برای برشمردن یا خلاصه کردن نقاط مورد علاقه در طول مسیر استفاده کند.

API سرور در یک فایل Protocol Buffers تعریف شده است که برای تولید کد boilerplate برای کلاینت و سرور استفاده می شود تا بتوانند با یکدیگر ارتباط برقرار کنند و در زمان و تلاش شما در اجرای آن عملکرد صرفه جویی می شود.

این کد تولید شده نه تنها از پیچیدگی های ارتباط بین سرور و کلاینت، بلکه سریال سازی و سریال سازی داده ها نیز مراقبت می کند.

چیزی که یاد خواهید گرفت

  • نحوه استفاده از بافرهای پروتکل برای تعریف API سرویس.
  • نحوه ساخت یک کلاینت و سرور مبتنی بر gRPC از تعریف بافرهای پروتکل با استفاده از تولید کد خودکار.
  • درک ارتباط مشتری-سرور با gRPC.

این کد لبه برای توسعه دهندگان جاوا که تازه به gRPC می پردازند یا به دنبال تازه سازی gRPC هستند یا هرکس دیگری که علاقه مند به ساختن سیستم های توزیع شده است، طراحی شده است. هیچ تجربه قبلی gRPC مورد نیاز نیست.

2. قبل از شروع

پیش نیازها

  • JDK نسخه 8 یا بالاتر

کد را دریافت کنید

برای اینکه مجبور نباشید به طور کامل از ابتدا شروع کنید، این کد لبه داربستی از کد منبع برنامه را برای شما فراهم می کند تا آن را تکمیل کنید. مراحل زیر به شما نشان می دهد که چگونه برنامه را تکمیل کنید، از جمله استفاده از افزونه های کامپایلر بافر پروتکل برای تولید کد gRPC دیگ بخار.

ابتدا پوشه کاری codelab و cd را در آن ایجاد کنید:

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 را که فقط شامل دایرکتوری Codelab است دانلود کرده و به صورت دستی آن را از حالت فشرده خارج کنید.

اگر می خواهید از تایپ کردن در یک پیاده سازی صرفنظر کنید، کد منبع تکمیل شده در GitHub موجود است.

3. سرویس را تعریف کنید

اولین قدم شما این است که سرویس gRPC برنامه، روش RPC آن، و انواع پیام درخواست و پاسخ آن را با استفاده از Protocol Buffers تعریف کنید. خدمات شما ارائه خواهد کرد:

  • یک روش RPC به نام GetFeature که سرور پیاده سازی می کند و کلاینت فراخوانی می کند.
  • انواع پیام Point و Feature که ساختارهای داده ای هستند که هنگام استفاده از روش GetFeature بین مشتری و سرور رد و بدل می شوند. مشتری مختصات نقشه را به عنوان یک Point در درخواست GetFeature خود به سرور ارائه می دهد و سرور با یک Feature مربوطه پاسخ می دهد که هر چیزی را که در آن مختصات قرار دارد را توصیف می کند.

این روش RPC و انواع پیام آن همه در فایل src/main/proto/routeguide/route_guide.proto کد منبع ارائه شده تعریف خواهند شد.

بافرهای پروتکل معمولاً به عنوان پروتوباف شناخته می شوند. برای اطلاعات بیشتر در مورد اصطلاحات gRPC، به مفاهیم اصلی، معماری و چرخه حیات gRPC مراجعه کنید.

از آنجایی که در این مثال در حال تولید کد جاوا هستیم، یک گزینه فایل java_package و یک نام برای کلاس جاوا در .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 GetFeature در تعریف RouteGuide اضافه کنید. همانطور که قبلاً توضیح داده شد، این روش نام یا آدرس یک مکان را از مجموعه مختصات معینی جستجو می کند، بنابراین از GetFeature بخواهید یک Feature برای یک Point داده شده برگرداند:

service RouteGuide {
  // Definition of the service goes here

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

این یک روش RPC یکنواخت است: یک RPC ساده که در آن کلاینت درخواستی را به سرور ارسال می کند و منتظر می ماند تا یک پاسخ بازگردد، درست مانند یک فراخوانی تابع محلی.

4. کد مشتری و سرور تولید کنید

در مرحله بعد باید رابط های سرویس گیرنده و سرور gRPC را از تعریف سرویس .proto خود تولید کنیم. ما این کار را با استفاده از پروتکل کامپایلر بافر protoc با یک پلاگین مخصوص gRPC جاوا انجام می دهیم. برای تولید سرویس های gRPC باید از کامپایلر proto3 (که از دستورات proto2 و proto3 پشتیبانی می کند) استفاده کنید.

هنگام استفاده از Gradle یا Maven، افزونه protoc بیلد می تواند کدهای لازم را به عنوان بخشی از بیلد ایجاد کند. برای نحوه تولید کد از فایل های .proto خود می توانید به grpc-java README مراجعه کنید.

برای ساخت این پروژه، محیط و پیکربندی 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 {
...
}

ما 2 فایل زیر را برای مقداردهی اولیه سرور شما با ویژگی ها ارائه کرده ایم:

./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 را در boilerplate خود قرار می دهیم:

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

ما سرویس را در سازنده می سازیم:

  1. پورتی را که می‌خواهیم برای گوش دادن به درخواست‌های مشتری با استفاده از متد forPort() سازنده استفاده کنیم را مشخص کنید (از آدرس wildcard استفاده می‌کند).
  2. نمونه ای از کلاس پیاده سازی سرویس ما RouteGuideService ایجاد کنید و آن را به متد addService() سازنده ارسال کنید.
  3. برای ایجاد یک سرور RPC برای سرویس ما، build() در سازنده فراخوانی کنید.

قطعه زیر نحوه ایجاد یک شی 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. برای فعال کردن سرور RPC برای سرویس ما، start() را فراخوانی کنید.
  3. با فراخوانی blockUntilShutdown() منتظر بمانید تا سرویس متوقف شود.
 public static void main(String[] args) throws Exception {
    RouteGuideServer server = new RouteGuideServer(8980);
    server.start();
    server.blockUntilShutdown();
  }

6. مشتری ایجاد کنید

در این بخش، ایجاد یک کلاینت برای سرویس RouteGuide خود را بررسی خواهیم کرد.

نمونه خرد

برای فراخوانی روش های سرویس، ابتدا باید یک خرد ایجاد کنیم. دو نوع خرد وجود دارد، اما ما فقط باید از یک مسدود کننده برای این کد لبه استفاده کنیم. این دو نوع عبارتند از:

  • یک خرد مسدود کننده/همگام که یک تماس RPC برقرار می کند و منتظر پاسخگویی سرور می ماند و یا پاسخی را برمی گرداند یا استثنایی ایجاد می کند.
  • یک خرد غیر مسدود/ناهمزمان که تماس‌های غیرانسدادی را با سرور برقرار می‌کند، جایی که پاسخ به صورت ناهمزمان برگردانده می‌شود. می‌توانید انواع خاصی از تماس‌های جریانی را فقط با استفاده از خرد ناهمزمان برقرار کنید.

ابتدا باید یک کانال gRPC ایجاد کنیم و سپس از کانال برای ایجاد خرد خود استفاده کنیم.

می‌توانستیم مستقیماً از ManagedChannelBuilder برای ایجاد کانال استفاده کنیم.

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

اما بیایید از یک روش کاربردی استفاده کنیم که یک رشته با hostname:port می گیرد.

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

اکنون می‌توانیم از کانال برای ایجاد خرد مسدودکننده استفاده کنیم. برای این کد لبه، ما فقط RPC های مسدود کننده داریم، بنابراین از روش newBlockingStub ارائه شده در کلاس RouteGuideGrpc که از .proto خود تولید کردیم استفاده می کنیم.

blockingStub = RouteGuideGrpc.newBlockingStub(channel);

روش های خدمات تماس

حال بیایید ببینیم که چگونه روش‌های خدمات خود را فراخوانی می‌کنیم.

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

این کد شما را کامپایل می کند، آن را در یک شیشه بسته بندی می کند و اسکریپت هایی را ایجاد می کند که نمونه را اجرا می کنند. آنها در دایرکتوری 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. بعدی چه خواهد شد