問題描述
當需要在應用中有大量的出站連線時候,就會涉及到SNAT(源地址網路轉換)耗盡的問題。而通過Azure App Service/Function的預設監控指標圖表中,卻沒有可以直接檢視到SNAT是否耗盡的問題(可以間接參考App Service Plan級中Metrics的 Socket Outbound All指標[截圖見文末附錄一],但是由於它是整個Plan下所有App Service的彙總資料,不能直接表明SNAT是否超過128的限制)。
這裡所說的出站連線如:SQL資料庫, Redis快取以及其他的Restful API等等需要從App Service中向外發出的請求。當SNAT耗盡後,會出現以下一種或多種問題:
- App Service的響應速度緩慢。
- 間歇性 5xx 錯誤或“錯誤的閘道器”錯誤
- 超時錯誤訊息
- 無法連線到外部終結點(例如 SQL DB,Redis,及其他API等)
問題分析
因為App Service是部署在雲服務中,所以它也遵循著一個叢集中很多例項(VM)通過負載均衡器的前端 IP 建立出站連線,所以出站的埠就成了用於維護不同流的唯一識別符號。 因為在網路流量的五元組中
- 目標 IP
- 目標埠
- 源 IP
- 源埠
- 協議
如訪問Redis服務(redistest01.redis.cache.chinacloudapi.cn 6380),目標IP為固定不變為RedisHost,而埠則固定為6380,源IP為當前App Service的出站IP,協議方式為Redis的序列化協議(RESP)。以上1,2,3,5都不可變的情況下,只有4 源埠可以改變,所以這裡就需要使用SNAT。
如App Service中一個示例(Worker Instance)傳送TCP協議,介紹SNAT的工作流程:
1)App Service應用傳送一個TCP包到外部IP地址,源地址和埠在TCP包中。
2)TCP包從App Service應用的工作例項上傳送到SNAT負載均衡,SNAT改變了TCP包中的源地址為負載均衡器的公共IP地址和埠號。然後傳送到外面目標IP地址。
Attribute | Value |
---|---|
Protocol | TCP |
Worker instance IP address:port | 10.0.5.60:51014 |
Load balancer IP address:port | 13.76.245.72:12481 |
External endpoint IP address:port | 52.189.232.180:80 |
3)外部服務接收到這個TCP包後,會原路回包,它會使用負載均衡器的公共IP地址和埠後作為目標IP和埠。
4)當負載均衡器收到外部服務的回包後,它將根據第2步中的對映關係,修改TCP包中的目標IP和埠。如此TCP回包就正確的回到了App Service的工作例項上。
負載均衡器的前端 IP 分配的每個公共 IP 都會為其後端池成員分配 64,000 個 SNAT 埠,後端池中大約有400多個例項,所以大約分配到每個例項的SNAT埠為64,000/400 =160(最大), 但是實際上分配的個數為128個。
間歇性連線問題的主要原因是在建立新的出站連線時遇到限制。 可以命中的限制包括:
- TCP 連線數:可以建立的出站連線數有限制。 對出站連線的限制與使用的輔助角色的大小關聯。
- SNAT 埠: Azure 使用源網路地址轉換 (SNAT) 和負載均衡器 (不向客戶公開,) 與公共 IP 地址進行通訊。 最初為 Azure 應用服務中的每個例項預分配了 128 個 SNAT 埠。 SNAT 埠限制會影響與相同地址和埠組合的開啟連線。 如果應用與混合的地址/埠組合建立了連線,則不會用盡 SNAT 埠。 重複呼叫同一個地址/埠組合時,會用盡 SNAT 埠。 釋放某個埠以後,即可根據需要重複使用該埠。 只有在等待 4 分鐘後,Azure 網路負載均衡器才會從關閉的連線回收 SNAT 埠。
當應用程式或功能快速開啟新的連線時,它們可能很快就會耗盡預分配的配額(128 個埠)。 然後,應用程式或功能會一直受到阻止,直到通過動態分配額外的 SNAT 埠或者通過重複使用回收的 SNAT 埠提供了新的 SNAT 埠為止。 如果你的應用程式用完了 SNAT 埠,則會出現間歇性的出站連線問題。
解決辦法
避免 SNAT 埠問題意味著需要避免對同一主機和埠反覆建立新連線。 連線池是解決該問題的更顯而易見的方法之一。
如短時間無法修改程式碼,基於App Service, Azure Function的易擴充套件的特性,可以增加例項個數來及時緩解SNAT的受限問題。當每增加一個例項,SNAT埠即可增加128個。
建立連線池的方式一:HttpClientFactory 建立 HTTP 連線池
儘管 HttpClient 實現了 IDisposable
介面,但它是為重複使用而設計的。 關閉 HttpClient
的例項使套接字在 TIME_WAIT
一小段時間內處於開啟狀態。 如果經常使用建立和處置物件的程式碼路徑 HttpClient
,應用可能會耗盡可用的套接字。 ASP.NET Core 2.1 中引入了HttpClientFactory作為此問題的解決方案。 它處理池 HTTP 連線以優化效能和可靠性。
建議:
- 不要 直接建立和釋放
HttpClient
例項。 - 請 使用 HttpClientFactory 來檢索
HttpClient
例項。
示例部分
1)以下程式碼即可復現SNAT快速耗盡的情況(沒有及時釋放Response資源)
public string Index(string url) { var request = HttpWebRequest.Create(url); request.GetResponse(); return "OK"; }
可以修改為:
public string Fin(string url) { var request = HttpWebRequest.Create(url); var response = request.GetResponse(); response.Close(); return "OK"; }
2)下面使用Using來釋放httpclient物件,但是也是用以導致SNAT耗盡的問題。
public async Task<string> Client(string url) { using (var client = new HttpClient()) { await client.GetAsync(url); } return "OK"; }
可以考慮重用HttpClient來緩解SNAT耗盡問題
private static Lazy<HttpClient> _client = new Lazy<HttpClient>(); public async Task<string> ReuseClient(string url) { var client = _client.Value; await client.GetAsync(url); return "OK"; }
附錄一:應用服務計劃中檢視全部的出站Socket連線
參考資料
使用 SNAT 進行出站連線(負載均衡器預設埠分配) : https://docs.microsoft.com/zh-cn/azure/load-balancer/load-balancer-outbound-connections#default-port-allocation
排查 Azure 應用服務中的間歇性出站連線錯誤:https://docs.microsoft.com/zh-cn/azure/app-service/troubleshoot-intermittent-outbound-connection-errors#avoiding-the-problem
SNAT with App Service,介紹Azure App Service中SNAT的原理,問題,及如何避免?:https://www.cnblogs.com/lulight/articles/13543209.html