作者:張富春(ahfuzhang),轉載時請註明作者和引用鏈接,謝謝!
- cnblogs博客
- zhihu
- Github
- 公眾號:一本正經的瞎扯
![]()
(文中的 http2 是指明文的 http2 協議,也叫 h2c, 並未測試 TLS 加密的情況)
如果僅從協議的角度對比,http2 會比 http1 更快嗎?如果更快,能快多少?
基於以上疑問,我基於 C# 的 Kestrel 框架,做了一個協議性能的對比。
結論
節約大家的時間,先説結論:
- 在同樣的運行環境,同樣的業務邏輯情況下。最好情況,http2 比 http1 快 4.03 倍
- 從處理字節數上看:http1 平均每個請求 230 字節,http2 平均每個請求 112 字節。就單個請求而言,http2 的包體積只有 http1 的包的 48.7%.
- http2 是二進制協議,理論上一定比 http1 這樣的文本協議更快。
- 發揮 http2 的性能優勢的關鍵參數是
MaxStreamsPerConnection(我測試時設置為 200),也就是説,一定要在一個 tcp 上併發多個 stream,才能發揮 http2 的性能優勢。
- 發揮 http2 的性能優勢的關鍵參數是
- 從應用上説:
- api 服務、rpc 服務,使用 http2 更好
- 文件下載(圖片、資源文件等)、html 頁面輸出、文件上傳、大數據量的 post 等等,使用 http1 更好
- 使用 http1 在代理服務器上也能獲得性能優勢。請看前一篇: 為什麼在代理服務器上測試, http2 的轉發性能比 http 1 更低?
- 是否簡單的使用 http2 的客户端,就能輕鬆實現 http2 比 http1 提升了 4 倍?答案是否定的,http2 的客户端並不簡單。
- 我一共使用了四種 http2 的客户端來測試:
- 使用 nghttp2 客户端:C 語言實現,專門用於壓測的工具,測試得到 http2 比 http1 快 4.03 倍
- 使用 golang 客户端,每個 HttpClient 對象對應一個協程,每個協程內一發一收:http2 的吞吐量是 http1 的 80%
- 如果以 http1 的模式來使用 http2,http2 會比 http1 慢
- 使用 golang 客户端,每個 HttpClient 對象上限制只有一個 tcp 連接,每個 HttpClient 對應 40 個協程一發一收:http2 的吞吐量是 http1 的 1.86 倍
- 通過限制 tcp 連接,來讓每個 tcp 連接上並行多個 stream,這才是 http2 client 的正確用法
- 壓測客户端啓動兩個進程:http2 的吞吐量是 http1 的 2.28 倍,由此説明 golang 的 http2 的 client 內部有很多鎖,單個進程不如多個進程性能好。
- 使用 golang 客户端,完全基於 tcp 協議來實現,每個 tcp 連接上,一個協程專門用於 send,一個協程專門用於 recv: http2 的吞吐量是 http1 的 2.68 倍
- nghttp2 客户端可能做了一些 socket option 的優化,導致做到了最好的壓測性能
- 如果希望做到 http2 上的極致性能,基於 tcp 來實現是個好主意
壓測環境説明
基於 kestrel 的C# 服務端
- 源碼位置: https://github.com/ahfuzhang/QiWa/tree/v0.1-http-compare/code-snippets/Http2EchoServer
- 使用 dotnet 10 編譯
make -f Makefile_linux build
$(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)
- http1 和 http2 採用同樣的 callback 函數
服務器運行環境
- 運行於 linux amd64 環境,在 docker 容器中運行
- 限定一個 cpu
- CPU 型號:Intel(R) Core(TM) Ultra 7 265KF, 小核, 4.5GHz
- 內存 256 MB
- 限制線程池的線程數為 1:
ThreadPool.SetMaxThreads(1, 1)- 為何限制,請看前一篇:C#的
ThreadPool.SetMaxThreads()配置最大線程數到底對性能有多大影響
- 為何限制,請看前一篇:C#的
- 客户端在同一個機器上請求服務器,儘量達到單核 100% 的 CPU 佔用率,然後在客户端統計 QPS
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
nghttp2 壓測
- 壓測 http1 端口的命令如下:
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
-
120 個 tcp 連接時,測試得到最優性能表現
-
壓測 http2 的命令如下:
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
- 最優性能組合為:
- 連接數 8
- max-concurrent-streams = 60, 每個 tcp 連接上併發 60 個 stream
golang 客户端+http1的模式壓測
- 源碼請看: https://github.com/ahfuzhang/QiWa/tree/v0.1-http-compare/code-snippets/GolangHttp2Client
- 編譯:
make build - 運行:
make run
golang 客户端 + 每個 tcp 連接上多個併發的模式
- 源碼: https://github.com/ahfuzhang/QiWa/tree/v0.1-http-compare/code-snippets/GolangHttp2ClientV2
- 編譯:
make build - 運行:
make run - 最佳配置:
- tcp 連接數 8
- 每個 tcp 連接上 40 個併發
golang 客户端 + 基於 tcp 協議來實現 http2 客户端
- 源碼: https://github.com/ahfuzhang/QiWa/tree/v0.1-http-compare/code-snippets/GolangHttp2ClientV3
- 編譯:
make build - 運行:
make run - 最佳配置:
- tcp 連接數 16
- 每個 tcp 連接上 80 個併發stream (不是 80 個協程)
總結
- http2 雖然比 http1 快了 4 倍,但是 rpc 的場景,自定義協議肯定更快
- c 實現的 nghttp2 的性能很強悍,我用 golang 實現的版本與之還有一大段距離
- Kestrel 框架的性能相當強悍 (雖然多核情況下可能會變慢)
希望對你有用。😃
