【问题标题】:gRPC server blocked on SendMsg在 SendMsg 上阻止 gRPC 服务器
【发布时间】:2022-08-15 01:50:09
【问题描述】:

我们遇到了一个问题,即我们的 gRPC 流服务器在 SendMsg 上被阻止,并带有以下堆栈跟踪:

google.golang.org/grpc/internal/transport.(*writeQuota).get(0xc000de4040, 0x32)
    /root/go/pkg/mod/google.golang.org/grpc@v1.46.0/internal/transport/flowcontrol.go:59 +0x74
google.golang.org/grpc/internal/transport.(*http2Server).Write(0xc000bb4680, 0xc000aa6000, {0xc000f2be60, 0x5, 0x5}, {0xc000d6d590, 0x2d, 0x2d}, 0x0)
    /root/go/pkg/mod/google.golang.org/grpc@v1.46.0/internal/transport/http2_server.go:1090 +0x23b
google.golang.org/grpc.(*serverStream).SendMsg(0xc0002785b0, {0xb8f9e0, 0xc000b686c0})
    /root/go/pkg/mod/google.golang.org/grpc@v1.46.0/stream.go:1530 +0x1cc

我们的服务器单向流向客户端。我们在节点上每隔 4-6 小时就会遇到这个问题,但大约 15 分钟后,TCP 连接将关闭,客户端将重新连接,并且流式传输将像以前一样继续。我们通过每 10 秒使用一次保持活动来初始化服务器来解决此问题:

server := grpc.NewServer(grpc.KeepaliveParams(keepalive.ServerParameters{Time: time.Duration(10) * time.Second, Timeout: 0}))

这个问题在过去两天内停止发生。现在这个问题在过去的 5 个小时里一直在单个节点上发生,并且还没有消失。

这是ss 的输出:

$ ss -ntmp|grep -A 1 9222
ESTAB      0      0      10.192.254.1:9222               10.120.224.70:50380
     skmem:(r0,rb524288,t0,tb524288,f0,w0,o0,bl0,d0)

对于在节点上正常运行的服务器,t (wmem_alloc) 值和 w (wmem_queued) 值非零。根据this answer,这表明没有数据包排队等待传输。

我还看到每 10 秒从服务器发送一次保持活动的 ACK。顺序是:

  • 服务器发送PSH, ACK
  • 客户端立即回复PSH, ACK
  • 服务器向上面发送ACK
  • 服务器在 10 秒后发送另一个 PSH, ACK

所以服务器保活机制认为一切正常。我没有看到来自客户端的任何保活。我会尝试为客户端设置一个keep-alive,但是为什么会出现这个问题?

  • 客户端是否在 RecvMsg 上被阻止?中间有代理吗?如果服务器在 SendMsg 上被阻止并且连接正在响应 keepalives,那么唯一真正的答案(除了我以前从未见过的错误)是客户端没有接收,并且流控制已满。
  • 中间没有代理。客户端应该在 RecvMsg() 上阻塞,但是当这个问题发生时我没有看客户端,所以我不确定它是否在 RecvMsg() 之后被卡在写入无缓冲通道或其他东西上。不过,自从我们添加了客户端超时后,这个问题就没有发生过。
  • 无论如何,客户端保活都是一个好主意。 (我们已经讨论过默认打开它们,但仍然没有这样做。)我不希望它能够解决这种服务器阻塞问题,但也许我只是不完全理解设想。如果它返回,请检查客户端,因为阻塞的服务器发送很可能是流量控制已满。
  • 在您的情况下,grpcClient.Stream 中使用了哪些选项?
  • @zangw 不熟悉那个 API。在服务器上,我如上所述调用grpc.NewServer()。在客户端,我用grpc.WithTransportCredentials(insecure.NewCredentials())grpc.WithKeepaliveParams() 调用grpc.Dial()。自从我们添加了客户端超时后,这个问题就没有发生过。

标签: go grpc


【解决方案1】:

(*writeQuota).get 中遇到与卡住SendMsg 类似的问题 通过使用 Mutex 保护 SendMsg 来解决,以防止并发调用:

type ClientStream interface 上的 cmets 说:

// ... but it is not safe
// to call SendMsg on the same stream in different goroutines. It is also
// not safe to call CloseSend concurrently with SendMsg.

【讨论】:

  • 这对我们来说不是问题,因为服务器通过自己的流向每个客户端发送。
【解决方案2】:

我们在客户端上添加了超时,自从我创建了这个问题后,这个问题就没有发生过:

grpcConn, err := grpc.Dial(bindAddr,
    grpc.WithKeepaliveParams(keepalive.ClientParameters{Time: time.Duration(10) * time.Second, Timeout: 0}))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-04
    • 2017-01-08
    • 2020-02-02
    • 2011-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多