gRPC, ein Open-Source-Framework für Remote Procedure Calls (RPC), ermöglicht eine effiziente und skalierbare Kommunikation zwischen Diensten. Ein entscheidender Aspekt von gRPC ist die Verwaltung von Fristen, Anforderungstimeouts und die Verbreitung von Kontext, einschließlich benutzerdefinierter Strukturen.
Das Verständnis dieser Mechanismen trägt dazu bei, sicherzustellen, dass die Dienste umgehend reagieren, keine Ressourcen für Vorgänge verschwendet werden, die einen angemessenen Zeitrahmen überschreiten, und benutzerdefinierte Metadaten effektiv übertragen werden.
Eine Frist in gRPC gibt die maximale Zeit an, bis zu der ein Vorgang abgeschlossen sein muss. Wenn der Vorgang nicht innerhalb dieses Zeitrahmens abgeschlossen ist, wird er automatisch beendet. Fristen sind wichtig, um sicherzustellen, dass Systemressourcen nicht auf unbestimmte Zeit aufgrund nicht reagierender oder langsamer Dienste gebunden sind.
Ein Anforderungstimeout ist die Zeitspanne, die ein Client bereit ist, auf eine Antwort vom Server zu warten. Wenn der Server innerhalb dieser Zeitspanne nicht antwortet, wird die Anforderung abgebrochen. Dieser Mechanismus verhindert, dass der Client auf unbestimmte Zeit hängen bleibt, während er auf eine Antwort wartet.
gRPC bietet flexible Optionen zum Setzen von Fristen und Anfordern von Timeouts sowohl auf Client- als auch auf Serverseite. So können Sie dies in Go tun:
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) }
Auf der Serverseite können Sie mit gRPC Fristen erzwingen und Szenarien verarbeiten, in denen die vom Client angegebene Frist überschritten wird:
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) } }
Um benutzerdefinierte Strukturen über Kontext in gRPC zu senden, müssen Sie die Daten serialisieren, bevor Sie sie an den Kontext anhängen, und sie dann auf der Empfängerseite deserialisieren. Dazu müssen Sie Ihre benutzerdefinierten Strukturen in ein Format konvertieren, das über das Netzwerk übertragen werden kann, z. B. JSON oder Protocol Buffers, und diese serialisierten Daten dann den Kontextmetadaten hinzufügen.
type CustomStruct struct { Field1 string Field2 int }
Schritt 2: Serialisieren der Struktur
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 }
Schritt 3: An Kontext anhängen
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 }
Schritt 4: Übertragen
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) }
Schritt 5: Extrahieren und Deserialisieren auf dem Server
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() } }
Um die Kontextausbreitung, einschließlich benutzerdefinierter Strukturen, über alle gRPC-Aufrufe hinweg konsistent zu handhaben, können Sie Interceptors verwenden. Interceptors sind Middleware, die Anfragen und Antworten verarbeitet und Funktionen wie Protokollierung, Überwachung und Verarbeitung von Kontextmetadaten hinzufügt.
Sie benötigen sowohl unäre als auch Streaming-Interceptors, um verschiedene Arten von RPC-Aufrufen zu verwalten:
Clientseitiger unärer Interceptor:
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...) }
Serverseitiger unärer Interceptor:
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) }
Clientseitiger Streaming-Interceptor:
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
Serverseitiger Streaming-Interceptor:
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) } }
Durch das Erstellen und Registrieren von unären und Streaming-Interceptors können Sie sicherstellen, dass die Kontextausbreitung, einschließlich benutzerdefinierter Strukturen, bei allen gRPC-Aufrufen konsistent gehandhabt wird. Dieser Ansatz stellt sicher, dass Ihre benutzerdefinierten Metadaten ordnungsgemäß verwaltet und weitergegeben werden, sodass Sie robuste und flexible gRPC-Dienste erstellen können.