เริ่มต้นใช้งาน 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 ดังที่อธิบายไว้ก่อนหน้านี้ วิธีนี้จะค้นหาชื่อหรือที่อยู่ของสถานที่จากชุดพิกัดที่กำหนด ดังนั้นให้ 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 อย่างง่ายกัน

RPC แบบ Unary

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 ไว้ในบอยเลอร์เพลตของเรา

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. ขั้นตอนถัดไป