paint-brush
gRPC-Secret: Dominar los plazos, los tiempos de espera y los contextos personalizadospor@gultm
757 lecturas
757 lecturas

gRPC-Secret: Dominar los plazos, los tiempos de espera y los contextos personalizados

por Tatyana9m2024/08/01
Read on Terminal Reader

Demasiado Largo; Para Leer

gRPC es un marco de llamada a procedimiento remoto (RPC) de código abierto. Permite una comunicación eficiente y escalable entre servicios. Un aspecto crucial es la gestión de plazos, tiempos de espera de solicitudes y la propagación del contexto. Comprender estos mecanismos ayuda a garantizar que los servicios respondan con prontitud.
featured image - gRPC-Secret: Dominar los plazos, los tiempos de espera y los contextos personalizados
Tatyana HackerNoon profile picture

gRPC, un marco de llamada a procedimiento remoto (RPC) de código abierto, permite una comunicación eficiente y escalable entre servicios. Un aspecto crucial de gRPC es la gestión de plazos, tiempos de espera de solicitudes y la propagación del contexto, incluidas las estructuras personalizadas.


Comprender estos mecanismos ayuda a garantizar que los servicios respondan con prontitud, que los recursos no se desperdicien en operaciones que excedan un período de tiempo razonable y que los metadatos personalizados se transmitan de manera efectiva.

Comprensión de los plazos y los tiempos de espera de las solicitudes

Plazos

Una fecha límite en gRPC especifica el tiempo máximo en el que se debe completar una operación. Si la operación no se completa dentro de este plazo, quedará automáticamente terminada. Los plazos son esenciales para garantizar que los recursos del sistema no queden inmovilizados indefinidamente debido a servicios lentos o que no responden.

Solicitar tiempos de espera

Un tiempo de espera de solicitud es un período en el que un cliente está dispuesto a esperar una respuesta del servidor. Si el servidor no responde dentro de este período, la solicitud se cancela. Este mecanismo protege al cliente de colgarse indefinidamente esperando una respuesta.

Establecer fechas límite y tiempos de espera de solicitud en gRPC

gRPC proporciona opciones flexibles para establecer plazos y solicitar tiempos de espera tanto en el lado del cliente como en el del servidor. Así es como puedes hacerlo en Go:

Plazos de configuración del lado del cliente


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

Manejo del lado del servidor

En el lado del servidor, gRPC le permite hacer cumplir los plazos y manejar escenarios en los que se excede el plazo especificado por el cliente:


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


Propagar estructuras personalizadas en contexto

Para enviar estructuras personalizadas a través del contexto en gRPC, debe serializar los datos antes de adjuntarlos al contexto y luego deserializarlos en el extremo receptor. Esto implica convertir sus estructuras personalizadas a un formato que pueda transmitirse a través de la red, como JSON o Protocol Buffers, y luego agregar estos datos serializados a los metadatos de contexto.

Proceso paso a paso

  1. Defina su estructura personalizada : defina la estructura personalizada que desea enviar.
  2. Serializar la estructura : convierta la estructura personalizada en una cadena o matriz de bytes.
  3. Adjuntar al contexto : agregue los datos serializados a los metadatos del contexto.
  4. Transmitir : envía la llamada gRPC con el contexto.
  5. Extraer y deserializar en el servidor : extraiga los metadatos del contexto en el lado del servidor y deserialícelos nuevamente en la estructura personalizada.

Paso 1: Defina su estructura personalizada


 type CustomStruct struct { Field1 string Field2 int }


Paso 2: serializar la estructura


 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 }


Paso 3: adjuntar al contexto


 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 }


Paso 4: transmitir


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


Paso 5: extraer y deserializar en el servidor


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


Implementación de middleware para todas las llamadas gRPC

Para manejar la propagación del contexto, incluidas las estructuras personalizadas, de manera consistente en todas las llamadas de gRPC, puede usar interceptores. Los interceptores son middleware que procesan solicitudes y respuestas, agregando funcionalidades como registro, monitoreo y manejo de metadatos de contexto.

Interceptores unarios y de streaming

Necesita interceptores unarios y de transmisión para administrar diferentes tipos de llamadas RPC:


  • Interceptores unarios : manejan ciclos únicos de solicitud-respuesta.


  • Interceptores de transmisión : manejan flujos de solicitudes y respuestas, incluida la transmisión del lado del cliente, la transmisión del lado del servidor y la transmisión bidireccional.

Implementación del interceptor unario

Interceptor unario del lado del cliente:


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


Interceptor unario del lado del servidor:


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

Implementación del interceptor de transmisión

Interceptor de transmisión del lado del cliente:


 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


Interceptor de transmisión del lado del servidor:


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


Al crear y registrar interceptores unarios y de transmisión, puede garantizar que la propagación del contexto, incluidas las estructuras personalizadas, se maneje de manera consistente en todas las llamadas de gRPC. Este enfoque garantiza que sus metadatos personalizados se administren y propaguen adecuadamente, lo que le permite crear servicios gRPC sólidos y flexibles.