1. מבוא
ב-codelab הזה תשתמשו ב-gRPC-Go כדי ליצור לקוח ושרת שמהווים את הבסיס לאפליקציה למיפוי מסלולים שנכתבה ב-Go.
בסוף המדריך יהיה לכם לקוח שמתחבר לשרת מרוחק באמצעות gRPC כדי לקבל את השם או את הכתובת למשלוח דואר של מה שנמצא בקואורדינטות ספציפיות במפה. אפליקציה מפותחת יכולה להשתמש בעיצוב הזה של לקוח-שרת כדי למנות או לסכם נקודות עניין לאורך מסלול.
השירות מוגדר בקובץ Protocol Buffers, שישמש ליצירת קוד boilerplate ללקוח ולשרת, כדי שהם יוכלו לתקשר זה עם זה. כך תוכלו לחסוך זמן ומאמץ בהטמעת הפונקציונליות הזו.
הקוד שנוצר מטפל לא רק במורכבויות של התקשורת בין השרת ללקוח, אלא גם בסריאליזציה ובדה-סריאליזציה של הנתונים.
מה תלמדו
- איך משתמשים ב-Protocol Buffers כדי להגדיר API של שירות.
- איך ליצור לקוח ושרת מבוססי gRPC מהגדרה של Protocol Buffers באמצעות יצירת קוד אוטומטית.
- הבנה של תקשורת בין שרתים ללקוחות באמצעות gRPC.
ה-codelab הזה מיועד למפתחי Go שחדשים ב-gRPC או שרוצים לרענן את הידע שלהם ב-gRPC, או לכל מי שמתעניין בבניית מערכות מבוזרות. לא נדרש ניסיון קודם ב-gRPC.
2. לפני שמתחילים
דרישות מוקדמות
ודאו שהתקנתם את הפריטים הבאים:
- ערכת הכלים של Go בגרסה 1.24.5 ואילך. הוראות התקנה מופיעות במאמר תחילת העבודה עם Go.
- הקומפיילר של מאגר אחסון לפרוטוקולים,
protoc
, גרסה 3.27.1 ואילך. הוראות ההתקנה מופיעות במדריך ההתקנה של המהדר. - תוספים של קומפיילר פרוטוקול באפר ל-Go ול-gRPC. כדי להתקין את הפלאגינים האלה, מריצים את הפקודות הבאות:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
מעדכנים את המשתנה PATH
כדי שהקומפיילר של מאגר הפרוטוקולים יוכל למצוא את הפלאגינים:
export PATH="$PATH:$(go env GOPATH)/bin"
קבל את הקוד
כדי שלא תצטרכו להתחיל מאפס, ב-codelab הזה מופיע סקפולד של קוד המקור של האפליקציה שתוכלו להשלים. בשלבים הבאים מוסבר איך לסיים את האפליקציה, כולל שימוש בתוספים של קומפיילר פרוטוקול החוצץ כדי ליצור את קוד ה-boilerplate של gRPC.
מורידים את קוד המקור הזה כארכיון ZIP מ-GitHub ומחלצים את התוכן שלו.
לחלופין, אפשר להוריד את קוד המקור המלא מ-GitHub אם לא רוצים להקליד את ההטמעה.
3. הגדרת השירות
השלב הראשון הוא להגדיר את שירות gRPC של האפליקציה, את שיטת ה-RPC ואת סוגי ההודעות של הבקשה והתגובה באמצעות Protocol Buffers. השירות שלכם יספק:
- שיטת RPC שנקראת
GetFeature
, שהשרת מטמיע והלקוח מפעיל. - סוגי ההודעות
Point
ו-Feature
שהן מבני נתונים שמועברים בין הלקוח לשרת כשמשתמשים בשיטהGetFeature
. הלקוח מספק קואורדינטות של מפה כ-Point
בבקשתGetFeature
שלו לשרת, והשרת משיב עםFeature
תואם שמתאר את מה שנמצא בקואורדינטות האלה.
שיטת ה-RPC הזו וסוגי ההודעות שלה יוגדרו בקובץ routeguide/route_guide.proto
של קוד המקור שסופק.
פרוטוקול Buffers ידוע בדרך כלל בשם protobufs. מידע נוסף על המינוח של gRPC זמין במאמר מושגי ליבה, ארכיטקטורה ומחזור חיים של gRPC.
סוגי הודעות
בקובץ 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. בספרייה routeguide
, מריצים את הפקודה:
protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ route_guide.proto
הפקודה הזו יוצרת את הקבצים הבאים:
-
route_guide.pb.go
, שמכיל פונקציות ליצירת סוגי ההודעות של האפליקציה ולגישה לנתונים שלהן. -
route_guide_grpc.pb.go
, שמכיל פונקציות שהלקוח משתמש בהן כדי לקרוא לשיטת ה-gRPC המרוחקת של השירות, ופונקציות שהשרת משתמש בהן כדי לספק את השירות המרוחק הזה.
בשלב הבא, נטמיע את השיטה GetFeature
בצד השרת, כדי שכשהלקוח ישלח בקשה, השרת יוכל להשיב עם תשובה.
5. הטמעה של השירות
הפונקציה GetFeature
בצד השרת היא המקום שבו מתבצעת העבודה העיקרית: היא מקבלת הודעת Point
מהלקוח ומחזירה בהודעת Feature
את פרטי המיקום התואמים מתוך רשימה של מקומות מוכרים. הנה ההטמעה של הפונקציה ב-server/server.go
:
func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {
for _, feature := range s.savedFeatures {
if proto.Equal(feature.Location, point) {
return feature, nil
}
}
// No feature was found, return an unnamed feature
return &pb.Feature{Location: point}, nil
}
כשמפעילים את השיטה הזו בעקבות בקשה מלקוח מרוחק, הפונקציה מקבלת אובייקט Context
שמתאר את קריאת ה-RPC, ואובייקט Point
של Protocol Buffer מהבקשה של הלקוח. הפונקציה מחזירה אובייקט של מאגר פרוטוקולים Feature
למיקום שנבדק, וגם error
לפי הצורך.
בשיטה, מאכלסים אובייקט Feature
במידע המתאים ל-Point
הנתון, ואז return
אותו יחד עם שגיאת nil
כדי לציין ל-gRPC שסיימתם לטפל ב-RPC ושאפשר להחזיר את אובייקט Feature
ללקוח.
השיטה GetFeature
מחייבת יצירה ורישום של אובייקט routeGuideServer
, כדי שניתוב הבקשות של לקוחות לחיפושי מיקום יתבצע לפונקציה הזו. הפעולה הזו מתבצעת בmain()
:
func main() {
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
var opts []grpc.ServerOption
grpcServer := grpc.NewServer(opts...)
s := &routeGuideServer{}
s.loadFeatures()
pb.RegisterRouteGuideServer(grpcServer, s)
grpcServer.Serve(lis)
}
זה מה שקורה באזור main()
, שלב אחר שלב:
- מציינים את יציאת ה-TCP שבה יש להשתמש כדי להאזין לבקשות של לקוחות מרוחקים, באמצעות
lis, err := net.Listen(...)
. כברירת מחדל, האפליקציה משתמשת ביציאת TCP50051
כפי שמצוין במשתנהport
או בהעברת המתג--port
בשורת הפקודה כשמריצים את השרת. אם אי אפשר לפתוח את יציאת ה-TCP, האפליקציה מסתיימת עם שגיאה חמורה. - יוצרים מופע של שרת gRPC באמצעות
grpc.NewServer(...)
ונותנים למופע הזה את השםgrpcServer
. - יוצרים מצביע ל-
routeGuideServer
, מבנה שמייצג את שירות ה-API של האפליקציה, ונותנים למצביע את השםs.
- משתמשים ב-
s.loadFeatures()
כדי לאכלס את המערךs.savedFeatures
במיקומים שאפשר לחפש באמצעותGetFeature
. - רושמים את שירות ה-API בשרת gRPC כדי שקריאות RPC אל
GetFeature
ינותבו לפונקציה המתאימה. - מתקשרים אל
Serve()
בשרת עם פרטי ההעברה כדי לבצע המתנה חוסמת לבקשות לקוח. הפעולה הזו נמשכת עד שהתהליך מופסק או עד שמתקשרים אלStop()
.
הפונקציה loadFeatures()
מקבלת את המיפויים של קואורדינטות למיקום מ-server/testdata.go
.
6. יצירת הלקוח
עכשיו עורכים את client/client.go
, שזה המקום שבו מטמיעים את קוד הלקוח.
כדי להפעיל את ה-methods של השירות המרוחק, קודם צריך ליצור ערוץ gRPC כדי לתקשר עם השרת. אנחנו יוצרים את המחרוזת הזו על ידי העברת מחרוזת ה-URI של יעד השרת (שבמקרה הזה היא פשוט הכתובת ומספר היציאה) אל grpc.NewClient()
בפונקציה main()
של הלקוח, באופן הבא:
conn, err := grpc.NewClient("dns:///"+*serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
כתובת השרת, שמוגדרת על ידי המשתנה serverAddr
, היא localhost:50051
כברירת מחדל, ואפשר לבטל את ברירת המחדל באמצעות המתג --addr
בשורת הפקודה כשמריצים את הלקוח.
אם הלקוח צריך להתחבר לשירות שנדרשים בו פרטי אימות, כמו פרטי אימות של TLS או JWT, הלקוח יכול להעביר אובייקט DialOptions
כפרמטר ל-grpc.NewClient
שמכיל את פרטי האימות הנדרשים. לא נדרשים פרטי כניסה לשירות RouteGuide
.
אחרי שמגדירים את הערוץ של gRPC, צריך stub של לקוח כדי לבצע RPC באמצעות קריאות לפונקציות Go. אנחנו מקבלים את ה-stub באמצעות ה-method NewRouteGuideClient
שמופיעה בקובץ route_guide_grpc.pb.go
שנוצר מקובץ .proto
של האפליקציה.
import (pb "github.com/grpc-ecosystem/codelabs/getting_started_unary/routeguide")
client := pb.NewRouteGuideClient(conn)
הפעלת שיטות של שירות
ב-gRPC-Go, קריאות RPC פועלות במצב חסימה/סינכרוני, כלומר הקריאה לשירות מרוחק (RPC) מחכה לתגובה מהשרת, ותחזיר תגובה או שגיאה.
Simple RPC
הקריאה ל-RPC הפשוט GetFeature
היא כמעט פשוטה כמו הקריאה ל-method מקומית, במקרה הזה client.GetFeature
:
point := &pb.Point{Latitude: 409146138, Longitude: -746188906}
log.Printf("Getting feature for point (%d, %d)", point.Latitude, point.Longitude)
// Call GetFeature method on the client.
feature, err := client.GetFeature(context.TODO(), point)
if err != nil {
log.Fatalf("client.GetFeature failed: %v", err)
}
הלקוח קורא לשיטה ב-stub שנוצר קודם. בפרמטרים של השיטה, הלקוח יוצר ומאכלס אובייקט של פרוטוקול מאגר בקשות Point
. מעבירים גם אובייקט context.Context
שמאפשר לנו לשנות את ההתנהגות של ה-RPC אם צריך, למשל להגדיר מגבלת זמן לשיחה או לבטל RPC שנמצא בתהליך. אם הקריאה לא מחזירה שגיאה, הלקוח יכול לקרוא את פרטי התגובה מהשרת מתוך הערך המוחזר הראשון:
log.Println(feature)
בסך הכול, הפונקציה main()
של הלקוח צריכה להיראות כך:
func main() {
flag.Parse()
// Set up a connection to the gRPC server.
conn, err := grpc.NewClient("dns:///"+*serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
// Create a new RouteGuide stub.
client := pb.NewRouteGuideClient(conn)
point := &pb.Point{Latitude: 409146138, Longitude: -746188906}
log.Printf("Getting feature for point (%d, %d)", point.Latitude, point.Longitude)
// Call GetFeature method on the client.
feature, err := client.GetFeature(context.TODO(), point)
if err != nil {
log.Fatalf("client.GetFeature failed: %v", err)
}
log.Println(feature)
}
7. רוצה לנסות?
כדי לוודא שהשרת והלקוח פועלים יחד בצורה תקינה, מריצים את הפקודות הבאות בספריית העבודה של האפליקציה:
- מריצים את השרת במסוף אחד:
cd server go run .
- מריצים את הלקוח ממסוף אחר:
cd client go run .
יוצג פלט כמו זה, בלי חותמות זמן כדי שיהיה ברור:
Getting feature for point (409146138, -746188906)
name:"Berkshire Valley Management Area Trail, Jefferson, NJ, USA" location:<latitude:409146138 longitude:-746188906 >
Getting feature for point (0, 0)
location:<>
8. המאמרים הבאים
- מידע על gRPC זמין במאמרים מבוא ל-gRPC ומושגי ליבה.
- עוברים על המדריך למתחילים
- עיון בהפניית ה-API