1. บทนำ
ในโค้ดแล็บนี้ คุณจะได้ใช้ gRPC-Go เพื่อสร้างไคลเอ็นต์และเซิร์ฟเวอร์ซึ่งเป็นรากฐานของแอปพลิเคชันการแมปเส้นทางที่เขียนด้วย Go
เมื่อจบบทแนะนำนี้ คุณจะมีไคลเอ็นต์ที่เชื่อมต่อกับเซิร์ฟเวอร์ระยะไกลโดยใช้ gRPC เพื่อรับชื่อหรือที่อยู่ไปรษณีย์ของสิ่งที่อยู่ ณ พิกัดที่เฉพาะเจาะจงบนแผนที่ แอปพลิเคชันที่สมบูรณ์อาจใช้การออกแบบไคลเอ็นต์-เซิร์ฟเวอร์นี้เพื่อแจงนับหรือสรุปจุดที่น่าสนใจตามเส้นทาง
บริการนี้กำหนดไว้ในไฟล์ Protocol Buffers ซึ่งจะใช้เพื่อสร้างโค้ดมาตรฐานสำหรับไคลเอ็นต์และเซิร์ฟเวอร์เพื่อให้สื่อสารกันได้ ซึ่งจะช่วยประหยัดเวลาและแรงในการติดตั้งใช้งานฟังก์ชันดังกล่าว
โค้ดที่สร้างขึ้นนี้ไม่เพียงแต่จัดการความซับซ้อนของการสื่อสารระหว่างเซิร์ฟเวอร์และไคลเอ็นต์เท่านั้น แต่ยังจัดการการซีเรียลไลซ์และการดีซีเรียลไลซ์ข้อมูลด้วย
สิ่งที่คุณจะได้เรียนรู้
- วิธีใช้ Protocol Buffers เพื่อกำหนด API ของบริการ
- วิธีสร้างไคลเอ็นต์และเซิร์ฟเวอร์ที่ใช้ gRPC จากคำจำกัดความของ Protocol Buffers โดยใช้การสร้างโค้ดอัตโนมัติ
- ความเข้าใจเกี่ยวกับการสื่อสารระหว่างไคลเอ็นต์กับเซิร์ฟเวอร์ด้วย gRPC
Codelab นี้มีไว้สำหรับนักพัฒนา Go ที่เพิ่งเริ่มใช้ gRPC หรือต้องการทบทวน gRPC หรือใครก็ตามที่สนใจสร้างระบบแบบกระจาย ไม่จำเป็นต้องมีประสบการณ์เกี่ยวกับ gRPC มาก่อน
2. ก่อนเริ่มต้น
ข้อกำหนดเบื้องต้น
ตรวจสอบว่าคุณได้ติดตั้งสิ่งต่อไปนี้แล้ว
- ชุดเครื่องมือ Go เวอร์ชัน 1.24.5 ขึ้นไป โปรดดูวิธีการติดตั้งที่เริ่มต้นใช้งานของ Go
- คอมไพเลอร์บัฟเฟอร์โปรโตคอล
protocเวอร์ชัน 3.27.1 ขึ้นไป ดูวิธีการติดตั้งได้ในคู่มือการติดตั้งของคอมไพเลอร์ - ปลั๊กอินคอมไพเลอร์ Protocol Buffer สำหรับ 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 เพื่อให้คอมไพเลอร์ Protocol Buffer ค้นหาปลั๊กอินได้
export PATH="$PATH:$(go env GOPATH)/bin"
รับโค้ด
Codelab นี้มีโครงสร้างของซอร์สโค้ดของแอปพลิเคชันเพื่อให้คุณทำต่อได้ คุณจึงไม่ต้องเริ่มต้นจากศูนย์ ขั้นตอนต่อไปนี้จะแสดงวิธีส่งแอปพลิเคชันให้เสร็จสมบูรณ์ ซึ่งรวมถึงการใช้ปลั๊กอินคอมไพเลอร์ Protocol Buffer เพื่อสร้างโค้ด gRPC ที่ซ้ำกัน
ดาวน์โหลดซอร์สโค้ดนี้เป็นไฟล์เก็บถาวร .ZIP จาก GitHub แล้วแตกไฟล์เนื้อหา
หรือหากต้องการข้ามการพิมพ์การติดตั้งใช้งาน คุณสามารถดูซอร์สโค้ดที่เสร็จสมบูรณ์แล้วได้ใน GitHub
3. กำหนดบริการ
ขั้นตอนแรกคือการกำหนดบริการ gRPC ของแอปพลิเคชัน เมธอด RPC รวมถึงประเภทข้อความคำขอและการตอบกลับโดยใช้ Protocol Buffers บริการของคุณจะให้ข้อมูลต่อไปนี้
- เมธอด RPC ที่ชื่อ
GetFeatureซึ่งเซิร์ฟเวอร์นำมาใช้และไคลเอ็นต์เรียกใช้ - ประเภทข้อความ
PointและFeatureซึ่งเป็นโครงสร้างข้อมูลที่แลกเปลี่ยนระหว่างไคลเอ็นต์กับเซิร์ฟเวอร์เมื่อใช้เมธอดGetFeatureไคลเอ็นต์จะระบุพิกัดแผนที่เป็นPointในคำขอGetFeatureไปยังเซิร์ฟเวอร์ และเซิร์ฟเวอร์จะตอบกลับด้วยFeatureที่เกี่ยวข้องซึ่งอธิบายสิ่งที่อยู่ในพิกัดเหล่านั้น
วิธีการ RPC นี้และประเภทข้อความของวิธีการนี้จะกำหนดไว้ในไฟล์ routeguide/route_guide.proto ของซอร์สโค้ดที่ระบุ
Protocol Buffers เรียกกันโดยทั่วไปว่า protobuf ดูข้อมูลเพิ่มเติมเกี่ยวกับคำศัพท์ 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 ซึ่งกำหนดวิธีการอย่างน้อย 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 โดยใช้คอมไพเลอร์ Protocol 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 และออบเจ็กต์ Protocol Buffer Point จากคำขอของไคลเอ็นต์นั้น ฟังก์ชันจะแสดงผลออบเจ็กต์บัฟเฟอร์โปรโตคอล Feature สำหรับสถานที่ที่ค้นหาและ error ตามความจำเป็น
ในเมธอด ให้ป้อนข้อมูลที่เหมาะสมสำหรับ Point ที่ระบุลงในออบเจ็กต์ Feature จากนั้น 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 ซึ่งเป็นที่ที่คุณจะใช้โค้ดฝั่งไคลเอ็นต์
หากต้องการเรียกใช้เมธอดของบริการระยะไกล เราต้องสร้างแชแนล 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 ไม่ต้องใช้ข้อมูลเข้าสู่ระบบใดๆ
เมื่อตั้งค่า Channel ของ gRPC แล้ว เราต้องมี Stub ของไคลเอ็นต์เพื่อเรียกใช้ RPC ผ่านการเรียกฟังก์ชัน Go เราได้รับสตับโดยใช้เมธอด 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 จะรอให้เซิร์ฟเวอร์ตอบกลับ และจะส่งคืนการตอบกลับหรือข้อผิดพลาด
RPC แบบง่าย
การเรียกใช้ RPC อย่างง่าย GetFeature นั้นตรงไปตรงมาเกือบเท่ากับการเรียกใช้เมธอดในเครื่อง ซึ่งในกรณีนี้คือ 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