精品国产一级在线观看,国产成人综合久久精品亚洲,免费一级欧美大片在线观看

.NET HttpClient的缺陷和文檔錯誤讓開發人員倍感沮喪

責任編輯:editor006

作者:Jonathan Allen

2016-09-09 16:19:54

摘自:INFOQ

設計錯誤、缺陷及文檔錯誤等導致正確使用 NET HttpClient變得出奇地困難。這大大增加了可用套接字耗盡的可能,導致運行時錯誤,比如“無法連接到遠程服務器

設計錯誤、缺陷及文檔錯誤等導致正確使用.NET HttpClient變得出奇地困難。所以,即使是生產環境中看似運行正常的應用程序,在負荷不滿的情況下,也遭受著性能問題和運行時故障。

來自ASP.NET Monsters的Simon Timms就通過一篇題為“你正在錯誤地使用HttpClient,它會破壞軟件的穩定性”的文章揭示了這個事實。

人們對這篇文章的反應有所不同,但大多數都顯示出了失望和沮喪:

……我是唯一一個讀到這種內容時會生氣的人嗎?我是說,如果我們發布了那樣的代碼,會產生什么樣的后果呢?當然,我們會受到公開批評。但是,當它成為核心代碼的一部分,我們只能接受它,設計變通方案,然后一次又一次地寫同樣的文章。

那嚴重破壞了最小驚訝原則。

--Voltrondemort

我想說,這表明,HttpClient要么Bug多,要么架構差。無法確定是哪一種。如果是第二種則會很有趣,就需要使用另外一種方法代替它發送Http請求。

-- Eirenarch

C#開發人員所受到的培訓

為了理解我們如何陷入了這種境地,我們首先需要看下另外一個面向連接的類SqlConnection。在第一次接受如何使用IDisposable和using語句的培訓時,絕大多數開發人員看到的都是類似下面這樣的例子:

using (var con = new SqlConnection(connectionString)) { con.open(); //這里使用連接} //這里關閉連接

雖然針對這個示例的說明并不完善,但這個模式是正確的,而且多年來很好地服務了開發人員。然而,如果你試圖將這個模式應用到另一個IDisposable類HttpClient上,則會遇到一些始料未及的問題。

具體來說,它會打開許多套接字,比你實際的需求多許多,這極大地增加了服務器的負載。而且,這些套接字實際上不會被using語句關閉。相反,它們是在應用程序停止使用它們幾分鐘之后才會關閉。

連接池

回到SqlConnection的例子,多數面向連接的資源都會放入連接池。當你“打開”一個數據庫連接時,它首先會檢查連接池中是否存在未使用的連接。如果找到了,就重用它,而不是創建一個新的連接。

同樣,當你“關閉”一個SqlConnection連接時,它只是簡單地將連接放回連接池。最后,一個單獨的進程可以關閉長期未使用的連接,但通常來說,你可以認為它會正確地執行操作,實現性能和服務器負載的平衡。

HttpClient的工作機制并非如此。當你銷毀它時,它就啟動一個進程,關閉在它控制之下的套接字。也就是說,你下次請求連接時,必須重復整個連接新建過程。如果網絡延遲很高,或者連接是受保護的(需要新一輪的SSL/TLS協商),就會非常痛苦。

關閉一個套接字需要花費4分鐘

如上所述,關閉套接字的過程并不快。當“關閉”套接字時,你真正做的是將其狀態置為TIME_WAIT。在一個預先配置好的時間窗口內,Windows將保持該套接字的狀態不變,默認情況下是4分鐘。這是為了防止有任何剩余的數據包仍在傳輸。

這大大增加了可用套接字耗盡的可能,導致運行時錯誤,比如“無法連接到遠程服務器。System.Net.Sockets.SocketException:每個套接字地址(協議/網絡地址/端口)通常只允許使用一次”。Simon Timms寫到:

“通過谷歌搜索那個錯誤會得出一些有關縮短連接超時時間的糟糕建議。事實上,當服務器上運行的應用程序恰當地使用了HttpClient或者類似的結構,縮短超時時間會導致其他不利的結果。我們需要理解“恰當”是指什么,并修復底層的問題,而不是修改機器層的變量”。

.NET Core的性能影響

大多數僅僅使用.NET Framework完整版的開發人員不會注意到這些問題。不過,那些使用.NET Core的開發人員會有一個額外的問題,使得整個問題更加明顯。

在.NET Core的RC1和RC2版本之間,引入了一個Bug,導致HttpClient.Dispose調用會產生一個介于1010毫秒和1030毫秒之間的延遲。在.NET Core 1.2之前,這個問題預計不會得到修復。

使用代理類作為解決方案

雖然HttpClient的文檔沒有提及,但微軟模式&實踐的GitHub站點介紹了一種模式。他們把HttpClient稱為“代理類”,并作了如下描述:

那些代理類的創建成本很高。因此,它們應該只初始化一次,并在應用程序的整個生存期內重用。然而,這些類的使用方式經常會被誤解,開發人員把它們當作資源對待,認為只能根據需要請求并快速釋放[……]

Microsoft P&P建議創建一個HttpClient實例,把它存儲在一個靜態字段中,并在應用程序的生存期內共享該實例,而不是根據需求創建和銷毀。

存在誤導的文檔

這將我們帶回到了文檔存在誤導的問題。雖然是基本的樣本文件,但官方文檔v118(當前谷歌和必應搜索返回的結果)指出,HttpClient不支持跨線程共享。

該類型的任何公有靜態(在Visual Basic中為Shared)成員都是線程安全的,而任何實例成員都不保證線程安全。

差不多就是這樣。當然,如果你看一下官方文檔v110,就會發現下面這段有用的描述。

HttpClient應該只初始化一次,并在應用程序的整個生存期內重用。在負載很高的情況下,為每個請求初始化一個HttpClient類會耗盡可用的套接字數量。這會導致SocketException錯誤。下面的例子展示了HttpClient的正確用法:

public class GoodController : ApiController{ // OK private static readonly HttpClient HttpClient; static GoodController() { HttpClient = new HttpClient(); }}

根據這份文檔,以下方法是線程安全的。

這似乎是MSDN文檔一直存在的問題。要了解任何類的演進過程,都必須檢查每個版本的文檔,才能了解到新增或刪除的重要段落。

DNS Bug

如果我們遵循目前為止的建議,則會出現其他的問題。Ali Kheyrollahi寫道:

但事實證明,有一個更嚴重的問題:HttpClient不遵循DNS變化,它會(通過HttpClientHandler)獨占連接,直到套接字關閉。沒有時間限制!那么,DNS什么時候會發生變化呢?每次你進行藍綠部署的時候(在Azure云服務中,當你部署到過渡槽,然后切換生產/過渡槽);每次你改變Azure流量管理器的設置;故障轉移場景;許多PaaS服務的內部。

在被報道出來之前,這種情況已經存在了兩年多……我在想,我們到底使用.NET構建了怎樣的應用程序?

現在,如果DNS變化的原因是故障轉移,則連接應該是出現了某種形式的故障,因此,這時會打開一個到新服務器的連接。但是,如果變化的原因是藍綠部署,你切換了過渡環境和生產環境,而調用仍然會轉到過渡環境——這是我們見過的一種行為,但已經通過重啟從屬服務器修復,我們認為這可能是Azure的一個怪象。我真是個傻瓜——它就在代碼里!誰的代碼?好吧,起爭執了……

這個問題并不是無法修復。理論上講,HttpClient會遵循DNS TTL(生存期)值,默認為1小時。每次過期后,HttpClient會驗證該DNS記錄是否仍然有效,并在必要時新建一個連接指向更新后的IP地址。

但是,由于那種情況可能不會出現,所以Kheyrollahi為我們提供了一個更簡單的變通方案。借助ServicePointManager,你可以告訴HttpClient自動回收連接。

var sp = ServicePointManager.FindServicePoint(new Uri("http://foo.bar"));sp.ConnectionLeaseTimeout = 60*1000; // 1分鐘

因此,你會希望只在應用程序啟動時做這件事,只做一次,并且是針對應用程序將來會訪問的所有端點(如果端點是運行時確定的,就需要在發現那個端點時設置那個值)。記住,路徑和查詢字符串會被忽略,只有主機、端口和模式是重要的。根據場景的不同,可以將該值設為1到5分鐘。

查看英文原文:Bugs and Documentation Errors in .NET's HttpClient Frustrate Developers

鏈接已復制,快去分享吧

企業網版權所有?2010-2024 京ICP備09108050號-6京公網安備 11010502049343號

  • <menuitem id="jw4sk"></menuitem>

    1. <form id="jw4sk"><tbody id="jw4sk"><dfn id="jw4sk"></dfn></tbody></form>
      主站蜘蛛池模板: 西安市| 南陵县| 磴口县| 威信县| 云梦县| 井冈山市| 鹤庆县| 望都县| 滨州市| 阜康市| 冀州市| 元谋县| 习水县| 奉贤区| 韩城市| 滨州市| 文安县| 上思县| 海盐县| 湄潭县| 岫岩| 如皋市| 玉屏| 阿荣旗| 洞头县| 淅川县| 博爱县| 永城市| 玉山县| 胶南市| 崇仁县| 涟水县| 抚宁县| 紫阳县| 黄龙县| 古田县| 横山县| 鄂尔多斯市| 内黄县| 清镇市| 盐山县|