1. מבוא
ב-codelab הזה תשתמשו ב-gRPC-Java כדי ליצור לקוח ושרת שיהוו את הבסיס לאפליקציה למיפוי מסלולים שנכתבה ב-Java.
בסוף המדריך יהיה לכם לקוח שמתחבר לשרת מרוחק באמצעות gRPC כדי לקבל את השם או את הכתובת למשלוח דואר של מה שנמצא בקואורדינטות ספציפיות במפה. אפליקציה מפותחת יכולה להשתמש בעיצוב הזה של לקוח-שרת כדי למנות או לסכם נקודות עניין לאורך מסלול.
ה-API של השרת מוגדר בקובץ Protocol Buffers, שישמש ליצירת קוד boilerplate עבור הלקוח והשרת, כדי שהם יוכלו לתקשר זה עם זה. כך תוכלו לחסוך זמן ומאמץ בהטמעת הפונקציונליות הזו.
הקוד שנוצר מטפל לא רק במורכבויות של התקשורת בין השרת ללקוח, אלא גם בסריאליזציה ובדה-סריאליזציה של הנתונים.
מה תלמדו
- איך משתמשים ב-Protocol Buffers כדי להגדיר API של שירות.
- איך ליצור לקוח ושרת מבוססי gRPC מהגדרה של Protocol Buffers באמצעות יצירת קוד אוטומטית.
- הבנה של תקשורת בין שרתים ללקוחות באמצעות gRPC.
ה-codelab הזה מיועד למפתחי Java שחדשים ב-gRPC או שרוצים לרענן את הידע שלהם ב-gRPC, או לכל מי שמעוניין ליצור מערכות מבוזרות. לא נדרש ניסיון קודם ב-gRPC.
2. לפני שמתחילים
דרישות מוקדמות
- JDK בגרסה 8 ואילך
קבל את הקוד
כדי שלא תצטרכו להתחיל מאפס, ב-codelab הזה מופיע סקפולד של קוד המקור של האפליקציה שתוכלו להשלים. בשלבים הבאים מוסבר איך לסיים את האפליקציה, כולל שימוש בתוספים של קומפיילר פרוטוקול החוצץ כדי ליצור את קוד ה-boilerplate של gRPC.
קודם יוצרים את ספריית העבודה של ה-codelab ועוברים אליה:
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
של קוד המקור שסופק.
פרוטוקול 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
מייצג זוג קואורדינטות של קו רוחב וקו אורך במפה. ב-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
שמגדיר שיטה אחת או יותר שסופקו על ידי השירות של האפליקציה.
מוסיפים את השיטה 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
. אנחנו עושים את זה באמצעות מהדר של פרוטוקול buffer protoc
עם פלאגין מיוחד של gRPC Java. כדי ליצור שירותי gRPC, צריך להשתמש בקומפיילר proto3 (שתומך בתחביר של proto2 וגם של proto3).
כשמשתמשים ב-Gradle או ב-Maven, תוסף ה-build protoc
יכול ליצור את הקוד הנדרש כחלק מה-build. ב-README של grpc-java מוסבר איך ליצור קוד מקובצי .proto
משלכם.
סיפקנו סביבת Gradle והגדרות בקוד המקור של ה-codelab כדי לבנות את הפרויקט הזה.
בתוך הספרייה grpc-java-getting-started
, מריצים את הפקודה הבאה:
$ chmod +x gradlew $ ./gradlew generateProto
הכיתות הבאות נוצרות מהגדרת השירות שלנו:
-
Feature.java
,Point.java
ואחרים שמכילים את כל קוד מאגר הפרוטוקולים לאכלוס, לסריאליזציה ולאחזור של סוגי הודעות הבקשה והתגובה שלנו. -
RouteGuideGrpc.java
שמכיל (יחד עם קוד שימושי אחר) מחלקת בסיס להטמעה בשרתיRouteGuide
, RouteGuideGrpc.RouteGuideImplBase
, עם כל השיטות שמוגדרות בשירותRouteGuide
ובמחלקות stub לשימוש הלקוחות.
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>
: אובייקט לצפייה בתגובה, שהוא ממשק מיוחד שהשרת יכול להשתמש בו כדי להתקשר עם התגובה שלו.
כדי להחזיר את התגובה שלנו ללקוח ולסיים את השיחה:
- אנחנו יוצרים ומאכלסים אובייקט תגובה של
Feature
כדי להחזיר אותו ללקוח, כפי שמצוין בהגדרת השירות שלנו. בדוגמה הזו, אנחנו עושים את זה בשיטה פרטית נפרדתcheckFeature()
. - אנחנו משתמשים בשיטה
onNext()
של response observer כדי להחזיר אתFeature
. - אנחנו משתמשים בשיטה
onCompleted()
של response observer כדי לציין שסיימנו לטפל ב-RPC.
הפעלת השרת
אחרי שמטמיעים את כל שיטות השירות, צריך להפעיל שרת gRPC כדי שהלקוחות יוכלו להשתמש בשירות. אנחנו כוללים ב-boilerplate שלנו את יצירת האובייקט ServerBuilder:
ServerBuilder.forPort(port), port, RouteGuideUtil.parseFeatures(featureFile)
אנחנו יוצרים את השירות ב-constructor:
- מציינים את היציאה שבה רוצים להשתמש כדי להאזין לבקשות של לקוחות באמצעות השיטה
forPort()
של הכלי לבנייה (הוא ישתמש בכתובת עם תו כללי). - יוצרים מופע של מחלקת הטמעת השירות
RouteGuideService
ומעבירים אותו לשיטהaddService()
של הכלי ליצירת מופעים. - מפעילים את הפונקציה
build()
ב-builder כדי ליצור שרת 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
.
ב-method העיקרי אנחנו:
- יוצרים מכונת
RouteGuideServer
. - כדי להפעיל שרת RPC בשירות שלנו, צריך להתקשר למספר
start()
. - כדי להפסיק את השירות, מתקשרים למספר
blockUntilShutdown()
.
public static void main(String[] args) throws Exception {
RouteGuideServer server = new RouteGuideServer(8980);
server.start();
server.blockUntilShutdown();
}
6. יצירת הלקוח
בקטע הזה נראה איך ליצור לקוח לשירות RouteGuide
.
יצירת מופע של stub
כדי לקרוא לשיטות של שירות, קודם צריך ליצור stub. יש שני סוגים של stubs, אבל אנחנו צריכים להשתמש רק ב-stub שחוסם מודעות ב-codelab הזה. יש שני סוגים:
- סטאב חוסם/סינכרוני שמבצע קריאת RPC וממתין לתגובת השרת, ויחזיר תגובה או יעלה חריגה.
- סטאב לא חוסם/אסינכרוני שמבצע קריאות לא חוסמות לשרת, שבהן התגובה מוחזרת באופן אסינכרוני. אפשר לבצע סוגים מסוימים של שיחות סטרימינג רק באמצעות ה-stub האסינכרוני.
קודם צריך ליצור ערוץ gRPC ואז להשתמש בערוץ כדי ליצור את ה-stub.
יכולנו להשתמש ב-ManagedChannelBuilder
ישירות כדי ליצור את הערוץ.
ManagedChannelBuilder.forAddress(host, port).usePlaintext().build
אבל נשתמש בשיטת עזר שמקבלת מחרוזת עם hostname:port
.
Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()).build();
עכשיו אפשר להשתמש בערוץ כדי ליצור את ה-stub לחסימה. ב-codelab הזה יש לנו רק RPCs חוסמים, לכן אנחנו משתמשים בשיטה newBlockingStub
שסופקה בכיתה RouteGuideGrpc
שיצרנו מ-.proto
.
blockingStub = RouteGuideGrpc.newBlockingStub(channel);
הפעלת שיטות של שירות
עכשיו נראה איך קוראים לשיטות של השירות.
Simple RPC
הפעלת ה-RPC הפשוט GetFeature
היא כמעט פשוטה כמו הפעלת method מקומי.
אנחנו יוצרים אובייקט של פרוטוקול buffer של בקשה (במקרה שלנו Point
) ומאכלסים אותו, מעבירים אותו לשיטת getFeature()
ב-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. כדאי לנסות!
- בתוך הספרייה
start_here
, מריצים את הפקודה הבאה:
$ ./gradlew installDist
הפעולה הזו תבצע קומפילציה של הקוד, תארוז אותו בקובץ JAR ותיצור את הסקריפטים שמריצים את הדוגמה. הם ייווצרו בספרייה build/install/start_here/bin/
. התסריטים הם: route-guide-server
ו-route-guide-client
.
השרת צריך לפעול לפני שמפעילים את הלקוח.
- מריצים את השרת:
$ ./build/install/start_here/bin/route-guide-server
- מריצים את הלקוח:
$ ./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. המאמרים הבאים
- מידע על gRPC זמין במאמרים מבוא ל-gRPC ומושגי ליבה.
- עוברים על המדריך למתחילים
- הפניית API