1. Giới thiệu
Trong lớp học lập trình này, bạn sẽ sử dụng gRPC-Go để tạo một ứng dụng khách và máy chủ tạo thành nền tảng của một ứng dụng lập bản đồ tuyến đường được viết bằng Go.
Khi kết thúc hướng dẫn này, bạn sẽ có một ứng dụng kết nối với một máy chủ từ xa bằng gRPC để lấy tên hoặc địa chỉ bưu chính của nội dung nằm tại một toạ độ cụ thể trên bản đồ. Một ứng dụng hoàn chỉnh có thể sử dụng thiết kế máy chủ-máy khách này để liệt kê hoặc tóm tắt các địa điểm yêu thích dọc theo một tuyến đường.
Dịch vụ này được xác định trong một tệp Protocol Buffers. Tệp này sẽ được dùng để tạo mã chuẩn cho ứng dụng và máy chủ để chúng có thể giao tiếp với nhau, giúp bạn tiết kiệm thời gian và công sức khi triển khai chức năng đó.
Mã được tạo này không chỉ xử lý sự phức tạp của việc giao tiếp giữa máy chủ và ứng dụng mà còn xử lý quá trình chuyển đổi dữ liệu thành chuỗi và chuyển đổi chuỗi thành dữ liệu.
Kiến thức bạn sẽ học được
- Cách sử dụng Protocol Buffers để xác định một API dịch vụ.
- Cách tạo máy khách và máy chủ dựa trên gRPC từ một định nghĩa Protocol Buffers bằng cách sử dụng tính năng tạo mã tự động.
- Hiểu rõ về giao tiếp máy khách-máy chủ bằng gRPC.
Lớp học lập trình này dành cho những nhà phát triển Go mới làm quen với gRPC hoặc muốn tìm hiểu lại về gRPC, hoặc bất kỳ ai khác quan tâm đến việc xây dựng hệ thống phân tán. Bạn không cần có kinh nghiệm sử dụng gRPC.
2. Trước khi bắt đầu
Điều kiện tiên quyết
Đảm bảo bạn đã cài đặt những thứ sau:
- Chuỗi công cụ Go phiên bản 1.24.5 trở lên. Để biết hướng dẫn cài đặt, hãy xem phần Bắt đầu của Go.
- Trình biên dịch vùng đệm giao thức,
protoc
, phiên bản 3.27.1 trở lên. Để biết hướng dẫn cài đặt, hãy xem hướng dẫn cài đặt của trình biên dịch. - Trình bổ trợ trình biên dịch vùng đệm giao thức cho Go và gRPC. Để cài đặt các trình bổ trợ này, hãy chạy các lệnh sau:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
Cập nhật biến PATH
để trình biên dịch vùng đệm giao thức có thể tìm thấy các trình bổ trợ:
export PATH="$PATH:$(go env GOPATH)/bin"
Lấy mã
Để bạn không phải bắt đầu hoàn toàn từ đầu, lớp học lập trình này cung cấp một khung mã nguồn ứng dụng để bạn hoàn tất. Các bước sau đây sẽ hướng dẫn bạn cách hoàn tất ứng dụng, bao gồm cả việc sử dụng trình bổ trợ trình biên dịch vùng đệm giao thức để tạo mã gRPC chung.
Tải mã nguồn này xuống dưới dạng tệp lưu trữ .ZIP từ GitHub rồi giải nén nội dung của tệp.
Ngoài ra, bạn có thể xem mã nguồn hoàn chỉnh trên GitHub nếu muốn bỏ qua bước nhập nội dung triển khai.
3. Xác định dịch vụ
Bước đầu tiên là xác định dịch vụ gRPC của ứng dụng, phương thức RPC và các loại thông báo yêu cầu và phản hồi bằng cách sử dụng Protocol Buffers. Dịch vụ của bạn sẽ cung cấp:
- Một phương thức RPC có tên là
GetFeature
mà máy chủ triển khai và máy khách gọi. - Các loại thông báo
Point
vàFeature
là các cấu trúc dữ liệu được trao đổi giữa ứng dụng và máy chủ khi sử dụng phương thứcGetFeature
. Ứng dụng cung cấp toạ độ trên bản đồ dưới dạngPoint
trong yêu cầuGetFeature
gửi đến máy chủ, còn máy chủ sẽ trả lời bằng mộtFeature
tương ứng mô tả mọi thứ nằm ở toạ độ đó.
Phương thức RPC này và các loại thông báo của phương thức này sẽ được xác định trong tệp routeguide/route_guide.proto
của mã nguồn được cung cấp.
Vùng đệm giao thức thường được gọi là protobuf. Để biết thêm thông tin về thuật ngữ gRPC, hãy xem phần Các khái niệm, cấu trúc và vòng đời cốt lõi của gRPC.
Loại thông báo
Trong tệp routeguide/route_guide.proto
của mã nguồn, trước tiên hãy xác định kiểu thông báo Point
. Point
biểu thị một cặp toạ độ vĩ độ và kinh độ trên bản đồ. Trong lớp học lập trình này, hãy sử dụng số nguyên cho toạ độ:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
Các số 1
và 2
là số nhận dạng duy nhất cho từng trường trong cấu trúc message
.
Tiếp theo, hãy xác định loại thông báo Feature
. Feature
sử dụng trường string
cho tên hoặc địa chỉ bưu chính của một thứ gì đó tại một vị trí do Point
chỉ định:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
Phương thức dịch vụ
Tệp route_guide.proto
có cấu trúc service
tên là RouteGuide
, xác định một hoặc nhiều phương thức do dịch vụ của ứng dụng cung cấp.
Thêm phương thức rpc
GetFeature
vào bên trong định nghĩa RouteGuide
. Như đã giải thích trước đó, phương thức này sẽ tra cứu tên hoặc địa chỉ của một vị trí từ một tập hợp toạ độ nhất định, vì vậy, hãy để GetFeature
trả về một Feature
cho một Point
nhất định:
service RouteGuide {
// Definition of the service goes here
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
}
Đây là một phương thức RPC đơn phương: một RPC đơn giản, trong đó ứng dụng gửi một yêu cầu đến máy chủ và đợi phản hồi quay lại, giống như một lệnh gọi hàm cục bộ.
4. Tạo mã máy khách và máy chủ
Tiếp theo, hãy tạo mã gRPC chung cho cả máy khách và máy chủ từ tệp .proto
bằng trình biên dịch vùng đệm giao thức. Trong thư mục routeguide
, hãy chạy:
protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ route_guide.proto
Lệnh này tạo ra các tệp sau:
route_guide.pb.go
, chứa các hàm để tạo kiểu thông báo của ứng dụng và truy cập vào dữ liệu của các kiểu thông báo đó.route_guide_grpc.pb.go
, chứa các hàm mà ứng dụng sử dụng để gọi phương thức gRPC từ xa của dịch vụ và các hàm mà máy chủ sử dụng để cung cấp dịch vụ từ xa đó.
Tiếp theo, chúng ta sẽ triển khai phương thức GetFeature
ở phía máy chủ để khi ứng dụng gửi yêu cầu, máy chủ có thể trả lời.
5. Triển khai dịch vụ
Hàm GetFeature
ở phía máy chủ là nơi thực hiện công việc chính: hàm này nhận một thông báo Point
từ máy khách và trả về thông tin vị trí tương ứng trong một thông báo Feature
từ danh sách các địa điểm đã biết. Dưới đây là cách triển khai hàm trong 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
}
Khi phương thức này được gọi sau một yêu cầu từ máy khách từ xa, hàm sẽ được truyền một đối tượng Context
mô tả lệnh gọi RPC và một đối tượng bộ đệm giao thức Point
từ yêu cầu của máy khách đó. Hàm này trả về một đối tượng Feature
của vùng đệm giao thức cho vị trí được tra cứu và một error
nếu cần.
Trong phương thức này, hãy điền thông tin thích hợp cho Point
đã cho vào đối tượng Feature
, rồi return
đối tượng đó cùng với lỗi nil
để cho gRPC biết rằng bạn đã hoàn tất việc xử lý RPC và đối tượng Feature
có thể được trả về cho máy khách.
Phương thức GetFeature
yêu cầu bạn tạo và đăng ký một đối tượng routeGuideServer
để các yêu cầu của ứng dụng khách về việc tra cứu vị trí có thể được định tuyến đến hàm đó. Thao tác này được thực hiện trong 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)
}
Sau đây là những gì diễn ra trong main()
, từng bước:
- Chỉ định cổng TCP cần dùng để theo dõi các yêu cầu của máy khách từ xa bằng cách sử dụng
lis, err := net.Listen(...)
. Theo mặc định, ứng dụng sẽ dùng cổng TCP50051
như được chỉ định bởi biếnport
hoặc bằng cách truyền công tắc--port
trên dòng lệnh khi chạy máy chủ. Nếu không mở được cổng TCP, ứng dụng sẽ kết thúc bằng một lỗi nghiêm trọng. - Tạo một phiên bản của máy chủ gRPC bằng cách sử dụng
grpc.NewServer(...)
, đặt tên cho phiên bản này làgrpcServer
. - Tạo một con trỏ đến
routeGuideServer
, một cấu trúc đại diện cho dịch vụ API của ứng dụng, đặt tên cho con trỏ làs.
- Sử dụng
s.loadFeatures()
để điền vào mảngs.savedFeatures
các vị trí có thể tra cứu thông quaGetFeature
. - Đăng ký dịch vụ API với máy chủ gRPC để các lệnh gọi RPC đến
GetFeature
được định tuyến đến hàm thích hợp. - Gọi
Serve()
trên máy chủ bằng thông tin chi tiết về cổng của chúng tôi để thực hiện một lệnh chờ chặn cho các yêu cầu của ứng dụng; lệnh này sẽ tiếp tục cho đến khi quy trình bị huỷ hoặcStop()
được gọi.
Hàm loadFeatures()
lấy các mối liên kết từ toạ độ đến vị trí từ server/testdata.go
.
6. Tạo ứng dụng
Bây giờ, hãy chỉnh sửa client/client.go
. Đây là nơi bạn sẽ triển khai mã ứng dụng.
Để gọi các phương thức của dịch vụ từ xa, trước tiên, chúng ta cần tạo một kênh gRPC để giao tiếp với máy chủ. Chúng ta tạo điều này bằng cách truyền chuỗi URI đích của máy chủ (trong trường hợp này, chỉ là địa chỉ và số cổng) đến grpc.NewClient()
trong hàm main()
của máy khách như sau:
conn, err := grpc.NewClient("dns:///"+*serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
Địa chỉ của máy chủ, được xác định bằng biến serverAddr
, theo mặc định là localhost:50051
và có thể bị ghi đè bằng công tắc --addr
trên dòng lệnh khi chạy ứng dụng.
Nếu cần kết nối với một dịch vụ yêu cầu thông tin xác thực, chẳng hạn như thông tin xác thực TLS hoặc JWT, ứng dụng có thể truyền đối tượng DialOptions
làm tham số đến grpc.NewClient
có chứa thông tin xác thực bắt buộc. Dịch vụ RouteGuide
không yêu cầu cung cấp thông tin đăng nhập.
Sau khi thiết lập kênh gRPC, chúng ta cần một stub ứng dụng để thực hiện các RPC thông qua lệnh gọi hàm Go. Chúng ta sẽ nhận được stub bằng phương thức NewRouteGuideClient
do tệp route_guide_grpc.pb.go
tạo từ tệp .proto
của ứng dụng cung cấp.
import (pb "github.com/grpc-ecosystem/codelabs/getting_started_unary/routeguide")
client := pb.NewRouteGuideClient(conn)
Gọi các phương thức dịch vụ
Trong gRPC-Go, các RPC hoạt động ở chế độ chặn/đồng bộ, tức là lệnh gọi RPC sẽ đợi máy chủ phản hồi và sẽ trả về phản hồi hoặc lỗi.
Simple RPC
Việc gọi RPC đơn giản GetFeature
gần như đơn giản như gọi một phương thức cục bộ, trong trường hợp này là 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)
}
Ứng dụng gọi phương thức trên phần giữ chỗ đã tạo trước đó. Đối với các tham số của phương thức, ứng dụng sẽ tạo và điền sẵn một đối tượng vùng đệm giao thức yêu cầu Point
. Bạn cũng truyền một đối tượng context.Context
cho phép chúng tôi thay đổi hành vi của RPC nếu cần, chẳng hạn như xác định giới hạn thời gian cho lệnh gọi hoặc huỷ RPC đang diễn ra. Nếu lệnh gọi không trả về lỗi, thì ứng dụng có thể đọc thông tin phản hồi từ máy chủ từ giá trị trả về đầu tiên:
log.Println(feature)
Nhìn chung, hàm main()
của ứng dụng sẽ có dạng như sau:
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. Dùng thử
Xác nhận rằng máy chủ và máy khách đang hoạt động với nhau một cách chính xác bằng cách thực thi các lệnh sau trong thư mục làm việc của ứng dụng:
- Chạy máy chủ trong một dòng lệnh:
cd server go run .
- Chạy ứng dụng từ một thiết bị đầu cuối khác:
cd client go run .
Bạn sẽ thấy kết quả như sau, trong đó dấu thời gian đã bị bỏ qua để đảm bảo sự rõ ràng:
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. Bước tiếp theo
- Tìm hiểu cách gRPC hoạt động trong phần Giới thiệu về gRPC và Các khái niệm cốt lõi
- Xem Hướng dẫn cơ bản
- Khám phá tài liệu tham khảo về API