一条 HTTP/2 连接上最多能同时跑多少个调用?超过了是被服务端拒绝还是排队?排队的时间算谁的?这篇用 MaxConcurrentStreams 和抓包代理逐项实测。
MaxConcurrentStreams(0):服务端第一个 SETTINGS:MAX_FRAME_SIZE=16384 MaxConcurrentStreams(4):服务端第一个 SETTINGS:MAX_FRAME_SIZE=16384 MAX_CONCURRENT_STREAMS=4
HTTP/2 的 SETTINGS_MAX_CONCURRENT_STREAMS 是接收方告诉发送方“我最多同时接受这么多流”。grpc-go 服务端默认不设置(不限制),用 grpc.MaxConcurrentStreams(n) 设置。
不限制:服务端 handler 同时在处理的峰值 20;总耗时 202.2 ms;前 100 ms 内客户端发出的 HEADERS 20 个;RST_STREAM 0 个;所有 20 个都成功
完成时刻分布:200–400 ms:20 个
限制 4:服务端 handler 同时在处理的峰值 4;总耗时 1007.1 ms;前 100 ms 内客户端发出的 HEADERS 4 个;RST_STREAM 0 个;所有 20 个都成功
完成时刻分布:200–400 ms:4 个;400–600 ms:4 个;600–800 ms:4 个;800–1000 ms:4 个;1000–1200 ms:4 个客户端读到这个设置后,同时在途的流达到上限就把新调用留在本地排队,有流结束再发。线上没有 RST_STREAM,服务端也不知道有人在排队。
第 1 轮:成功 8 个,DeadlineExceeded 12 个,其它 0 个
服务端 handler 实际被调用 14 次;客户端发出的 HEADERS 16 个;客户端发出的 RST_STREAM 8 个
客户端发出 HEADERS 的时刻分布:0–100 ms 4 个;100–300 ms 4 个;300–450 ms 4 个;450 ms 之后 4 个
第 2 轮:成功 8 个,DeadlineExceeded 12 个,其它 0 个
服务端 handler 实际被调用 12 次;客户端发出的 HEADERS 15 个;客户端发出的 RST_STREAM 7 个
客户端发出 HEADERS 的时刻分布:0–100 ms 4 个;100–300 ms 4 个;300–450 ms 4 个;450 ms 之后 3 个
第 3 轮:成功 8 个,DeadlineExceeded 12 个,其它 0 个
服务端 handler 实际被调用 13 次;客户端发出的 HEADERS 15 个;客户端发出的 RST_STREAM 7 个
客户端发出 HEADERS 的时刻分布:0–100 ms 4 个;100–300 ms 4 个;300–450 ms 4 个;450 ms 之后 3 个排队的时间算在调用自己的 deadline 里:排队太久得到的是 DeadlineExceeded,不是“服务端忙”。deadline 到期的瞬间,有的调用刚好发出又被取消,每次运行数量略有不同。
1 条连接:服务端同时处理的峰值 4;40 个调用总耗时 1013.3 ms 2 条连接:服务端同时处理的峰值 8;40 个调用总耗时 509.1 ms 4 条连接:服务端同时处理的峰值 16;40 个调用总耗时 306.4 ms 10 条连接:服务端同时处理的峰值 40;40 个调用总耗时 102.3 ms
限制作用在每条连接上。客户端建 n 条连接,并发上限就是 n 倍;但每多一条连接就多一份连接的开销(第 38 篇讨论连接池)。
2 个长流占满名额 600 ms 后:这个 Unary 调用已到达服务端的次数 0(没有到达就还在客户端排队) 长流结束(释放)之后,Unary 调用在开始后 603.8 ms 才完成;服务端 handler 共被调用 1 次
一个流从开始到结束都占一个名额,不管它传了多少数据。几个长期存活的流(订阅、双向流)可能把名额占满,使同一条连接上的普通调用排队。
全新的连接上直接发 300 个并发调用(没有预热):峰值 300;总耗时 210.4 ms
服务端不设置时客户端的配额是 MaxUint32,300 个并发调用全部同时在处理。限制是保护服务端的手段,不是默认存在的。