天天看點

【Azure 應用服務】App Service/Azure Function的出站連接配接過多而引起了SNAT端口耗盡,導緻一些新的請求出現逾時錯誤(Timeout)

問題描述

當需要在應用中有大量的出站連接配接時候,就會涉及到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 建立出站連接配接,是以出站的端口就成了用于維護不同流的唯一辨別符。 因為在網絡流量的五元組中

  1. 目标 IP
  2. 目标端口
  3. 源 IP
  4. 源端口
  5. 協定

如通路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的工作執行個體上。

【Azure 應用服務】App Service/Azure Function的出站連接配接過多而引起了SNAT端口耗盡,導緻一些新的請求出現逾時錯誤(Timeout)

負載均衡器的前端 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連接配接

【Azure 應用服務】App Service/Azure Function的出站連接配接過多而引起了SNAT端口耗盡,導緻一些新的請求出現逾時錯誤(Timeout)

參考資料

使用 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

當在複雜的環境中面臨問題,格物之道需:濁而靜之徐清,安以動之徐生。 雲中,恰是如此!

繼續閱讀