paint-brush
gRPC-Secret: освоение сроков, тайм-аутов и пользовательских контекстовк@gultm
888 чтения
888 чтения

gRPC-Secret: освоение сроков, тайм-аутов и пользовательских контекстов

к Tatyana9m2024/08/01
Read on Terminal Reader

Слишком долго; Читать

gRPC — это платформа удаленного вызова процедур (RPC) с открытым исходным кодом. Это обеспечивает эффективную и масштабируемую связь между службами. Одним из важнейших аспектов является управление сроками, тайм-аутами запросов и распространением контекста. Понимание этих механизмов помогает обеспечить оперативное реагирование служб.
featured image - gRPC-Secret: освоение сроков, тайм-аутов и пользовательских контекстов
Tatyana HackerNoon profile picture

gRPC, платформа удаленного вызова процедур (RPC) с открытым исходным кодом, обеспечивает эффективную и масштабируемую связь между службами. Одним из важнейших аспектов gRPC является управление сроками, таймаутами запросов и распространением контекста, включая пользовательские структуры.


Понимание этих механизмов помогает гарантировать, что службы реагируют быстро, ресурсы не тратятся зря на операции, превышающие разумные сроки, а пользовательские метаданные эффективно передаются.

Понимание сроков и тайм-аутов запросов

Сроки

Крайний срок в gRPC указывает максимальное время, к которому операция должна быть завершена. Если операция не будет завершена в течение этого периода времени, она будет автоматически прекращена. Сроки необходимы для обеспечения того, чтобы системные ресурсы не были задействованы на неопределенный срок из-за не отвечающих или медленных служб.

Запросить таймауты

Таймаут запроса — это период, в течение которого клиент готов ждать ответа от сервера. Если сервер не отвечает в течение этого периода, запрос прерывается. Этот механизм защищает клиента от бесконечного зависания в ожидании ответа.

Установка сроков и таймаутов запросов в gRPC

gRPC предоставляет гибкие возможности установки сроков и запроса таймаутов как на стороне клиента, так и на стороне сервера. Вот как это можно сделать в Go:

Сроки установки на стороне клиента


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

Обработка на стороне сервера

На стороне сервера gRPC позволяет обеспечивать соблюдение сроков и обрабатывать сценарии, в которых срок, указанный клиентом, превышается:


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


Распространение пользовательских структур в контексте

Чтобы отправлять пользовательские структуры через контекст в gRPC, вам необходимо сериализовать данные перед их присоединением к контексту, а затем десериализовать их на принимающей стороне. Это предполагает преобразование ваших пользовательских структур в формат, который можно передавать по сети, например JSON или буферы протокола, а затем добавление этих сериализованных данных в метаданные контекста.

Пошаговый процесс

  1. Определите свою пользовательскую структуру : Определите собственную структуру, которую вы хотите отправить.
  2. Сериализация структуры : преобразование пользовательской структуры в строковый или байтовый массив.
  3. Присоединить к контексту : добавьте сериализованные данные к метаданным контекста.
  4. Transmit : отправьте вызов gRPC с контекстом.
  5. Извлечение и десериализация на сервере . Извлеките метаданные из контекста на стороне сервера и десериализуйте их обратно в пользовательскую структуру.

Шаг 1. Определите свою собственную структуру


 type CustomStruct struct { Field1 string Field2 int }


Шаг 2. Сериализация структуры


 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 }


Шаг 3. Привязка к контексту


 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 }


Шаг 4: Передача


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


Шаг 5. Извлечение и десериализация на сервере


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


Реализация промежуточного программного обеспечения для всех вызовов gRPC

Чтобы обеспечить единообразную обработку распространения контекста, включая пользовательские структуры, во всех вызовах gRPC, вы можете использовать перехватчики. Перехватчики — это промежуточное программное обеспечение, которое обрабатывает запросы и ответы, добавляя такие функции, как ведение журнала, мониторинг и обработка метаданных контекста.

Унарные и потоковые перехватчики

Для управления различными типами вызовов RPC вам потребуются как унарные, так и потоковые перехватчики:


  • Унарные перехватчики : обрабатывают одиночные циклы запрос-ответ.


  • Перехватчики потоковой передачи : обрабатывают потоки запросов и ответов, включая потоковую передачу на стороне клиента, потоковую передачу на стороне сервера и двунаправленную потоковую передачу.

Реализация унарного перехватчика

Унарный перехватчик на стороне клиента:


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


Унарный перехватчик на стороне сервера:


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

Реализация потокового перехватчика

Клиентский перехватчик потоковой передачи:


 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


Серверный перехватчик потоковой передачи:


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


Создавая и регистрируя унарные и потоковые перехватчики, вы можете гарантировать, что распространение контекста, включая пользовательские структуры, обрабатывается согласованно во всех вызовах gRPC. Такой подход гарантирует правильное управление и распространение ваших пользовательских метаданных, что позволяет создавать надежные и гибкие службы gRPC.