慧林 發表於 2026-2-26 15:56:00

[DotNet] Kestrel 框架中, http1 与 http2 的性能对比

<p><strong><font size="1" color="gray">作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!</font></strong></p>
<ul>
<li><font size="1" color="gray">cnblogs博客</font></li>
<li><font size="1" color="gray">zhihu</font></li>
<li><font size="1" color="gray">Github</font></li>
<li><font size="1" color="gray">公众号:一本正经的瞎扯</font><br>
<img src="https://img2022.cnblogs.com/blog/1457949/202202/1457949-20220216153819145-1193738712.png"></li>
</ul>
<hr>
<p>(文中的 http2 是指明文的 http2 协议,也叫 h2c, 并未测试 TLS 加密的情况)<br>
如果仅从协议的角度对比,http2 会比 http1 更快吗?如果更快,能快多少?<br>
基于以上疑问,我基于 C# 的 Kestrel 框架,做了一个协议性能的对比。</p>
<h1 id="结论">结论</h1>
<p>节约大家的时间,先说结论:</p>
<ul>
<li>在同样的运行环境,同样的业务逻辑情况下。最好情况,http2 比 http1 <strong>快 4.03 倍</strong>
<ul>
<li>从处理字节数上看:http1 平均每个请求 230 字节,http2 平均每个请求 112 字节。就单个请求而言,<strong>http2 的包体积只有 http1 的包的 48.7%</strong>.</li>
</ul>
</li>
<li>http2 是二进制协议,理论上一定比 http1 这样的文本协议更快。
<ul>
<li>发挥 http2 的性能优势的关键参数是 <code>MaxStreamsPerConnection</code> (我测试时设置为 200),也就是说,一定要在一个 tcp 上并发多个 stream,才能发挥 http2 的性能优势。</li>
</ul>
</li>
<li>从应用上说:
<ul>
<li>api 服务、rpc 服务,使用 http2 更好</li>
<li>文件下载(图片、资源文件等)、html 页面输出、文件上传、大数据量的 post 等等,使用 http1 更好
<ul>
<li>使用 http1 在代理服务器上也能获得性能优势。请看前一篇: 为什么在代理服务器上测试, http2 的转发性能比 http 1 更低?</li>
</ul>
</li>
</ul>
</li>
<li>是否简单的使用 http2 的客户端,就能轻松实现 http2 比 http1 提升了 4 倍?答案是否定的,http2 的客户端并不简单。
<ul>
<li>我一共使用了四种 http2 的客户端来测试:</li>
<li>使用 nghttp2 客户端:C 语言实现,专门用于压测的工具,测试得到 http2 比 http1 快 4.03 倍</li>
<li>使用 golang 客户端,每个 HttpClient 对象对应一个协程,每个协程内一发一收:http2 的吞吐量是http1 的 80%
<ul>
<li>如果以 http1 的模式来使用 http2,http2 会比 http1 慢</li>
</ul>
</li>
<li>使用 golang 客户端,每个 HttpClient 对象上限制只有一个 tcp 连接,每个 HttpClient 对应 40 个协程一发一收:http2 的吞吐量是http1 的 1.86 倍
<ul>
<li>通过限制 tcp 连接,来让每个 tcp 连接上并行多个 stream,这才是 http2 client 的正确用法</li>
<li>压测客户端启动两个进程:http2 的吞吐量是http1 的 2.28 倍,由此说明 golang 的 http2 的 client 内部有很多锁,单个进程不如多个进程性能好。</li>
</ul>
</li>
<li>使用 golang 客户端,完全基于 tcp 协议来实现,每个 tcp 连接上,一个协程专门用于 send,一个协程专门用于 recv: http2 的吞吐量是http1 的 2.68 倍
<ul>
<li>nghttp2 客户端可能做了一些 socket option 的优化,导致做到了最好的压测性能</li>
<li>如果希望做到 http2 上的极致性能,基于 tcp 来实现是个好主意</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="压测环境说明">压测环境说明</h1>
<h2 id="基于-kestrel-的c-服务端">基于 kestrel 的C# 服务端</h2>
<ul>
<li>源码位置: https://github.com/ahfuzhang/QiWa/tree/v0.1-http-compare/code-snippets/Http2EchoServer</li>
<li>使用 dotnet 10 编译
<ul>
<li><code>make -f Makefile_linux build</code></li>
</ul>
</li>
</ul>
<pre><code class="language-bash">$(DOTNET) publish $(PRJ).csproj \
          -r linux-x64 \
          -p:DefineConstants=UNIX -p:AllowUnsafeBlocks=true \
          -p:PublishAot=true \
          -p:StripSymbols=false \
          --self-contained true \
          -c Release -o $(BUILD_DIR)
</code></pre>
<ul>
<li>http1 和 http2 采用同样的 callback 函数</li>
</ul>
<h2 id="服务器运行环境">服务器运行环境</h2>
<ul>
<li>运行于 linux amd64 环境,在 docker 容器中运行</li>
<li>限定一个 cpu
<ul>
<li>CPU 型号:Intel(R) Core(TM) Ultra 7 265KF, 小核, 4.5GHz</li>
</ul>
</li>
<li>内存 256 MB</li>
<li>限制线程池的线程数为 1: <code>ThreadPool.SetMaxThreads(1, 1)</code>
<ul>
<li>为何限制,请看前一篇:C#的 <code>ThreadPool.SetMaxThreads()</code> 配置最大线程数到底对性能有多大影响</li>
</ul>
</li>
<li>客户端在同一个机器上请求服务器,尽量达到单核 100% 的 CPU 占用率,然后在客户端统计 QPS</li>
</ul>
<pre><code class="language-bash">docker run -it --rm \
          --platform=linux/amd64 \
          --cpuset-cpus="19" \
                -m 256m \
          -v $(BUILD_DIR):/app \
          --network=host \
          mcr.microsoft.com/dotnet/runtime:10.0 \
          /app/Http2EchoServer \
          -http2.port=9081 \
                -http1.port=9082 \
                -threadpool.max=1
</code></pre>
<h2 id="nghttp2-压测">nghttp2 压测</h2>
<ul>
<li>压测 http1 端口的命令如下:</li>
</ul>
<pre><code class="language-bash">docker run --rm -it --network host goodideal/nghttp2:latest \
                h2load -p http/1.1 -c 120 -t 8 -n 2000000 \
                http://127.0.0.1:9082/echo?seq=9999
</code></pre>
<ul>
<li>
<p>120 个 tcp 连接时,测试得到最优性能表现</p>
</li>
<li>
<p>压测 http2 的命令如下:</p>
</li>
</ul>
<pre><code class="language-bash">docker run --rm -it --network host goodideal/nghttp2:latest \
                h2load -p h2c -c 8 -t 8 --max-concurrent-streams 60 -n 1000000 \
                http://127.0.0.1:9081/echo?seq=8888
</code></pre>
<ul>
<li>最优性能组合为:
<ul>
<li>连接数 8</li>
<li>max-concurrent-streams = 60, 每个 tcp 连接上并发 60 个 stream</li>
</ul>
</li>
</ul>
<h2 id="golang-客户端http1的模式压测">golang 客户端+http1的模式压测</h2>
<ul>
<li>源码请看: https://github.com/ahfuzhang/QiWa/tree/v0.1-http-compare/code-snippets/GolangHttp2Client</li>
<li>编译: <code>make build</code></li>
<li>运行: <code>make run</code></li>
</ul>
<h2 id="golang-客户端--每个-tcp-连接上多个并发的模式">golang 客户端 + 每个 tcp 连接上多个并发的模式</h2>
<ul>
<li>源码: https://github.com/ahfuzhang/QiWa/tree/v0.1-http-compare/code-snippets/GolangHttp2ClientV2</li>
<li>编译: <code>make build</code></li>
<li>运行: <code>make run</code></li>
<li>最佳配置:
<ul>
<li>tcp 连接数 8</li>
<li>每个 tcp 连接上 40 个并发</li>
</ul>
</li>
</ul>
<h2 id="golang-客户端--基于-tcp-协议来实现-http2-客户端">golang 客户端 + 基于 tcp 协议来实现 http2 客户端</h2>
<ul>
<li>源码: https://github.com/ahfuzhang/QiWa/tree/v0.1-http-compare/code-snippets/GolangHttp2ClientV3</li>
<li>编译: <code>make build</code></li>
<li>运行: <code>make run</code></li>
<li>最佳配置:
<ul>
<li>tcp 连接数 16</li>
<li>每个 tcp 连接上 80 个并发stream (不是 80 个协程)</li>
</ul>
</li>
</ul>
<h1 id="总结">总结</h1>
<ul>
<li>http2 虽然比 http1 快了 4 倍,但是 rpc 的场景,自定义协议肯定更快</li>
<li>c 实现的 nghttp2 的性能很强悍,我用 golang 实现的版本与之还有一大段距离</li>
<li>Kestrel 框架的性能相当强悍 (虽然多核情况下可能会变慢)</li>
</ul>
<p>希望对你有用。😃</p><br><br>
来源:https://www.cnblogs.com/ahfuzhang/p/19641964
頁: [1]
查看完整版本: [DotNet] Kestrel 框架中, http1 与 http2 的性能对比