为点对点连接使用Go gRPC指定截止日期。

huangapple go评论96阅读模式
英文:

Specify a deadline with Go gRPC for peer to peer connections.

问题

根据gRPC文档,客户端可以指定截止时间(deadlines)来确定客户端在服务器上等待多长时间后退出并返回DEADLINE_EXCEEDED错误。文档提到不同的语言有不同的实现方式,有些语言没有默认值。

在Go的gRPC文档中,通过快速搜索"deadline",发现没有相关结果。但我找到了在TCP连接的拨号器上使用WithTimeout的实现方式。

以下是一个示例代码(来自helloworld示例):

package main

import (
    "log"
    "os"
    "time"

    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
    address     = "localhost:50051"
    defaultName = "world"
    deadline    = 20 
)

func main() {
    // 设置带有超时的服务器连接
    conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithTimeout(time.Duration(deadline)*time.Second))
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)

    // 联系服务器并打印其响应。
    name := defaultName
    if len(os.Args) > 1 {
        name = os.Args[1]
    }
    r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.Message)
}

该代码只有在客户端在20秒内无法连接时才会引发错误。输出结果可能如下所示:

2016/05/24 09:02:54 grpc: Conn.resetTransport failed to create client transport: connection error: desc = "transport: dial tcp [::1]:3265: getsockopt: connection refused"; Reconnecting to "localhost:3265"
2016/05/24 09:02:54 Failed to dial localhost:3265: grpc: timed out trying to connect; please retry.
2016/05/24 09:02:54 could not greet: rpc error: code = 2 desc = grpc: the client connection is closing

正如问题标题中所提到的,我正在处理的系统是点对点的,没有中央始终在线的服务器,因此gRPC实现的重试系统非常好用。然而,我实际上正在寻找截止时间,因为如果远程连接成功,但服务器在超过20秒后才响应,WithTimeout上下文中不会引发异常。

对我来说,完美的解决方案是一个超时/截止时间系统,具备以下特点:

  • 如果客户端无法连接,在超时后返回错误。
  • 如果客户端连接成功,但服务器在超时前没有响应,在超时后返回错误。
  • 如果客户端连接成功,但连接在超时前断开,在超时后返回错误。

但我感觉我需要一些连接管理和gRPC截止时间管理的组合。有人知道如何在Go中实现截止时间吗?

英文:

According to the gRPC documentation, deadlines can be specified by clients to determine how long the client will wait on the server before exiting with a DEADLINE_EXCEEDED error. The documentation mentions that different languages have different implementations and that some languages do not have default values.

Indeed, a quick CTRL+F for "deadline" on the Go gRPC documentation reveals no results. What I did discover was a WithTimeout on the dialer for the TCP connection.

Implemented as follows (from the helloworld example):

package main

import (
    "log"
    "os"
    "time"

    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
    address     = "localhost:50051"
    defaultName = "world"
    deadline    = 20 
)

func main() {
    // Set up a connection to the server with a timeout 
    conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithTimeout(time.Duration(deadline)*time.Second)
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)

    // Contact the server and print out its response.
    name := defaultName
    if len(os.Args) > 1 {
        name = os.Args[1]
    }
    r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.Message)
}

The code will raise an error only if the client cannot connect after 20 seconds. The output will be something as follows:

2016/05/24 09:02:54 grpc: Conn.resetTransport failed to create client transport: connection error: desc = "transport: dial tcp [::1]:3265: getsockopt: connection refused"; Reconnecting to "localhost:3265"
2016/05/24 09:02:54 Failed to dial localhost:3265: grpc: timed out trying to connect; please retry.
2016/05/24 09:02:54 could not greet: rpc error: code = 2 desc = grpc: the client connection is closing

As noted in the question title, the system I'm working with is peer to peer, so there is no central, always up server and therefore the retry system that gRPC implements is wonderful. However, I'm actually looking for deadlines because if the remote does connect, but the server takes > 20 seconds to respond, no exception will be raised in the WithTimeout context.

A complete win for me would be a timeout/deadline system where:

  • if the client cannot connect, an error is returned after timeout
  • if the client connects, but the server doesn't respond before timeout, an error is returned.
  • if the client connects, but the connection drops before timeout, an error is returned.

My feeling though is that I will need some combination of connection management plus gRPC deadline management. Does anyone know how to implement deadlines in Go?

答案1

得分: 4

根据context中的WithTimeout示例:

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    // 传递一个带有超时的上下文,告诉阻塞函数在超时后放弃工作。
    ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
    defer cancel()

    select {
    case <-time.After(1 * time.Second):
        fmt.Println("overslept")
    case <-ctx.Done():
        fmt.Println(ctx.Err()) // 输出 "context deadline exceeded"
    }
}

你可以将helloworld example客户端代码更改为100毫秒的超时:

ctx, _ := context.WithTimeout(context.Background(), 100*time.Millisecond)
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
    log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.Message)
英文:

According to the WithTimeout example of context

package main

import (
    &quot;context&quot;
    &quot;fmt&quot;
    &quot;time&quot;
)

func main() {
    // Pass a context with a timeout to tell a blocking function that it
    // should abandon its work after the timeout elapses.
    ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
    defer cancel()

    select {
        case &lt;-time.After(1 * time.Second):
	    fmt.Println(&quot;overslept&quot;)
    case &lt;-ctx.Done():
	    fmt.Println(ctx.Err()) // prints &quot;context deadline exceeded&quot;
    }
} 

You can change the helloworld example client code for 100ms timeout:

ctx, _ := context.WithTimeout(context.Background(), 100 * time.Millisecond)
r, err := c.SayHello(ctx, &amp;pb.HelloRequest{Name: name})
if err != nil {
    log.Fatalf(&quot;could not greet: %v&quot;, err)
}
log.Printf(&quot;Greeting: %s&quot;, r.Message)

答案2

得分: 0

你应该更仔细地查看context包。GRPC是以上下文作为其基本组成部分构建的。你可能需要基于相关信息构建grpc.Dial上下文和client.SayHello上下文,但这应该相当简单明了。

英文:

You should look at the context package more closely. GRPC was built with contexts as a fundamental part of it. You might need the grpc.Dial context and client.SayHello context to be built off related info, but that should be fairly straight forward.

huangapple
  • 本文由 发表于 2016年5月24日 21:18:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/37414825.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定