AWS ALB上的HTTP2 PING帧(gRPC保持活动PING)



我正在使用AWS应用程序负载均衡器(ALB(来公开ASP.NET Core gRPC服务。这些服务在Fargate容器中运行,并公开了不安全的HTTP端口。ALB终止外部TLS连接,并基于该路由将未加密的流量转发到目标组。gRPC应用程序有几个客户端流式传输端点,客户端可以暂停流式传输几分钟。我知道有HTTP2 PING帧,可以在这种情况下使用,以在一定时间内保持没有数据传输的连接。

gRPC服务器配置为每20秒发送HTTP2 ping,以保持连接有效。我测试了这种方法,它起作用了,ping帧从服务器发出,并得到了客户端的确认。但这种方法在ALB方面失败了。在传输暂停期间,我没有看到来自负载均衡器后面的服务器的任何包(我使用Wireshark(。然后在超时1分钟后,ALB重置连接。

我也尝试使用客户端发送的HTTP2 ping。但连接也在1分钟内重置,我没有证据表明这些ping包是否真的到达了ALB后面的服务器。我有一个假设,AWS ALB不允许这样的数据包通过它,但我没有找到任何文件来证明这一点。

ALB基于HTTP协议语义而不是原始HTTP/2帧转发请求。因此,像ping帧这样的东西只适用于其中一个跃点。

如果你想要一个端到端的ping,你可以定义一个gRPC API来执行ping。对于服务器到客户端,您需要使用服务器端的流式API。但实际上,让客户端启动ping可能更可取,以减少服务器必须执行的工作程序。

AWS支持团队对我的票证做出了回应,简短的回答是ALB不支持HTTP2 ping帧。他们建议增加负载均衡器上的空闲超时值,但这种解决方案在某些情况下可能不适用。

正如Matthias247已经提到的,可能的解决方法是定义一个gRPC API来执行ping。

由于ALB不支持HTTP2 ping帧。解决此问题的一个简单方法是使用自定义PING消息。

我认为当当前流由于空闲超时(空闲时间内没有消息(而被ALB关闭时,可以获得另一个新的流来发送消息

当ALB空闲超时时,带有ErrCode=PROTOCOL_ERRORRST_STREAM消息将从ALB发送到客户端。客户端可以处理发送方和接收方中的此错误,然后获取另一个新流来发送新消息以重用http2连接。

以下是带有gRPC-go的示例代码

conn, errD := grpc.Dial(ServerAddress, 
grpc.WithTransportCredentials(cred), 
grpc.WithConnectParams(grpc.ConnectParams{MinConnectTimeout: time.Duration(63) * time.Second}),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time:                time.Second * 20,
Timeout:             time.Second * 3,
PermitWithoutStream: true,
}))
if errD != nil {
log.Fatalf("net.Connect err: %v", errD)
}
defer conn.Close()
grpcClient := protocol.NewChatClient(conn)
ctx := context.Background()
stream, errS := grpcClient.Stream(ctx, grpc.WaitForReady(true))
if errS != nil {
log.Fatalf("get BidirectionalHello stream err: %v", errS)
}
for i := 0; i < 200; i++ {
err := stream.Send(
// some message
})
if err != nil {
if err == io.EOF {
// get anothe stream to send new message on sender
stream, errS = grpcClient.Stream(ctx, grpc.WaitForReady(true))
if errS != nil {
log.Fatalf("get  stream err: %v", errS)
}
} else if s, ok := status.FromError(err); ok {
switch s.Code() {
case codes.OK:
// noop
case codes.Unavailable, codes.Canceled, codes.DeadlineExceeded:
return
default:
return
}
}
}
go func() {
for {
res, errR := stream.Recv()
if errR != nil {
if errR == io.EOF {
log.Printf("stream recv err %+v n", errR)
}
// get anothe stream to send new message on receiver
stream, errS = grpcClient.Stream(ctx, grpc.WaitForReady(true))
if errS != nil {
log.Fatalf("in recv to get stream err: %v", errS)
}
return
}
log.Printf("recv resp %+v", res)
}
}()
// over the idle timeout of alb (60 seconds)
time.Sleep(time.Duration(61) * time.Second)
}

要查看gRPC消息的详细信息,您可以通过GODEBUG=http2debug=2 go run main.go运行它

最新更新