zoukankan      html  css  js  c++  java
  • 【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地址。

    AttributeValue
    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

    当在复杂的环境中面临问题,格物之道需:浊而静之徐清,安以动之徐生。 云中,恰是如此!

  • 相关阅读:
    用SSH指令批量修改文件夹 文件权限和拥有者
    magento转移服务器和magento建立多站点总结
    ssh 命令
    magento缓存系列详解:clean cache
    如何配置magento免运费商品方法
    Magento后台订单显示产品图片的修改方法
    如何在magento后台增加一个自定义订单状态
    Magento路径函数getBaseUrl使用方法
    图解HTTPS
    php 数组 添加元素、删除元素
  • 原文地址:https://www.cnblogs.com/lulight/p/14697432.html
Copyright © 2011-2022 走看看