golang: время ожидания вызова grpc

#go #grpc

#Вперед #grpc

Вопрос:

Я использую приведенный ниже код для подключения к серверу grpc, и clientConn объект используется для всех последующих вызовов rpc. maxDelay устанавливается равным 5 секундам. Теперь из-за какой-то проблемы на сервере он не отвечает на вызов grpc. Итак, мой клиент долго ждет каждого вызова rpc. Нужно ли устанавливать время ожидания по-другому?

     b := grpc.BackoffConfig{
            MaxDelay: maxDelay,
    }

    clientConn, err := grpc.Dial(serverAddress, grpc.WithBackoffConfig(b), grpc.WithInsecure())
    if err != nil {
            log.Println("Dial failed!")
            return err
    }
  

Комментарии:

1. MaxDelay может быть неверным, вам нужно установить время истечения крайнего срока, чтобы соединение выдавало ошибку / исключение по тайм-ауту, проверьте ссылки grpc.io/blog/deadlines

2. Задержки отката не являются тайм-аутами. Они определяют время ожидания между повторными попытками. Используйте контексты для тайм-аутов и / или отмены.

Ответ №1:

Вы можете изменить свой код, чтобы добавить тайм-аут, используя grpc.WithTimeout(5 * time.Second) вместо использования MaxDelay , и grpc.WithBackoffConfig(b) которые предназначены для повторных попыток и задержки повторных попыток.

 clientConn, err := grpc.Dial(serverAddress, grpc.WithTimeout(5 * time.Second), grpc.WithInsecure())
if err != nil {
        log.Println("Dial failed!")
        return err
}
  

Однако приведенное выше устарело, в качестве альтернативы вы можете использовать DialContext и context.WithTimeout

 ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)

clientConn, err := grpc.DialContext(ctx, serverAddress, grpc.WithInsecure())
if err != nil {
    log.Println("Dial failed!")
    return err
}
  

Ответ №2:

Используется context.WithTimeout grpc.DialContext для управления временем ожидания всех вызовов RPC текущего DialContext. Неудобно обрабатывать разные тайм-ауты одного / некоторых конкретных вызовов RPC.


Мы могли бы определить один пользовательский тайм-аут callOption для обработки значения принудительного тайм-аута некоторых вызовов RPC в clientInterceptor

Определите пользовательский тайм-аут callOption через EmptyCallOption

 type TimeoutCallOption struct {
    grpc.EmptyCallOption

    forcedTimeout time.Duration
}

func WithForcedTimeout(forceTimeout time.Duration) TimeoutCallOption {
    return TimeoutCallOption{forcedTimeout: forceTimeout}
}
  

Обрабатывать forcedTimeout в UnaryClientInterceptor

 func getForcedTimeout(callOptions []grpc.CallOption) (time.Duration, bool) {
    for _, opt := range callOptions {
        if co, ok := opt.(TimeoutCallOption); ok {
            return co.forcedTimeout, true
        }
    }

    return 0, false
}

func TimeoutInterceptor(t time.Duration) grpc.UnaryClientInterceptor {
    return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
        invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
        timeout := t
        if v, ok := getForcedTimeout(opts); ok {
            timeout = v
        }

        if timeout <= 0 {
            return invoker(ctx, method, req, reply, cc, opts...)
        }

        ctx, cancel := context.WithTimeout(ctx, timeout)
        defer cancel()

        return invoker(ctx, method, req, reply, cc, opts...)
    }
}
  

Примеры использования

 // The default timeout of RPC call of this conn is 3 seconds
conn, err := grpc.Dial(
        address,
grpc.WithUnaryInterceptor(TimeoutInterceptor(time.Duration(3)*time.Second)), ...)

....

c := pb.NewGreeterClient(conn)
c.SayHello(context.Background(), amp;pb.HelloRequest{Name: "world"},
            WithForcedTimeout(time.Duration(10)*time.Second))
// The timeout of SayHello RPC call is 10 seconds