虎牙在全球 DNS 秒级生效上的实践

这次分享的是全球 DNS 秒级生效在虎牙的实践,以及由此产生的一些思考,整体上,分为以下5各部分:

背景介绍;
方案设计和对比;
高可用;
具体实践和落地;
规划;
背景介绍
虎牙用到的基础技术很多,DNS 是其中比较重要的一个环节。

虎牙在全球 DNS 秒级生效上的实践

DNS 的解析过程很关键,例如上图中的 DNS 解析器通过一个定位解析追踪到我们的 DNS,再到本地域名服务器迭代解析,经过根域再到.com名,最后到huya.com的根域名,获取最终的解析结果。

在这个过程中, DNS解析是天然的分布式架构,每一层都会有缓存,上一层出现问题挂掉,下一层都会有缓存进行容灾。另外,整个 DNS 协议支持面广,包括手机和 PC,我们用的编程框架里也有 DNS 解析器,服务器也会配 DNS 解析引擎,因此,DNS 在虎牙的基础设施中是很重要的部分。

虎牙的 DNS 的应用现状
虎牙当前主要是依赖于公共的 DNS,相信在座的小伙伴们或多或少都会遇到过下面这些问题:

依赖公共 localDNS,解析不稳定,延迟大。
记录变更生效时间长,无法及时屏蔽线路和节点异常对业务的影响。例如,权威 DNS 全球各节点数据同步时间不可控,全局生效时间超过10分钟;localDNS 缓存过期时间不可控,部分 localDNS 不遵循TTL时间,缓存时间超过48小时。
内部 DNS 功能缺失,无法解决内部服务调用面临挑战。例如,时延大、解析不准、支持多种调度策略。
无法满足国外业务的快速发展,虽然一些海外云厂商提供了基于 DNS 的快速扩容方案,以及基于 DNS 的数据库切换方案。
方案设计和对比
基于以上的问题,我们开始重新规划 DNS 的设计。

名字服务架构
虎牙在全球 DNS 秒级生效上的实践

整个规划会分三个方面,核心是我们做了「名字服务」的中心点,基于此,可以满足我们的需求。

一方面通过 Nacos Sync,将现有多个注册中心的服务, 同步到「名字服务」中, 通过 DNS 实现不同框架之间的 Rest 服务方式的调用, 实现例如 Eureka,Consul,Taf等框架之间的服务调用。

另一方面,在全球负载均衡的场景下,由于虎牙是以音视频业务为主,而音视频业务对节点的延迟是非常敏感的,所以我们希望一旦出现节点延迟的情况,能立马做切换。

第三个是传统 DNS 的场景, 可以满足容器和物理机的 DNS 需求, 提供本机 Agent 和集群两种方案, 通过缓存和 prefect 大大提高 DNS 解析的可用性和加快生效时间。

虎牙在全球 DNS 秒级生效上的实践

对于名字服务的总体设计主要分3部分,接入层需要提供 API,消息通知和 DNS 接入的能力。核心功能需要能在基于现有网络数据,CMDB 和 IP 库的数据基础上,提供灵活的负载均衡能力,全球数据的秒级同步,多个数据源的同步,能对全网服务的健康状态进行监控,及时感知到大范围的节点异常,并且能够及时将节点的屏蔽的信息推送到端上。

最终,我们选择 Nacos 作为名字服务的核心,提供统一的 API ,包括名字注册、变化推送、负载均衡等;Nacos Sync 作为全球集群间数据同步的组件;DNS - F是客户端组件,用于拦截 DNS 请求,实现基于 DNS 的名字服务。

改造前后 DNS 变更生效流程的不同
接下来,我们通过对比看下改造前后 DNS 变更生效流程的差异。

虎牙在全球 DNS 秒级生效上的实践

原有 DNS 变更生效流程中,对 DNS 生效时间有影响的是:

Auth DNS:

跨区域、跨国数据同步慢,不稳定。

bind 在数据量比较大的时候,同步比较慢。

Local DNS:

根据 TTL 缓存,过期后才会刷新数据。

部分厂商不遵循 TTL 时间缓存,超过24小时的缓存时间。

服务器:

服务器开启 nscd 做 DNS 缓存。

业务进程:

应用的 DNS 缓存,比如 Java 虚拟机、框架层的 DNS 缓存。

以上四种情况会比较影响 DNS 的变更生效流程,下图是我们现有的 DNS 变更生效流程:

虎牙在全球 DNS 秒级生效上的实践

整体上相对简单,只要业务进程这边将自己内部的 DNS 缓存关掉, 通过 DNS-F 进行查询的时候, 会直接到最近的 Nacos 集群拉取最新的服务节点信息, 而且后续节点的变化也会推送到 DNS-F 中, 后续可以直接在缓存中获取最新信息。

国内 Nacos 集群:

集群内通过 raft 协议同步数据,毫秒级别完成同步。

Nacos Sync:

Nacos 推送变化到 Nacos Sync,跨区域、跨国网络差的情况下可能会导致推送结果丢失,或者延迟加大。

Nacos Sync 会主动拉取实例变更,拉取周期和监听的服务数量会影响到变更时效。

DNS - F:

Nacos 会将变更推送到 DNS - F,网络差的情况可能会导致推送结果丢失,或者延迟加大。

DNS - F 会主动拉取实例变更,拉取周期和监听的服务数量会影响到变更时效。

业务进程:

通过应用禁用 DNS 缓存来解决。

核心设计 Nacos
Nacos 有两套推送机制。

虎牙在全球 DNS 秒级生效上的实践

一种是通过客户端来选择一个可获节点,比如它第一次拉取的是一个正常节点,这个正常节点就会跟它维护一个订阅关系,后面有变化就会有一个相应的实地变化推送给我。如果当前节点挂掉, 他会通过重连, 在节点列表上,连上一个正常的节点。这时候会有新的 DNS 关系出现。

另一种是通过 SDK 的方式,在服务端寻找可获节点。服务端每个节点之间, 会进行一个可活的探测, 选择其中一个可活节点用户维护这个订阅关系。 当这个节点出现问题, 连接断开后, SDK 重新发送订阅请求,服务端会再次选择另外一个可活的节点来维护这个订阅关系。这就保证整了推送过程不会因为某个节点挂掉而没有推送。

推送的效率方面,主要是用 UDP 的方式,这个效率不像 TCP 消耗那么高。

以上两个方案都比较适合我们目前的场景。

核心组件设计 Nacos Sync
我们选择 Nacos Sync 作为多集群数据同步的组件,主要是从以下4方面进行考虑的。

同步粒度:
Nacos Sync 同步数据的时候是以服务为维度, 比较容易做最终一致性处理, 同时可以提供保活的机制,满足节点维持的场景。 数据库通过 Binlog 同步的方式只能局限于事务粒度, 而文件同步只能通过单个文件的粒度, 在服务同步这个维度并不是很合适。

可用性方面:
Nacos Sync 作为一个中间件,是以集群方式进行的,传统的数据库和文件方式基本是单进程进行的,可用性方面可能不太满足要求。

同步方式方面:
Nacos Sync 通过在服务粒度的全量写入,满足服务注册和 DNS 这两种场景, 不需要额外的事务消耗, 能保证最终一致即可。

环形同步:
我们国内有多个可获的节点,希望它们之间的数据可以进行环形同步,每个节点之间是相互备份的,这时候用 Nacos Sync 的话,是支持的。虽然数据库方面,比较经典的是主主同步,但如果同时对一个主件进行更新的话,每一个点进行协助是会有问题的,而且文件方面是不支持的。

Nacos Sync 和开源版本的不同
我们对 Nacos Sync 开源方案上做了几处修改,以更好的适用于现在的场景:

第一,通过配置方式对任务进行分拆。因为在实际应用场景里面,因为 Nacos Sync 的任务达一两万,单机很容易到达瓶颈,所以我们通过配置的方式将这些分片到多台 Nacos Sync 机器上。

第二,通过事件合并和队列控制的方式控制 Nacos 集群的写入量,以保证后端的稳定性。虽然下发事件一秒钟只有一个,但在很多场景中,例如需要 K8s 或者 Taf 进行数据同步的时候,变化的频率是非常高的,这时候通过事件合并,每个服务单独进行一个写入进程。这样通过队列控制的方式可以控制整个 Nacos 集群的写入量。

第三,添加了能支持从K8s 和 Taf 同步数据的功能。后期我们会将这个特性提交给 Nacos,让更多的开发者使用。

核心组件设计 DNS - F
DNS - F是基于 CoreDNS 上开发的,我们扩展了以下 4 个组件:

Nacos 插件:查询 Nacos 服务信息,监听 Nacos 服务变化,并将服务转化为域名,实现以 DNS 协议为基础的服务发现;

Cache 插件:提供域名缓存服务;

Log 插件:将 DNS 解析日志上报到日志服务;

Proxy 插件:代理解析外部域名;

DNS - F 和开源版本的不同
第一,在日志组件里面将日志上传到自己的日志服务。

第二,对缓存功能做了一个增强。一般的缓存功能可能根据 TTL 时间会过期,我们把这个过期时间给去掉了,直接令到缓存永远不会过期,然后通过异步将这个缓存进行刷新。比如 TTL 可能快到到时间了,我们就会主动做一个查询或者推送查询,这样,服务端或者公共 DNS 出现问题的时候,就不会影响到整体服务。

第三,增强了高可用的保障能力。包括进程监控、内部运营和外部运营的探测。另外,原来的开源版本用的是本机部署的方式,我们做成了集群化的部署,解决了服务推送、服务负载均衡方面的问题。

高可用
接下来由我们团队的李志鹏,分享一下虎牙在高可用方面的实践。

周健同学跟大家介绍了项目的背景跟方案设计,我来和大家介绍一下具体的实践和落地,实践过程中的主要关注点是高可用。

全球化部署方案
虎牙在全球 DNS 秒级生效上的实践

这是虎牙的一个全球化的部署方案,我们在全球部署了两个大区,分别是国内和国外。这两个大区是指定服务同步的,走的是专线,这样可以保障同步的稳定性。在一个大区内我们又部署了多个接入点,例如在国内大区,我们部署了深圳和无锡两个接入点,这两个节点的数据是互相同步、互为备份,保证在一个集群挂掉下可以切换到另外一个集群。

多个接入点的情况下,我们通过 HttpDNS 实现客户端的就近接入。客户端定期请求 HttpDNS,HttpDNS 能根据地域寻找就近接入点。如果接入点出现故障,我们就直接在HttpDNS 把这个节点给摘除,这样客户端就能快速地切换到另外一个接入点。

接下来讲一下单个集群下的部署方案。

虎牙在全球 DNS 秒级生效上的实践

单个集群部署了多个 Nacos 节点,并通过7层负载均衡的方式暴露给外面使用,并且提供了多个 VIP,满足不同线路和区域的接入要求。同时,Nacos Sync 做了分片处理,将同步压力分散到各个分片上,一个分片下我们又部署了多个 Nacos Sync 的节点,以保障多活和高可用。

线上演练
演练的场景是模拟一个单个集群挂了和两个集群都挂了。

虎牙在全球 DNS 秒级生效上的实践

从图中可以看到,把深圳的流量切走之后,无锡的流量就涨上去了,然后再把无锡的流量切走,再关闭服务,这样就可以看到两边的流量已经没了。之后,再去恢复两个集群的流量,看一下整个切换过程中对服务的影响。

虎牙在全球 DNS 秒级生效上的实践

首先看一下对写入的影响,在单个集群挂了的情况下,是没有任何影响的。如果是两个集群都挂了,写入就会失败。可以看到,这个图有一个波峰,这个波峰就是我们两个集群都挂了的情况下,写入失败延迟加大。

但是切换的整个过程对 DNS-F 是没有任何影响的,延迟保持平稳。此外,在集群重新上线前,我们需要做数据校验,保证集群之间元数据和实例数据的最终一致。

可用性级别方面,我们可以保障:

单集群挂掉后不会有影响;
双集群挂掉后只会影响域名变更,不影响域名解析;
线上演练数据校验机制
运行过程中,我们也要保证集群间数据的一致性。我们通过全量校验和增量校验两种手段去保证,全量校验方式如下:

大区内部做10分钟的全量校验,保证大区内各个集群数据的一致;
大区之间做2分钟做一次全量校验,保证大区之间被同步的服务的数据一致性。
增量校验方式如下:
从其他数据源同步的数据,通过数据源的时间戳,做增量校验;
基于API的写入日志,定期校验写入的内容是否已经全部同步。
DNF - S 高可用
关于 DNS - F 的高可用,我们主要做了以下5个点:

Agent 的健康状态监测,包括进程存活和是否能正常解析;
缓存内部域名,并做持久化处理,保证 Nacos 集群出现问题时不会影响内部域名的解析;
提供备用节点,保证在 DNS-F 挂了,或者是 DNS-F 需要升级的情况下,也不会影响到内部域名解析;
resolv.conf 配置检查,发现127.0.0.1不在配置中会自动添加;
限制 Agent 的 CPU 的使用,避免对业务进程造成影响。
具体的实践和落地
实践一:数据库域名改造
之前的数据库是用 IP 方式接入的,在数据库切换的时候,需要通知每个业务方修改配置,重启服务,这样就带来一个问题:整个过程是不可控的,取决于业务方的响应速度,生效时间通常超过十分钟。

虎牙在全球 DNS 秒级生效上的实践

提升数据库切换的关键点,第一个就是切换时不需要业务方参与,能在业务方无感知的情况下进行切换;第二个是实例变化能秒级推送到我们的应用,将应用快速切换到一个新的实例上。

虎牙在全球 DNS 秒级生效上的实践

大家可以看一下这个图,这是我们现在做的一个改造,图中的 DMX 是虎牙内部的一个数据库管理系统,思路就是把 DMX 和名字服务打通。DMX 会把数据库实例信息以服务的形式注册到名字服务,服务名就是域名。

实际应用过程中,通过这个域名去访问数据库,应用在访问前首先会经过 DNS - F 去做域名的解析,解析的时候是从名字服务查询实例信息,然后把实例的IP返回给应用。这样,应用就能通过 IP 和我们的数据库实例进行连接。

切换的时候,在 DMX 平台修改域名对应的实例信息,并把变更推送到名字服务,名字服务再推送给 DNS-F,应用在下一次解析的时候就能拿到新的实例 IP,达到切换数据库实例的目的。

这套方案落地后,虎牙的数据库切换基本上在10秒钟之内能够完成。

实践二:内部调用使用内部域名
虎牙部分内部系统之间调用是通过7层负载均衡,但是由于没有内部 DNS,需要通过的公共的 LocalDNS 来解析,这就带来一些问题:

问题一:扩缩容的时候要去修改 DNS 记录,整个过程生效时间可能会超过10分钟,故障的节点会影响业务较长的时间。

问题二:公共的 LocalDNS 智能解析不准确,比如无锡的机器可能会解析到深圳的一个接入点,影响接入质量。

问题三:不支持定制化的负载均衡策略,例如同机房、同大区优先的策略,通过公共 LocalDNS 是实现不了的。

如果想要提升内部服务调用质量,一是 DNS 记录变更绕过 LocalDNS,把 DNS 的记录变更直接推到 DNS-F。二是与内部系统打通,从 CMDB 等内部系统获取机器信息,支持多种负载均衡策略。

虎牙在全球 DNS 秒级生效上的实践

大家可以看一下上面的图,这个改造和数据库域名的改造思路是一样的,最右上角有一个7层负载管理系统,我们把这个系统和名字服务打通,7层负载管理系统会把域名信息以服务形式注册到名字服务,变更域名记录时直接从7层负载管理系统推送到名字服务,名字服务再推送到 DNS-F,达到快速切换的目的。

如果域名配置了负载均衡策略,名字服务会从 CMDB 获取机器、机房等信息,打标到域名的实例信息。然后,DNS-F 查询名字服务时,会携带 ClientIp,名字服务根据 ClientIp 的CMDB 信息过滤实例列表,返回同机房的实例给 DNS-F,达到同机房优先的目的。

由此带来的效果是:

第一,服务扩缩容能够秒级完成,减少了故障时间。

第二,扩展了 DNS 的负载均衡策略,例如有些业务是需要在不同区域有不同的接入点的,而且不能跨区域调用,之前的 DNS 负载均衡策略是不能满足这个需求的,但在改造之后,我们能根据 CMDB 信息去做同区域调度的负载均衡策略。

第三,业务在接入内部域名之后,延迟会有明显的下降。上图显示的就是某个服务在接入到内部域名之后,延迟出现明显的下降。

另一个落地的效果就是我们对主机上的域名解析的优化。因为我们的 DNS - F 是部署在每台主机上的,然后提供一个缓存的功能。带来的效果就是:

平均解析延迟会从之前的200毫秒下降到现在的1毫秒;
虎牙在全球 DNS 秒级生效上的实践
缓存命中率会从之前的90%上升到99.8%,90%是用 CoreDNS 原生的那个 Cache,99.8%是在这个 Cache 的组件下做了优化之后的效果;
解析失败率是从之前的0.1%下降到0%;
这里再总结一下项目落地的技术价值:

第一,提供了基于 DNS 服务发现的能力,消除异构系统之间互相调用的障碍。

第二,填补了没有内部域名解析能力的空白。

第三,解决我们上面说的内部服务调用面临的挑战:延时大、解析不准、不支持多种负载均衡策略、故障牵引慢。

第四,优化外部域名的解析,屏蔽 LocalDNS 的故障。

落地规模是:DNS - F 覆盖率100%,完成 Taf 和 Eureka 注册中心的数据同步。

后续规划
LocalDNS:

解决公共 DNS 节点位置影响域名解析准确性的问题;

解决内部使用公共 DNS 不稳定的问题;

优化内外网解析;

精准调度:

解决全球 DNS 节点生效慢的问题。

本文作者:

周健:GitHub ID @nanamikon,虎牙中间件团队成员,2012年毕业于中山大学,主要负责名字和配置服务,以及虎牙 DNS 和微服务相关的工作。

李志鹏:GitHub ID @lzp0412,虎牙中间件团队成员,主要负责 DNS,以及服务注册与发现、服务治理、Service Mesh 等相关工作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章