paint-brush
gRPC-Secret: dominando prazos, tempos limite e contextos personalizadospor@gultm
757 leituras
757 leituras

gRPC-Secret: dominando prazos, tempos limite e contextos personalizados

por Tatyana9m2024/08/01
Read on Terminal Reader

Muito longo; Para ler

gRPC é uma estrutura de chamada de procedimento remoto (RPC) de código aberto. Ele permite uma comunicação eficiente e escalonável entre serviços. Um aspecto crucial é o gerenciamento de prazos, tempos limite de solicitação e propagação de contexto. A compreensão destes mecanismos ajuda a garantir que os serviços respondam prontamente.
featured image - gRPC-Secret: dominando prazos, tempos limite e contextos personalizados
Tatyana HackerNoon profile picture

gRPC, uma estrutura de chamada de procedimento remoto (RPC) de código aberto, permite uma comunicação eficiente e escalonável entre serviços. Um aspecto crucial do gRPC é o gerenciamento de prazos, tempos limite de solicitação e propagação de contexto, incluindo estruturas personalizadas.


A compreensão desses mecanismos ajuda a garantir que os serviços respondam prontamente, que os recursos não sejam desperdiçados em operações que excedam um período de tempo razoável e que os metadados personalizados sejam transmitidos de maneira eficaz.

Noções básicas sobre prazos e tempos limite de solicitação

Prazos

Um prazo no gRPC especifica o tempo máximo em que uma operação deve ser concluída. Caso a operação não seja concluída dentro deste prazo, ela será automaticamente encerrada. Os prazos são essenciais para garantir que os recursos do sistema não fiquem presos indefinidamente devido a serviços que não respondem ou são lentos.

Solicitar tempos limite

Um tempo limite de solicitação é um período que um cliente está disposto a esperar por uma resposta do servidor. Se o servidor não responder nesse período, a solicitação será abortada. Esse mecanismo protege o cliente de ficar pendurado indefinidamente aguardando uma resposta.

Definindo prazos e tempos limite de solicitação no gRPC

O gRPC oferece opções flexíveis para definir prazos e solicitar tempos limite tanto no cliente quanto no servidor. Veja como você pode fazer isso em Go:

Prazos de configuração do lado do 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) }

Tratamento no lado do servidor

No lado do servidor, o gRPC permite impor prazos e lidar com cenários em que o prazo especificado pelo cliente é excedido:


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


Propagando estruturas personalizadas no contexto

Para enviar estruturas personalizadas via contexto no gRPC, você precisa serializar os dados antes de anexá-los ao contexto e, em seguida, desserializá-los no destinatário. Isso envolve converter suas estruturas personalizadas em um formato que possa ser transmitido pela rede, como JSON ou buffers de protocolo, e depois adicionar esses dados serializados aos metadados de contexto.

Processo passo a passo

  1. Defina sua estrutura personalizada : defina a estrutura personalizada que deseja enviar.
  2. Serialize a estrutura : converta a estrutura personalizada em uma string ou matriz de bytes.
  3. Anexar ao Contexto : Adicione os dados serializados aos metadados de contexto.
  4. Transmitir : Envie a chamada gRPC com o contexto.
  5. Extrair e desserializar no servidor : extraia os metadados do contexto no lado do servidor e desserialize-os de volta na estrutura personalizada.

Etapa 1: Defina sua estrutura personalizada


 type CustomStruct struct { Field1 string Field2 int }


Etapa 2: serializar a estrutura


 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 }


Etapa 3: anexar ao 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 }


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


Etapa 5: extrair e desserializar no 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() } }


Implementando Middleware para Todas as Chamadas gRPC

Para lidar com a propagação de contexto, incluindo estruturas personalizadas, de forma consistente em todas as chamadas gRPC, você pode usar interceptores. Interceptores são middleware que processam solicitações e respostas, adicionando funcionalidades como registro, monitoramento e manipulação de metadados de contexto.

Interceptadores unários e de streaming

Você precisa de interceptores unários e de streaming para gerenciar diferentes tipos de chamadas RPC:


  • Interceptores unários : lidam com ciclos únicos de solicitação-resposta.


  • Interceptadores de streaming : lidam com fluxos de solicitações e respostas, incluindo streaming do lado do cliente, streaming do lado do servidor e streaming bidirecional.

Implementação do Interceptador Unário

Interceptador unário do lado do 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...) }


Interceptador unário do lado do 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) }

Implementação do interceptador de streaming

Interceptador de streaming do lado do 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


Interceptador de streaming do lado do 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) } }


Ao criar e registrar interceptores unários e de streaming, você pode garantir que a propagação de contexto, incluindo estruturas personalizadas, seja tratada de forma consistente em todas as chamadas gRPC. Essa abordagem garante que seus metadados personalizados sejam gerenciados e propagados adequadamente, permitindo que você crie serviços gRPC robustos e flexíveis.