gRPC, khung gọi thủ tục từ xa (RPC) nguồn mở, cho phép giao tiếp hiệu quả và có thể mở rộng giữa các dịch vụ. Một khía cạnh quan trọng của gRPC là quản lý thời hạn, thời gian chờ yêu cầu và truyền bá ngữ cảnh, bao gồm cả các cấu trúc tùy chỉnh.
Việc hiểu các cơ chế này giúp đảm bảo rằng các dịch vụ phản hồi kịp thời, tài nguyên không bị lãng phí cho các hoạt động vượt quá khung thời gian hợp lý và siêu dữ liệu tùy chỉnh được truyền đi một cách hiệu quả.
Thời hạn trong gRPC chỉ định thời gian tối đa mà một thao tác phải được hoàn thành. Nếu hoạt động không được hoàn thành trong khung thời gian này, nó sẽ tự động bị chấm dứt. Thời hạn là điều cần thiết để đảm bảo rằng tài nguyên hệ thống không bị ràng buộc vô thời hạn do dịch vụ không phản hồi hoặc chậm.
Thời gian chờ yêu cầu là khoảng thời gian mà khách hàng sẵn sàng chờ phản hồi từ máy chủ. Nếu máy chủ không phản hồi trong khoảng thời gian này, yêu cầu sẽ bị hủy bỏ. Cơ chế này bảo vệ máy khách không bị treo vô thời hạn chờ phản hồi.
gRPC cung cấp các tùy chọn linh hoạt để đặt thời hạn và yêu cầu thời gian chờ ở cả phía máy khách và máy chủ. Đây là cách bạn có thể làm điều đó trong Go:
import ( "context" "log" "time" "google.golang.org/grpc" pb "path/to/your/protobuf/package" ) func main() { conn, err := grpc.Dial("server_address", grpc.WithInsecure()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() client := pb.NewYourServiceClient(conn) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() resp, err := client.YourMethod(ctx, &pb.YourRequest{}) if err != nil { log.Fatalf("could not call method: %v", err) } log.Printf("Response: %v", resp) }
Về phía máy chủ, gRPC cho phép bạn thực thi thời hạn và xử lý các tình huống vượt quá thời hạn do khách hàng chỉ định:
import ( "context" "log" "net" "time" "google.golang.org/grpc" pb "path/to/your/protobuf/package" ) type server struct { pb.UnimplementedYourServiceServer } func (s *server) YourMethod(ctx context.Context, req *pb.YourRequest) (*pb.YourResponse, error) { select { case <-time.After(10 * time.Second): return &pb.YourResponse{}, nil case <-ctx.Done(): return nil, ctx.Err() } } func main() { lis, err := net.Listen("tcp", ":50051") if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterYourServiceServer(s, &server{}) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }
Để gửi cấu trúc tùy chỉnh qua ngữ cảnh trong gRPC, bạn cần tuần tự hóa dữ liệu trước khi đính kèm nó vào ngữ cảnh và sau đó giải tuần tự hóa dữ liệu đó ở đầu nhận. Điều này liên quan đến việc chuyển đổi cấu trúc tùy chỉnh của bạn thành định dạng có thể được truyền qua mạng, chẳng hạn như JSON hoặc Bộ đệm giao thức, sau đó thêm dữ liệu được tuần tự hóa này vào siêu dữ liệu ngữ cảnh.
type CustomStruct struct { Field1 string Field2 int }
Bước 2: Tuần tự hóa cấu trúc
import ( "context" "encoding/json" "fmt" "google.golang.org/grpc/metadata" ) func serializeCustomStruct(customStruct CustomStruct) (string, error) { data, err := json.Marshal(customStruct) if err != nil { return "", err } return string(data), nil }
Bước 3: Đính kèm vào bối cảnh
func attachCustomStructToContext(ctx context.Context, customStruct CustomStruct) (context.Context, error) { serializedData, err := serializeCustomStruct(customStruct) if err != nil { return nil, err } md := metadata.Pairs("custom-struct", serializedData) ctx = metadata.NewOutgoingContext(ctx, md) return ctx, nil }
Bước 4: Truyền
func main() { conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() client := pb.NewYourServiceClient(conn) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() customStruct := CustomStruct{Field1: "value1", Field2: 42} ctx, err = attachCustomStructToContext(ctx, customStruct) if err != nil { log.Fatalf("could not attach custom struct to context: %v", err) } resp, err := client.YourMethod(ctx, &pb.YourRequest{}) if err != nil { log.Fatalf("could not call method: %v", err) } log.Printf("Response: %v", resp) }
Bước 5: Giải nén và giải tuần tự hóa trên máy chủ
func deserializeCustomStruct(data string) (CustomStruct, error) { var customStruct CustomStruct err := json.Unmarshal([]byte(data), &customStruct) if err != nil { return CustomStruct{}, err } return customStruct, nil } func extractCustomStructFromContext(ctx context.Context) (CustomStruct, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { return CustomStruct{}, fmt.Errorf("no metadata found in context") } serializedData := md["custom-struct"] if len(serializedData) == 0 { return CustomStruct{}, fmt.Errorf("no custom struct found in metadata") } return deserializeCustomStruct(serializedData[0]) } func (s *server) YourMethod(ctx context.Context, req *pb.YourRequest) (*pb.YourResponse, error) { customStruct, err := extractCustomStructFromContext(ctx) if err != nil { return nil, err } log.Printf("Received custom struct: %+v", customStruct) select { case <-time.After(10 * time.Second): return &pb.YourResponse{}, nil case <-ctx.Done(): return nil, ctx.Err() } }
Để xử lý việc truyền bá ngữ cảnh, bao gồm các cấu trúc tùy chỉnh, một cách nhất quán trên tất cả các lệnh gọi gRPC, bạn có thể sử dụng bộ chặn. Bộ chặn là phần mềm trung gian xử lý các yêu cầu và phản hồi, bổ sung các chức năng như ghi nhật ký, giám sát và xử lý siêu dữ liệu ngữ cảnh.
Bạn cần cả thiết bị chặn đơn tuyến và thiết bị chặn phát trực tuyến để quản lý các loại cuộc gọi RPC khác nhau:
Bộ chặn đơn nhất phía máy khách:
func unaryClientInterceptor( ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption, ) error { customStruct, ok := ctx.Value("customStruct").(CustomStruct) if ok { ctx, err := attachCustomStructToContext(ctx, customStruct) if err != nil { return err } } return invoker(ctx, method, req, reply, cc, opts...) }
Bộ chặn đơn nhất phía máy chủ:
func unaryServerInterceptor( ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, ) (interface{}, error) { customStruct, err := extractCustomStructFromContext(ctx) if err != nil { return nil, err } ctx = context.WithValue(ctx, "customStruct", customStruct) return handler(ctx, req) }
Bộ chặn phát trực tuyến phía máy khách:
func streamClientInterceptor( ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption, ) (grpc.ClientStream, error) { customStruct, ok := ctx.Value("customStruct").(CustomStruct) if ok { ctx, err := attachCustomStructToContext(ctx, customStruct) if err != nil { return nil, err } } return
Bộ chặn phát trực tuyến phía máy chủ:
import ( "context" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) // StreamServerInterceptor handles server-side streaming func streamServerInterceptor( srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler, ) error { ctx := ss.Context() customStruct, err := extractCustomStructFromContext(ctx) if err != nil { return err } // Add custom struct to context for server handling newCtx := context.WithValue(ctx, "customStruct", customStruct) wrapped := grpc_middleware.WrapServerStream(ss) wrapped.WrappedContext = newCtx // Handle the request return handler(srv, wrapped) } // Example of using the interceptor in a gRPC server setup func main() { lis, err := net.Listen("tcp", ":50051") if err != nil { log.Fatalf("failed to listen: %v", err) } // Register the interceptors server := grpc.NewServer( grpc.UnaryInterceptor(unaryServerInterceptor), grpc.StreamInterceptor(streamServerInterceptor), ) // Register your gRPC service implementations here pb.RegisterYourServiceServer(server, &yourServiceServer{}) if err := server.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }
Bằng cách tạo và đăng ký các bộ chặn đơn tuyến và phát trực tuyến, bạn có thể đảm bảo rằng việc truyền ngữ cảnh, bao gồm cả các cấu trúc tùy chỉnh, được xử lý nhất quán trên tất cả các lệnh gọi gRPC. Cách tiếp cận này đảm bảo rằng siêu dữ liệu tùy chỉnh của bạn được quản lý và phổ biến đúng cách, cho phép bạn xây dựng các dịch vụ gRPC mạnh mẽ và linh hoạt.