เริ่มต้นใช้งาน gRPC-Java

1. บทนำ

ในโค้ดแล็บนี้ คุณจะได้ใช้ gRPC-Java เพื่อสร้างไคลเอ็นต์และเซิร์ฟเวอร์ซึ่งเป็นรากฐานของแอปพลิเคชันการแมปเส้นทางที่เขียนด้วย Java

เมื่อจบบทแนะนำนี้ คุณจะมีไคลเอ็นต์ที่เชื่อมต่อกับเซิร์ฟเวอร์ระยะไกลโดยใช้ gRPC เพื่อรับชื่อหรือที่อยู่ไปรษณีย์ของสิ่งที่อยู่ ณ พิกัดที่เฉพาะเจาะจงบนแผนที่ แอปพลิเคชันที่สมบูรณ์อาจใช้การออกแบบไคลเอ็นต์-เซิร์ฟเวอร์นี้เพื่อแจงนับหรือสรุปจุดที่น่าสนใจตามเส้นทาง

API ของเซิร์ฟเวอร์จะกำหนดไว้ในไฟล์ Protocol Buffers ซึ่งจะใช้เพื่อสร้างโค้ดบอยเลอร์เพลตสำหรับไคลเอ็นต์และเซิร์ฟเวอร์เพื่อให้สื่อสารกันได้ ซึ่งจะช่วยประหยัดเวลาและแรงในการติดตั้งใช้งานฟังก์ชันดังกล่าว

โค้ดที่สร้างขึ้นนี้ไม่เพียงแต่จัดการความซับซ้อนของการสื่อสารระหว่างเซิร์ฟเวอร์กับไคลเอ็นต์เท่านั้น แต่ยังจัดการการซีเรียลไลซ์และดีซีเรียลไลซ์ข้อมูลด้วย

สิ่งที่คุณจะได้เรียนรู้

  • วิธีใช้ Protocol Buffers เพื่อกำหนด API ของบริการ
  • วิธีสร้างไคลเอ็นต์และเซิร์ฟเวอร์ที่ใช้ gRPC จากคำจำกัดความ Protocol Buffers โดยใช้การสร้างโค้ดอัตโนมัติ
  • ความเข้าใจในการสื่อสารแบบไคลเอ็นต์-เซิร์ฟเวอร์ด้วย gRPC

Codelab นี้มีไว้สำหรับนักพัฒนา Java ที่เพิ่งเริ่มใช้ gRPC หรือต้องการทบทวน gRPC หรือผู้ที่สนใจสร้างระบบแบบกระจาย ไม่จำเป็นต้องมีประสบการณ์เกี่ยวกับ gRPC มาก่อน

2. ก่อนเริ่มต้น

ข้อกำหนดเบื้องต้น

  • JDK เวอร์ชัน 8 ขึ้นไป

รับโค้ด

Codelab นี้มีโครงสร้างของซอร์สโค้ดของแอปพลิเคชันเพื่อให้คุณทําให้เสร็จสมบูรณ์ เพื่อที่คุณจะได้ไม่ต้องเริ่มต้นจากศูนย์ ขั้นตอนต่อไปนี้จะแสดงวิธีสมัครให้เสร็จสมบูรณ์ รวมถึงการใช้ปลั๊กอินคอมไพเลอร์ Protocol Buffer เพื่อสร้างโค้ด gRPC ที่ซ้ำกัน

ก่อนอื่น ให้สร้างไดเรกทอรีการทำงานของ Codelab แล้วใช้คำสั่ง cd เพื่อเข้าไปในไดเรกทอรีดังกล่าว

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

ดาวน์โหลดและแตกไฟล์ Codelab

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 ของซอร์สโค้ดที่ระบุ

Protocol Buffers เรียกกันโดยทั่วไปว่า Protobuf ดูข้อมูลเพิ่มเติมเกี่ยวกับคำศัพท์ 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 แสดงคู่พิกัดละติจูด-ลองจิจูดบนแผนที่ สำหรับ Codelab นี้ ให้ใช้จำนวนเต็มสำหรับพิกัด

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 ที่กำหนดเมธอดอย่างน้อย 1 รายการที่บริการของแอปพลิเคชันมีให้

เพิ่มrpc method GetFeature ภายในRouteGuide definition ดังที่อธิบายไว้ก่อนหน้านี้ วิธีนี้จะค้นหาชื่อหรือที่อยู่ของสถานที่จากชุดพิกัดที่กำหนด ดังนั้นให้ 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 Java พิเศษ คุณต้องใช้คอมไพเลอร์ proto3 (ซึ่งรองรับทั้งไวยากรณ์ proto2 และ proto3) เพื่อสร้างบริการ gRPC

เมื่อใช้ Gradle หรือ Maven protocปลั๊กอินการสร้างจะสร้างโค้ดที่จำเป็นเป็นส่วนหนึ่งของการสร้างได้ คุณดูวิธีสร้างโค้ดจากไฟล์ .proto ของคุณเองได้ใน README ของ grpc-java

เราได้จัดเตรียมสภาพแวดล้อมและการกำหนดค่า Gradle ไว้ในซอร์สโค้ดของโค้ดแล็บเพื่อสร้างโปรเจ็กต์นี้

เรียกใช้คำสั่งต่อไปนี้ในไดเรกทอรี grpc-java-getting-started

$ chmod +x gradlew
$ ./gradlew generateProto

ระบบจะสร้างคลาสต่อไปนี้จากคำจำกัดความของบริการ

  • Feature.java, Point.java และอื่นๆ ที่มีโค้ด Protocol Buffer ทั้งหมดเพื่อสร้างข้อมูล จัดรูปแบบ และดึงข้อมูลประเภทข้อความคำขอและการตอบกลับ
  • RouteGuideGrpc.java ซึ่งมี (พร้อมกับโค้ดอื่นๆ ที่มีประโยชน์) คลาสพื้นฐานสำหรับRouteGuideเซิร์ฟเวอร์ที่จะใช้ RouteGuideGrpc.RouteGuideImplBase โดยมีเมธอดทั้งหมดที่กำหนดไว้ในRouteGuideคลาสบริการและคลาส Stub สำหรับให้ไคลเอ็นต์ใช้

5. ติดตั้งใช้งานเซิร์ฟเวอร์

ก่อนอื่นมาดูวิธีสร้างRouteGuideเซิร์ฟเวอร์กัน การทำให้RouteGuideบริการของเราทำงานได้ตามที่ควรจะเป็นมี 2 ส่วน ดังนี้

  • การใช้ส่วนติดต่อบริการที่สร้างขึ้นจากคำจำกัดความของบริการ ซึ่งจะ "ทำงาน" จริงของบริการ
  • เรียกใช้เซิร์ฟเวอร์ 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() เมธอดใช้พารามิเตอร์ 2 รายการ ดังนี้

  • Point: คำขอ
  • StreamObserver<Feature>: ตัวสังเกตการตอบกลับ ซึ่งเป็นอินเทอร์เฟซพิเศษสำหรับเซิร์ฟเวอร์ในการเรียกด้วยการตอบกลับ

หากต้องการส่งคำตอบกลับไปยังไคลเอ็นต์และสิ้นสุดการเรียกใช้ ให้ทำดังนี้

  1. เราสร้างและป้อนข้อมูลFeatureออบเจ็กต์การตอบกลับเพื่อส่งคืนไปยังไคลเอ็นต์ตามที่ระบุไว้ในคำจำกัดความของบริการ ในตัวอย่างนี้ เราจะทำเช่นนี้ในเมธอด checkFeature() ส่วนตัวแยกต่างหาก
  2. เราใช้วิธี onNext() ของเครื่องสังเกตการตอบกลับเพื่อส่งคืน Feature
  3. เราใช้เมธอด onCompleted() ของเครื่องสังเกตการตอบกลับเพื่อระบุว่าเราจัดการกับ RPC เสร็จแล้ว

เริ่มเซิร์ฟเวอร์

เมื่อเราได้ใช้เมธอดบริการทั้งหมดแล้ว เราต้องเริ่มเซิร์ฟเวอร์ gRPC เพื่อให้ไคลเอ็นต์ใช้บริการของเราได้จริง เราจะรวมการสร้างออบเจ็กต์ ServerBuilder ไว้ใน Boilerplate

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

เราสร้างบริการในตัวสร้างดังนี้

  1. ระบุพอร์ตที่เราต้องการใช้เพื่อรอคำขอของไคลเอ็นต์โดยใช้เมธอด forPort() ของ Builder (จะใช้ที่อยู่ไวลด์การ์ด)
  2. สร้างอินสแตนซ์ของคลาสการติดตั้งใช้งานบริการ RouteGuideService และส่งไปยังเมธอด addService() ของ Builder
  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

สร้างอินสแตนซ์ของ Stub

หากต้องการเรียกใช้เมธอดบริการ เราต้องสร้างสตับก่อน มี Stub อยู่ 2 ประเภท แต่เราต้องใช้เฉพาะ Stub ที่บล็อกสำหรับ Codelab นี้ โดยมี 2 ประเภทดังนี้

  • สแต็บการบล็อก/ซิงโครนัสที่ทำการเรียก RPC และรอให้เซิร์ฟเวอร์ตอบกลับ จากนั้นจะส่งคืนการตอบกลับหรือยกเว้น
  • สตับที่ไม่บล็อก/แบบอะซิงโครนัสที่ทำการเรียกที่ไม่บล็อกไปยังเซิร์ฟเวอร์ ซึ่งจะส่งคืนการตอบกลับแบบอะซิงโครนัส คุณจะโทรแบบสตรีมมิงบางประเภทได้โดยใช้ Stub แบบไม่พร้อมกันเท่านั้น

ก่อนอื่นเราต้องสร้างแชแนล gRPC แล้วใช้แชแนลนั้นเพื่อสร้าง Stub

เราใช้ ManagedChannelBuilder เพื่อสร้างช่องได้โดยตรง

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

แต่เราจะใช้วิธีการยูทิลิตีที่รับสตริงที่มี hostname:port

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

ตอนนี้เราใช้ช่องเพื่อสร้าง Stub การบล็อกได้แล้ว สำหรับ Codelab นี้ เรามีเฉพาะ RPC ที่บล็อก ดังนั้นเราจึงใช้เมธอด newBlockingStub ที่ระบุไว้ในคลาส RouteGuideGrpc ที่เราสร้างจาก .proto

blockingStub = RouteGuideGrpc.newBlockingStub(channel);

วิธีการเรียกใช้บริการ

ตอนนี้มาดูวิธีเรียกใช้เมธอดบริการกัน

RPC แบบง่าย

การเรียกใช้ RPC อย่างง่าย GetFeature นั้นแทบจะตรงไปตรงมาเหมือนกับการเรียกใช้เมธอดในเครื่อง

เราสร้างและป้อนข้อมูลออบเจ็กต์บัฟเฟอร์โปรโตคอลคำขอ (ในกรณีของเราคือ Point) ส่งไปยังเมธอด getFeature() ใน Blocking Stub และรับ 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. ขั้นตอนถัดไป