httpclient

.net framework 及 .net 8 HttpClient 之特殊案例探討

韓世謙 2025/02/07 11:46:45
26

背景

此專案是一個舊系統升級案,舊系統的 .net 版本為 .net framework 4.8,要升級到 .net 8.

因此,基本原則是盡量不異動舊系統程式邏輯的前提下,將系統升級.升級的過程中,除了部分必要的基礎架構的異動外,

大致都順利完成升級了.

 

所以最後階段就是要進行新系統的壓力測試,測試結果不如預期,在八台 server 總共同時 1000 人的情境下,測試了 30 分鐘,

大約會出現 5% 的 request 發生了 HttpClient SendAsync 錯誤,錯誤訊息為:

System.Net.Http.HttpRequestException: An error occured while sending the request. Response ended prematurely. (ResponseEnded)

 

即使將壓測人數及 server 降低到兩台,同時 300 人,也一樣會出現錯誤

 

但是在相同條件下以舊系統進行壓力測試,完全沒有錯誤

API 測試的流程如下圖:

處理過程

由上面的錯誤訊息去 google 或是詢問 AI 助理,得到的答案不外乎都是這個要求被 server 端拒絕了,

所以這就不是我們的責任了,應該要去詢問“資料後台”為什麼會拒絕這個請求,但是實際在“資料後台”中,沒有發現有幫助的 Log 紀錄

 

另外一個比較難解釋的點是,舊中台完全沒有錯誤,為什麼新中台會持續地出現錯誤.

 

為了還原這種情境,我在公司的環境中,架設了一台跟客戶環境類似的“模擬資料後台”,用了三台 server ,總共 500 人的情境下去做壓力測試

都完全正常,沒有出現一樣的 HttpClient 錯誤資訊

 

為了釐清這個問題,反覆做了程式調整及無數次的壓力測試,依然解決不了,就這樣拖了一段時間

 

 

最後,在客戶端測試時,側錄了 TCP 封包的資料,得到了一些資訊:

361012 -> Client建立連線(SYN)

361016 -> Server回應連線建立(SYN, ACK)

361017 -> Client回應連線建立(ACK)

361065 -> Client傳送資料(PSH, ACK)

361191 -> Server回應資料(PSH, ACK)

361194 -> Client回應(ACK)

361266 -> Client傳送資料(PSH, ACK)

361606 -> Server回應資料(PSH, ACK)

361619 -> Server回應資料(ACK)

361620 -> Client回應(ACK)

361621、362290 -> Server回應資料(ACK)

362291 -> Client回應(ACK)

362292 -> Server回應資料並通知結束連線(FIN, PSH, ACK)

362294 ->Client回應(ACK)

362323 -> Client傳送資料(PSH, ACK)

362328、362550(重送) -> Client回應連線結束(FIN, ACK)

362969 -> Server回應Reset(RST, ACK)

362972、363091 -> Server回應Reset (RST)

 

網路這方面我不熟悉,所以我將上面的封包資料詢問我的“助理”,想看看他有什麼想法.經過了兩三回的詢問後,我得到了幾種可能的因素,

有一點我覺得有解決問題的機會,如下:

 

HttpClient 使用共享的連線池來進行 HTTP 請求。如果多個請求共享連線池並且超過了伺服器的允許併發數量,可能會導致擁塞和重試。調整 MaxConnectionsPerServer 可以限制每個伺服器的併發連接數,從而防止過多的重送

 

但是上述的說明無法解釋新舊系統的測試結果不同,但是我得到了一些靈感,於是我去查詢了 .net framework 4.8 及 .net 8 的 HttpClient MaxConnectionsPerServer 程式預設值是多少.

 

得到的結果有非常大的差距:

.net framework 4.8 預設值為 “2”

.net 8 預設值為 Int.Max(也就是 20億)

 

所以我們就將新系統的 MaxConnectionsPerServer 改成了 2 或 10,再進行壓力測試就完全沒有發生錯誤了

 

最後就來探討為什麼會有這種問題

在公司環境進行測試時,我所架設的”模擬資料後台“的程式框架為 .net 8

在客戶端的環境中,他們的“資料後台”為 Java 寫的服務,因為舊系統是在五年前就上線了,因此我猜測他們的“資料後台”也還是維持當初上線的程式版本,

沒有進行升級

 

而我也有提供給客戶我寫的“模擬資料後台”程式,請他們放到相同的 server 建置起來,壓力測試後,沒有發生相同的 HttpClient 錯誤

 

所以,應該是客戶的“資料後台”的版本太舊了,無法承受同時大量的連線,因此部分的請求被拒絕了

 

.net 在這幾年中進行了好幾次的升級,升級的核心有個重點是在對於高併發的處理,因此在 .net 8 的版本中,他可以將同時連線數的預設值放大到 20 億

代表他能夠承受非常大量的同時請求,這也是為什麼在我自己架設的環境中,一直無法重現相同的錯誤

 

在 Java 這幾年中,我認為也有進行了類似的升級,只是客戶端還是維持著舊版本

結論

如果要讓新系統發揮全部的效能,除了本身的專案系統要進行升級外,搭配的其他系統最好也要同步進行升級,才能發揮最大的效益,避免一些未預期的潛在錯誤

 

 

韓世謙