Traefik网关DNS解析超时问题优化

1、背景

在生产环境使用 Traefik 网关时出现了偶发的 DNS 解析超时导致网关与后端服务建立连接异常的情况。通过调用链埋点数据观察发现,该部署环境中 Traefik 的 DNS 解析性能较差,耗时通常在 4ms 以上(正常应该是 1ms 以内)

初步分析:

Traefik 基于 Go 语言的 net/http 实现连接池,net/http 中仅支持连接复用未实现 DNS 缓存机制,所以每次新建连接时都会进行 DNS 解析。当网络不稳定情况下(DNS 解析使用 UDP 协议,如果网络丢包时只能依赖客户端超时)时就容易出现 DNS 解析超时情况

2、DNS 域名解析优化

1)、DNS 域名解析参数配置

DNS 解析策略、次数和 /etc/resolv.conf 文件中的 ndots、search domain(搜索域) 和 nameserver(DNS 服务器) 配置强相关:

  1. 默认情况下 DNS 查询会同时尝试解析 IPv4(A 记录) 和 IPv6 地址(AAAA 记录),无论环境是否支持 IPv6

  2. ndots 和待解析的域名决定要不要优先使用 search domain,如果你的域名中,点的个数比配置的 ndots 小,则会优先拼接 search domain 后去解析

    比如有如下配置时:

    search default.svc.cluster.local svc.cluster.local cluster.local
    options ndots:3
    

    如果要解析的域名是 apportal.rehearsal.com,ndots 配置的是3,待解析域名中的点数小于 ndots,所以会优先拼接搜索域名去解析,解析顺序如下:

    • apportal.rehearsal.com.default.svc.cluster.local.
    • apportal.rehearsal.com.svc.cluster.local.
    • apportal.rehearsal.com.cluster.local.
    • apportal.rehearsal.com.

    如果 ndots 配置的是2(待解析域名中的点数等于ndots),则解析顺序如下:

    • apportal.rehearsal.com.
    • apportal.rehearsal.com.default.svc.cluster.local.
    • apportal.rehearsal.com.svc.cluster.local.
    • apportal.rehearsal.com.cluster.local.
  3. serach domain 和 nameserver 决定了 DNS 最多查询的次数,即 DNS 最多查询的次数等于搜素域的数量+1乘以 dnsserver 的数量

    比如有以下配置:

    search default.svc.cluster.local svc.cluster.local cluster.local
    nameserver 169.254.20.10
    nameserver 172.16.0.10
    options ndots:5
    

    当解析 apportal.rehearsal.com 域名时,解析顺序如下:

    解析域名查询类型dns server
    apportal.rehearsal.com.default.svc.cluster.local.A(IPv4)169.254.20.10
    apportal.rehearsal.com.default.svc.cluster.local.A172.16.0.10
    apportal.rehearsal.com.default.svc.cluster.local.AAAA(IPv6)169.254.20.10
    apportal.rehearsal.com.default.svc.cluster.local.AAAA172.16.0.10
    apportal.rehearsal.com.svc.cluster.local.A169.254.20.10
    apportal.rehearsal.com.svc.cluster.local.A172.16.0.10
    apportal.rehearsal.com.svc.cluster.local.AAAA169.254.20.10
    apportal.rehearsal.com.svc.cluster.local.AAAA172.16.0.10
    apportal.rehearsal.com.cluster.local.A169.254.20.10
    apportal.rehearsal.com.cluster.local.A172.16.0.10
    apportal.rehearsal.com.cluster.local.AAAA169.254.20.10
    apportal.rehearsal.com.cluster.local.AAAA172.16.0.10
    apportal.rehearsal.com.A169.254.20.10
    apportal.rehearsal.com.A172.16.0.10
    apportal.rehearsal.com.AAAA169.254.20.10
    apportal.rehearsal.com.AAAA172.16.0.10
2)、Kubernetes 中搜索域和ndots 默认值

1)搜索域

Kubernetes 为方便 Service 访问,默认配置 3 个搜索域:nsSvcDomain、svcDomain、clusterDomain

default namespace下的搜索域默认为:

search default.svc.cluster.local svc.cluster.local cluster.local
  • default.svc.cluster.local:同 namespace 内可直接用 Service 名称访问(比如通过 B 访问同命名空间的 Service B)
  • svc.cluster.local:跨 namespace 可通过 ${service name}.${namespace name} 访问
  • cluster.local:支持非 Kubernetes 上的域名访问,例如设置 clusterDomain 为 rehearsal.com,那么对于 apportal.rehearsal.com 域名,可以使用 apportal 来访问

2)ndots

Kubernetes 中 ndots 默认值为5(Kubernetes官方解释),官方这样设置的初衷是优先匹配 Kubernetes 集群内域名,但可能对外部域名解析造成冗余

3)、ndots 参数优化

在我们的部署环境下都是通过三级域名(例如:apportal.rehearsal.com)来访问 Service 的,ndots 默认值为5

default namespace Pod 的 /etc/resolv.conf 文件

# cat resolv.conf 
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 192.168.0.10
options ndots:5

apportal.rehearsal.com 解析情况如下,需要解析8次才能解析到对应域名:(如果 nameserver 有两个时需要解析16次)

在这里插入图片描述

这里我们在 Deployment 中将 ndots 参数调整为2,调整后只需要2次就能解析到对应域名

在这里插入图片描述

优化依据:三级域名(含2个点)在 ndots:2 配置下,会直接解析原域名,减少不必要的搜索域拼接步骤,显著降低解析次数和耗时

3、Java DNS 缓存机制分析

在减少 DNS 解析次数后,我们还希望在 Traefik 中添加 DNS 缓存机制来减少每次新建连接时 DNS 解析的耗时,这里我们调研了 Java DNS 缓存机制来作为参考。以下从 JDK 底层缓存和 Http 客户端缓存两方面展开分析:

1)、JDK 中的 DNS 缓存

Java 通过 java.net.InetAddress 类处理域名解析,并将结果缓存以避免重复查询

缓存查询流程

解析入口方法为 InetAddress.getAllByName(String host),其优先从缓存获取结果:

// java.net.InetAddressprivate static InetAddress[] getAllByName0 (String host, InetAddress reqAddr, boolean check)throws UnknownHostException  {/* If it gets here it is presumed to be a hostname *//* Cache.get can return: null, unknownAddress, or InetAddress[] *//* make sure the connection to the host is allowed, before we* give out a hostname*/if (check) {SecurityManager security = System.getSecurityManager();if (security != null) {security.checkConnect(host, -1);}}// 先从缓存中获取InetAddress[] addresses = getCachedAddresses(host);/* If no entry in cache, then do the host lookup */if (addresses == null) {// 缓存中数据为空,进行dns lookupaddresses = getAddressesFromNameService(host, reqAddr);}if (addresses == unknown_array)throw new UnknownHostException(host);return addresses.clone();}private static Cache addressCache = new Cache(Cache.Type.Positive);private static InetAddress[] getCachedAddresses(String hostname) {hostname = hostname.toLowerCase();// search both positive & negative cachessynchronized (addressCache) {cacheInitIfNeeded();// 使用addressCache进行缓存CacheEntry entry = addressCache.get(hostname);if (entry == null) {entry = negativeCache.get(hostname);}if (entry != null) {return entry.addresses;}}// not foundreturn null;}

缓存策略控制

sun.net.InetAddressCachePolicy 中定义了缓存的三个策略值:

  • FOREVER = -1:永久缓存(默认值)
  • NEVER = 0:不缓存
  • 大于0,缓存多少秒后过期

Cache addressCache = new Cache(Cache.Type.Positive) 这里定义的 type 为 Type.Positive

        private int getPolicy() {if (type == Type.Positive) {return InetAddressCachePolicy.get();} else {return InetAddressCachePolicy.getNegative();}}
public final class InetAddressCachePolicy {private static int cachePolicy = -1;public static synchronized int get() {return cachePolicy;}

默认值为永久缓存,不过期,也不会刷新 DNS 数据

2)、Http 客户端缓存实现

在 Java Http 客户端层面,不同的 Http 客户端实现的缓存机制不同,以 Apache HttpClient 4.5.6 版本为例,核心代码如下:

// org.apache.http.impl.conn.DefaultHttpClientConnectionOperator
public class DefaultHttpClientConnectionOperator implements HttpClientConnectionOperator {public DefaultHttpClientConnectionOperator(final Lookup<ConnectionSocketFactory> socketFactoryRegistry,final SchemePortResolver schemePortResolver,final DnsResolver dnsResolver) {super();Args.notNull(socketFactoryRegistry, "Socket factory registry");this.socketFactoryRegistry = socketFactoryRegistry;this.schemePortResolver = schemePortResolver != null ? schemePortResolver :DefaultSchemePortResolver.INSTANCE;// 初始化 dnsResolverthis.dnsResolver = dnsResolver != null ? dnsResolver :SystemDefaultDnsResolver.INSTANCE;}@Overridepublic void connect(final ManagedHttpClientConnection conn,final HttpHost host,final InetSocketAddress localAddress,final int connectTimeout,final SocketConfig socketConfig,final HttpContext context) throws IOException {final Lookup<ConnectionSocketFactory> registry = getSocketFactoryRegistry(context);final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName());if (sf == null) {throw new UnsupportedSchemeException(host.getSchemeName() +" protocol is not supported");}// 使用dnsResolver进行dns解析final InetAddress[] addresses = host.getAddress() != null ?new InetAddress[] { host.getAddress() } : this.dnsResolver.resolve(host.getHostName());...}

Apache HttpClient 中默认 DnsResolver 为 Java DNS 缓存逻辑,也就是 DNS 永久不刷新

//org.apache.http.impl.conn.SystemDefaultDnsResolver
public class SystemDefaultDnsResolver implements DnsResolver {public static final SystemDefaultDnsResolver INSTANCE = new SystemDefaultDnsResolver();@Overridepublic InetAddress[] resolve(final String host) throws UnknownHostException {return InetAddress.getAllByName(host);}}

而对应 OkHttp 来说,OkHttp 内部维护了一个 DNS 缓存,具有以下特点:

  • 成功解析缓存:默认缓存时间为5分钟(300 秒),与 DNS 服务器返回的 TTL 无关
  • 失败解析缓存:默认缓存10秒,避免频繁重试无效域名
  • 缓存实现:通过连接池(RealConnectionPool)中的 RouteDatabase 关联域名与解析结果,随连接池管理自动失效

4、Traefik 添加 DNS 缓存机制

1)、方案一:net/http替换为fasthttp

可以参考最新的 PR https://github.com/traefik/traefik/pull/11122,将 net/http 替换为 fasthttp,fasthttp 支持 DNS 缓存且性能更好

但目前此功能官方标记为实验性功能,不推荐生产使用,而且这块改动相对较大,所以未采用该方案

2)、方案二:引入 DNS 缓存库

我们选择集成 go-dnscache 库实现 DNS 缓存功能,go-dnscache 库刷新 DNS 缓存有两个额外的配置选项如下:

				r := &Resolver{}options := dnscache.ResolverRefreshOptions{ClearUnused:      true, // 默认为true,清除未使用的 dns 条目PersistOnFailure: false,  // 默认为false,刷新失败时清理旧数据}resolver.RefreshWithOptions(options)

使用的 DNS 缓存策略如下:

  • 缓存有效期设置为10分钟,每10分钟异步刷新 DNS 缓存
  • 刷新时清除未使用的 DNS 条目(ClearUnused=true),缓存刷新失败时自动保留旧数据,(PersistOnFailure=true,防止因集中刷新时 DNS 解析异常影响后续程序访问)

代码改动示例:

// pkg/server/service/roundtripper.go
func NewRoundTripperManager(spiffeX509Source SpiffeX509Source) *RoundTripperManager {resolver := &dnscache.Resolver{}go func() {logger := log.Ctx(context.Background()).With().Str(logs.ComponentName, "dns-cache-refresher").Logger()t := time.NewTicker(10 * time.Minute)defer t.Stop()for range t.C {// 每隔10分钟刷新一次 dns 缓存start := time.Now()options := dnscache.ResolverRefreshOptions{ClearUnused:      false, // 不清除未使用的 dns 条目PersistOnFailure: true,  // 刷新失败时保留旧数据}resolver.RefreshWithOptions(options)logger.Info().Dur("duration", time.Since(start)).Msg("DNS cache refreshed successfully")}}()return &RoundTripperManager{roundTrippers:    make(map[string]http.RoundTripper),configs:          make(map[string]*dynamic.ServersTransport),spiffeX509Source: spiffeX509Source,dnsResolver:      resolver,}
}func (r *RoundTripperManager) createRoundTripper(cfg *dynamic.ServersTransport) (http.RoundTripper, error) {...// 创建使用 dns 缓存的 DialContextcustomDialContext := func(ctx context.Context, network, addr string) (net.Conn, error) {host, port, err := net.SplitHostPort(addr)if err != nil {return nil, err}...transport := &http.Transport{Proxy:                 http.ProxyFromEnvironment,DialContext:           utils.CachedDialContext(dialer), // 使用带 dns 缓存的 DialContextMaxIdleConnsPerHost:   cfg.MaxIdleConnsPerHost,IdleConnTimeout:       90 * time.Second,TLSHandshakeTimeout:   10 * time.Second,ExpectContinueTimeout: 1 * time.Second,ReadBufferSize:        64 * 1024,WriteBufferSize:       64 * 1024,}

参考:

go dns解析原理及调优

阿里云DNS最佳实践

kubernetes容器中域名解析优化

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/93720.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/93720.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从0到1掌握 Spring Security(第三篇):三种认证方式,按配置一键切换

> 本文是Spring Security系列第三篇,将带你实现内存、JDBC和自定义三种认证方式的无缝切换,只需修改配置文件即可完成认证策略变更! ## 一、为什么需要多种认证方式? 在软件开发的不同阶段,我们需要不同的认证策略: - **开发阶段**:使用内存认证,快速配置测试账号…

阿里云国际站云防火墙:如何利用阿里云云防火墙实现细粒度的访问控制?

利用阿里云云防火墙实现细粒度的访问控制&#xff0c;可以从分层策略、精确匹配、动态调整三个方面着手&#xff0c;让不同业务、用户和资源的访问权限清晰可控。一、明确控制目标业务隔离&#xff1a;不同业务系统、部门或环境&#xff08;生产/测试&#xff09;之间互不干扰。…

rom定制系列------小米cc9机型 原生安卓15系统 双版线刷root 定制修改功能项

小米 9 Lite/CC9 机型代码;pyxis.搭载骁龙710处理器.适用于以下型号的小米机型&#xff1a;M1904F3BG, M1904F3BC. 刷写前提; 需要当前机型已经解锁bl的状态下进入fast模式刷写。此机型可以正常官方解锁与强解bl锁。效果都是一样的。在fast模式下装好联机驱动。使用官方平台刷…

解读60页全面认识大数据基础知识培训【附全文阅读】

该培训课件适用于对大数据知识感兴趣的初学者、企业管理人员、相关技术从业者等。内容围绕大数据展开,先介绍其基本概念,包括定义、数据级别、来源、类型、价值挖掘等,还阐述了 5 个 “V” 特征及与传统数据的区别。接着讲述大数据的发展演进,涵盖国际国内发展历程、发展阶…

Prompt engineering(PE) —— prompt 优化如何进行?

从新手到高手&#xff1a;Prompt最佳实践全解析 一、引言&#xff1a;开启 Prompt 的神秘大门在这个人工智能飞速发展的时代&#xff0c;AI 已经悄然融入我们生活的方方面面。你是否有过这样的经历&#xff1a; 当你对着智能音箱询问 “明天天气如何” 时&#xff0c;它能迅速给…

云服务器的优缺点都有哪些?

云服务器作为一种有着高度灵活性的服务器类型&#xff0c;能够根据用户的需求来调整资源&#xff0c;有着很强的优势&#xff0c;但是云服务器还是有着一定的缺点的&#xff0c;本文就来共同探讨一下云服务器的优缺点都有哪些吧&#xff01;首先&#xff0c;云服务器能根据业务…

宋红康 JVM 笔记 Day05|运行时数据区内部结构、JVM中的线程说明、程序计数器

一、今日视频区间 P39-P43 二、一句话总结 运行时数据区内部结构&#xff1b;JVM中的线程说明&#xff1b;程序计数器&#xff08;PC寄存器&#xff09;&#xff1b; 三、关键图/命令 3.1 运行时数据区内部结构3.2 JVM中的线程说明3.3 程序计数器&#xff08;PC寄存器&#xff…

Java增强for循环(小白友好版)

前言&#xff1a;为什么需要增强for循环&#xff1f;作为Java初学者&#xff0c;你或许已经学会使用传统for循环来遍历数组或集合&#xff1a;for (int i 0; i < array.length; i) {System.out.println(array[i]); }这种写法需要手动维护索引变量i&#xff0c;对于集合还需…

【OLAP】trino安装和基本使用

目录 ​一、概述 1.1Trino不是什么 1.2Trino是什么 二、Trino特点 三、Trino架构 3.1架构和服务节点 3.2Trino数据模型 四、Trino安装部署 4.1配置JDK 4.2单机版&#xff08;Coordinator和Worker同进程&#xff09; 4.2.1启动服务 4.2.2下载客户端 五、配置HTTPS&…

如何写出更清晰易读的布尔逻辑判断?

列编码技巧和规范&#xff0c;来降低逻辑的“认知负荷”。成功的实践&#xff0c;必须系统性地涵盖五大关键策略&#xff1a;采用有意义的变量名进行封装、将复杂的判断拆解为独立的函数、优先使用“肯定式”而非“否定式”逻辑、利用括号明确运算的优先级、以及运用德摩根定律…

新手向:Java方向讲解

从诺基亚塞班到阿里双11&#xff0c;从安卓应用到华尔街交易&#xff0c;Java用一行System.out.println()征服了数字世界1998年&#xff0c;诺基亚在塞班系统上首次采用Java ME技术&#xff0c;让手机具备了运行应用程序的能力&#xff0c;开启了移动互联网的序幕。当时的Java开…

视觉图像界面设计【QT-creator高级编程 - 01】图像显如何保证跟随主窗口变化,且保留必要的设定窗口

前言&#xff1a;问题&#xff0c;显示图像的时候&#xff0c;按最大窗口&#xff0c;图片窗口不跟着变大&#xff0c;还有&#xff0c;右边那些设置控件都没有动解决&#xff1a;步骤1&#xff1a;1️⃣ 让 graphicsView 自动占满在 Qt Creator 中选中 graphicsView_7 / 12 / …

pair之于vector、queue(vector<pair<int,int>>)

1、vector&#xff1c;pair&#xff1c;int,int&#xff1e;&#xff1e; 和 Map 的异同点map&#xff1a;会对插入的元素按键Key&#xff0c;自动排序&#xff0c;而且键Key不允许重复&#xff1b;vector&#xff1a;的这种用法不会自动排序&#xff0c;而且允许重复。2、queu…

从合规到卓越:全星QMS如何成为制造企业的质量战略引擎

从合规到卓越&#xff1a;全星质量管理QMS软件系统如何成为制造企业的质量战略引擎 全星质量管理QMS软件系统凭借其高度定制化、智能化、全流程覆盖等核心优势&#xff0c;已在汽车制造、电子、医疗、航空航天等多个高端制造领域实现领先性应用&#xff0c;显著提升了企业的质…

按键及消抖

方法一&#xff1a;延时阻塞key.c:#include "key.h" #include "delay.h"//初始化GPIO void key_init(void) {GPIO_InitTypeDef gpio_initstruct;//打开时钟__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOA时钟//调用GPIO初始化函数…

什么是接口?PHP如何使用 SessionHandlerInterface 接口实现Session自定义会话数据存储

在面向对象编程中&#xff0c;接口&#xff08;Interface&#xff09;作为类与类之间的契约规范&#xff0c;定义了实现类必须遵守的方法签名集合&#xff0c;却不包含具体实现细节。这种抽象机制通过强制统一的方法命名和参数结构&#xff0c;实现了代码的解耦与多态性&#x…

健身房预约系统SSM+Mybatis-plus实现(二、增删改查的具体实现)

文章目录一、环境搭建二、用户管理页面&#xff08;纯展示无事件操作&#xff09;0.三步走1.查询表单&#xff08;1&#xff09;书写页面代码 &#xff1a;&#xff08;2&#xff09;对应的js部分创建对象数据模型的绑定部分&#xff1a;&#xff08;3&#xff09;引入需要的库…

在IAR Embedded Workbench for Arm中实现NXP S32K3安全调试

随着汽车电子系统变得越来越智能&#xff0c;对功能安全&#xff08;Safety&#xff09;的要求越来越高&#xff0c;同时信息安全&#xff08;Security&#xff09;也越来越被关注&#xff0c;安全调试&#xff08;Secure Debug&#xff09;机制已成为一个重要的信息安全特性。…

Vue实例中的其他属性【5】

目录1.计算属性&#xff1a;1.概述&#xff1a;2.语法特点&#xff1a;3.案例&#xff1a;案例1&#xff1a;案例2&#xff1a;案例3&#xff1a;4.总结&#xff1a;5.get函数什么时候执行&#xff1f;6.注意:2.监视属性&#xff1a;1.概述&#xff1a; 2.用法&#xff1a;1.监…

C++入门自学Day11-- String, Vector, List 复习

往期内容回顾 List类型的自实现 List类型&#xff08;初识&#xff09; Vector类的自实现 Vector类&#xff08;注意事项&#xff09; 初识Vector String类的自实现 String类的使用&#xff08;续&#xff09; String类&#xff08;续&#xff09; String类&#xff08;初识&…