paint-brush
gRPC-Geheimnis: Deadlines, Timeouts und benutzerdefinierte Kontexte meisternvon@gultm
757 Lesungen
757 Lesungen

gRPC-Geheimnis: Deadlines, Timeouts und benutzerdefinierte Kontexte meistern

von Tatyana9m2024/08/01
Read on Terminal Reader

Zu lang; Lesen

gRPC ist ein Open-Source-Framework für Remote Procedure Calls (RPC). Es ermöglicht eine effiziente und skalierbare Kommunikation zwischen Diensten. Ein entscheidender Aspekt ist die Verwaltung von Fristen, Anforderungstimeouts und die Kontextweitergabe. Das Verständnis dieser Mechanismen trägt dazu bei, sicherzustellen, dass Dienste umgehend reagieren.
featured image - gRPC-Geheimnis: Deadlines, Timeouts und benutzerdefinierte Kontexte meistern
Tatyana HackerNoon profile picture

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.

Grundlegendes zu Fristen und Anforderungstimeouts

Fristen

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.

Anforderungs-Timeouts

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.

Festlegen von Fristen und Anforderungstimeouts in gRPC

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:

Clientseitiges Festlegen von Fristen


 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) }

Serverseitige Handhabung

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) } }


Benutzerdefinierte Strukturen im Kontext propagieren

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.

Schritt-für-Schritt-Prozess

  1. Definieren Sie Ihre benutzerdefinierte Struktur : Definieren Sie die benutzerdefinierte Struktur, die Sie senden möchten.
  2. Serialisieren Sie die Struktur : Konvertieren Sie die benutzerdefinierte Struktur in ein Zeichenfolgen- oder Byte-Array.
  3. An Kontext anhängen : Fügen Sie die serialisierten Daten den Kontextmetadaten hinzu.
  4. Senden : Senden Sie den gRPC-Aufruf mit dem Kontext.
  5. Extrahieren und Deserialisieren auf dem Server : Extrahieren Sie die Metadaten aus dem Kontext auf der Serverseite und deserialisieren Sie sie zurück in die benutzerdefinierte Struktur.

Schritt 1: Definieren Sie Ihre benutzerdefinierte Struktur


 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() } }


Implementieren von Middleware für alle gRPC-Aufrufe

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.

Unäre und Streaming-Interceptors

Sie benötigen sowohl unäre als auch Streaming-Interceptors, um verschiedene Arten von RPC-Aufrufen zu verwalten:


  • Unäre Interceptors : Behandeln einzelne Anforderungs-Antwort-Zyklen.


  • Streaming-Interceptors : Verarbeiten Sie Anforderungs- und Antwortströme, einschließlich clientseitigem Streaming, serverseitigem Streaming und bidirektionalem Streaming.

Unäre Interceptor-Implementierung

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) }

Implementierung eines Streaming-Interceptors

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.