paint-brush
gRPC-Secret : maîtriser les délais, les délais d'attente et les contextes personnaliséspar@gultm
888 lectures
888 lectures

gRPC-Secret : maîtriser les délais, les délais d'attente et les contextes personnalisés

par Tatyana9m2024/08/01
Read on Terminal Reader

Trop long; Pour lire

gRPC est un framework d'appel de procédure à distance (RPC) open source. Il permet une communication efficace et évolutive entre les services. Un aspect crucial est la gestion des délais, des délais d’attente des demandes et de la propagation du contexte. Comprendre ces mécanismes permet de garantir que les services répondent rapidement.
featured image - gRPC-Secret : maîtriser les délais, les délais d'attente et les contextes personnalisés
Tatyana HackerNoon profile picture

gRPC, un framework d'appel de procédure à distance (RPC) open source, permet une communication efficace et évolutive entre les services. Un aspect crucial de gRPC est la gestion des délais, des délais d'attente des demandes et la propagation du contexte, y compris les structures personnalisées.


Comprendre ces mécanismes permet de garantir que les services répondent rapidement, que les ressources ne sont pas gaspillées dans des opérations qui dépassent un délai raisonnable et que les métadonnées personnalisées sont transmises efficacement.

Comprendre les délais et les délais d'attente des demandes

Délais

Une date limite dans gRPC spécifie le délai maximum dans lequel une opération doit être terminée. Si l’opération n’est pas réalisée dans ce délai, elle sera automatiquement terminée. Les délais sont essentiels pour garantir que les ressources système ne sont pas bloquées indéfiniment en raison de services qui ne répondent pas ou qui sont lents.

Délais d'expiration des demandes

Un délai d'attente de requête est une période pendant laquelle un client est prêt à attendre une réponse du serveur. Si le serveur ne répond pas dans ce délai, la requête est abandonnée. Ce mécanisme empêche le client de rester indéfiniment en attente d'une réponse.

Définition des délais et des délais d'attente des demandes dans gRPC

gRPC fournit des options flexibles pour définir des délais et demander des délais d'attente tant du côté client que du côté serveur. Voici comment procéder dans Go :

Délais de définition côté client


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

Gestion côté serveur

Côté serveur, gRPC vous permet d'imposer des délais et de gérer les scénarios dans lesquels le délai spécifié par le client est dépassé :


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


Propagation de structures personnalisées en contexte

Pour envoyer des structures personnalisées via le contexte dans gRPC, vous devez sérialiser les données avant de les attacher au contexte, puis les désérialiser du côté récepteur. Cela implique de convertir vos structures personnalisées dans un format pouvant être transmis sur le réseau, tel que JSON ou Protocol Buffers, puis d'ajouter ces données sérialisées aux métadonnées de contexte.

Processus étape par étape

  1. Définissez votre structure personnalisée : définissez la structure personnalisée que vous souhaitez envoyer.
  2. Sérialiser la structure : convertissez la structure personnalisée en un tableau de chaînes ou d'octets.
  3. Attacher au contexte : ajoutez les données sérialisées aux métadonnées de contexte.
  4. Transmettre : Envoyez l'appel gRPC avec le contexte.
  5. Extraire et désérialiser sur le serveur : extrayez les métadonnées du contexte côté serveur et désérialisez-les dans la structure personnalisée.

Étape 1 : définissez votre structure personnalisée


 type CustomStruct struct { Field1 string Field2 int }


Étape 2 : sérialiser la structure


 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 }


Étape 3 : attacher au contexte


 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 }


Étape 4 : Transmettre


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


Étape 5 : Extraire et désérialiser sur le serveur


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


Implémentation d'un middleware pour tous les appels gRPC

Pour gérer la propagation du contexte, y compris les structures personnalisées, de manière cohérente dans tous les appels gRPC, vous pouvez utiliser des intercepteurs. Les intercepteurs sont des middlewares qui traitent les requêtes et les réponses, ajoutant des fonctionnalités telles que la journalisation, la surveillance et la gestion des métadonnées contextuelles.

Intercepteurs unaires et streaming

Vous avez besoin d'intercepteurs unaires et de streaming pour gérer différents types d'appels RPC :


  • Intercepteurs unaires : gèrent des cycles de requête-réponse uniques.


  • Intercepteurs de streaming : gérez les flux de requêtes et de réponses, y compris le streaming côté client, le streaming côté serveur et le streaming bidirectionnel.

Implémentation de l'intercepteur unaire

Intercepteur unaire côté client :


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


Intercepteur unaire côté serveur :


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

Implémentation de l'intercepteur de streaming

Intercepteur de streaming côté client :


 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


Intercepteur de streaming côté serveur :


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


En créant et en enregistrant des intercepteurs unaires et de streaming, vous pouvez garantir que la propagation du contexte, y compris les structures personnalisées, est gérée de manière cohérente dans tous les appels gRPC. Cette approche garantit que vos métadonnées personnalisées sont correctement gérées et propagées, vous permettant de créer des services gRPC robustes et flexibles.