http2客户端在写入头部信息后立即刷新,因此有两个TCP数据包被分开。https://github.com/golang/net/blob/master/http2/transport.go#L1617当有大量请求时,这可能会导致性能损失。那么是否应该将头部和主体数据包组合在一起,并在写入头部信息后不要立即刷新?
r8xiu3jd1#
我创建了一个discussion,似乎没有专业的答案。
1dkrff032#
这看起来不像是一个API变更,所以将其从提案流程中移除。CC @neild@bradfitz
t98cgbkg3#
不确定为什么有这个刷新。在尝试更改此情况时的一个担忧是,请求在读取响应的某些部分之前不会发送正文。这不是常见情况,但当将HTTP/2请求视为双向流时,这是允许的。只是删除这个刷新意味着请求永远不会被发送。在尝试更改这里的任何内容之前,我希望有一个可靠的答案来说明这种情况是如何工作的,或者为什么不重要。
lstz6jyr4#
你好,我进行了一个测试。服务器代码:
package main import ( "net/http" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" ) func main() { h2s := &http2.Server{} handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { }) server := &http.Server{ Addr: "0.0.0.0:28080", Handler: h2c.NewHandler(handler, h2s), } if err := server.ListenAndServe(); err != nil { panic(err) } }
客户端代码:
package main import ( "context" "crypto/tls" "fmt" "net" "net/http" "strings" "sync" "time" "golang.org/x/net/http2" ) func main() { client := http.Client{ Transport: &http2.Transport{ AllowHTTP: true, DialTLSContext: func(_ context.Context, network, addr string, _ *tls.Config) (net.Conn, error) { return net.Dial(network, addr) }, }, } var ( startTime = time.Now() wg sync.WaitGroup concurrency = 10 count = 100000 ) for i := 0; i < concurrency; i++ { wg.Add(1) go func() { worker(client, count) wg.Done() }() } wg.Wait() dur := time.Since(startTime) fmt.Printf("%s %d %.2f\n", dur, 10*100000, float64(10*100000)/dur.Seconds()) } func worker(client http.Client, n int) { for i := 0; i < n; i++ { _, err := client.Post( "http://192.168.0.4:28080", "application/json", strings.NewReader(`{"key":"value"}`), ) if err != nil { panic(err) } } }
当我使用原始的 golang.org/x/net/http2 包时,测试结果如下:
golang.org/x/net/http2
➜ go run ./test-h2c/client/client.go 42.616582576s 1000000 23465.04 ➜ go run ./test-h2c/client/client.go 43.251295758s 1000000 23120.69
在我注解掉 cc.bw.Flush() 之后,再次进行测试,结果如下:
cc.bw.Flush()
diff --git a/http2/transport.go b/http2/transport.go index 05ba23d..a09df11 100644 --- a/http2/transport.go +++ b/http2/transport.go @@ -1614,7 +1614,7 @@ func (cc *ClientConn) writeHeaders(streamID uint32, endStream bool, maxFrameSize cc.fr.WriteContinuation(streamID, endHeaders, chunk) } } - cc.bw.Flush() + // cc.bw.Flush() return cc.werr }
从测试情况来看,有超过 40% 的改进。此外,还可以通过 tcpdump 捕获看到修改前有两个数据包,修改后它们被合并成一个。
rur96b6h5#
我同意这个改变降低了POST延迟,这是没有疑问的。我的担忧是它改变了用户可见的行为:一个POST请求现在不会在从响应体中读取第一个字节之前发送。我不知道这是否会破坏任何现有的用户。如果有人确实想在发送第一个请求体字节之前从响应体中读取,那么在这个改变之后他们应该如何做?
bmvo0sr56#
我认为没有必要单独发送头部。当然,你可以评估在发送头部之前等待读取正文是否存在问题?
x3naxklr7#
另外,如果有问题。那么是否可以为发送头部添加等待超时?它应该支持以下情况:
7条答案
按热度按时间r8xiu3jd1#
我创建了一个discussion,似乎没有专业的答案。
1dkrff032#
这看起来不像是一个API变更,所以将其从提案流程中移除。
CC @neild@bradfitz
t98cgbkg3#
不确定为什么有这个刷新。
在尝试更改此情况时的一个担忧是,请求在读取响应的某些部分之前不会发送正文。这不是常见情况,但当将HTTP/2请求视为双向流时,这是允许的。只是删除这个刷新意味着请求永远不会被发送。
在尝试更改这里的任何内容之前,我希望有一个可靠的答案来说明这种情况是如何工作的,或者为什么不重要。
lstz6jyr4#
你好,我进行了一个测试。
服务器代码:
客户端代码:
当我使用原始的
golang.org/x/net/http2
包时,测试结果如下:在我注解掉
cc.bw.Flush()
之后,再次进行测试,结果如下:从测试情况来看,有超过 40% 的改进。
此外,还可以通过 tcpdump 捕获看到修改前有两个数据包,修改后它们被合并成一个。
rur96b6h5#
我同意这个改变降低了POST延迟,这是没有疑问的。
我的担忧是它改变了用户可见的行为:一个POST请求现在不会在从响应体中读取第一个字节之前发送。我不知道这是否会破坏任何现有的用户。如果有人确实想在发送第一个请求体字节之前从响应体中读取,那么在这个改变之后他们应该如何做?
bmvo0sr56#
我认为没有必要单独发送头部。当然,你可以评估在发送头部之前等待读取正文是否存在问题?
x3naxklr7#
另外,如果有问题。那么是否可以为发送头部添加等待超时?
它应该支持以下情况: