技术开发 频道

ContainerDNS性能优化17W到1000W QPS实践

  【IT168 技术】

  一、 引入

  随着TIG阿基米德平台全面应用。组成京东容器生态技术栈的分布式域名解析服务ContainerDNS(go版https://github.com/tiglabs/containerdns )全量生产环境应用,承载着每天百亿的访问量,单实例峰值每秒请求达到15W QPS,已经接近ContainerDNS的性能极限(17W QPS)。为了更好的提高系统的并发服务,对ContainerDNS 的优化也势在必行。

  本文对ContainerDNS性能优化思考和技术实践历程,希望对业内在容器领域和域名解析方向技术实践一些启迪。

  ContainerDNS 的DNS Server 代码用Go 语言实现,我们在后期的优化中,通过各种尝试,并通过pprof 采集数据分析,发现性能损耗最大的是收发包的地方。同时我们对bind9 进行了压测、采集分析发现同样是收发包函数最为耗时。

ContainerDNS性能优化17W到1000W QPS实践

  数据平面开发套件DPDK(Data Plane Development Kit)可以较高提升数据处理性能和吞吐量,提高数据平面应用程序的工作效率。灵感来自TIG容器生态技术栈另外一个重要的服务高性能负载均衡服务Jupiter (https://github.com/tiglabs/jupiter )实现单实例200万QPS的高性能。因此TIG团队工程师想到用DPDK 来实现DNS数据报文的收发是一个有挑战和前景的尝试。

  二、系统概述

  让我们先来回顾下ContainerDNS设计之初的愿景和初心。ContainerDNS作为京东商城新一代软件定义数据中心的关键基础服务之一,具有以下特点:

  * 分布式设计 多数据中心数据同步复制 避免维护多个域名解析元数据副本

  * 支持自动发现服务域名 支持容器Service

  * 支持后端探活

  * 易于维护、易于动态扩展

ContainerDNS性能优化17W到1000W QPS实践
▲图一 ContainerDNS 架构图

  ContainerDNS 包括四大组件 DNS server、service to DNS 、user API 、IP status check。这四个组件通过etcd 数据库集群结合在一起,彼此独立,降低了耦合性,每个模块可以单独部署。DNS server 用于提供DNS 查询服务的主体。service to DNS 组件是k8s 集群与DNS server的中间环节,会实时监控k8s集群的服务的创建,将服务转化为域名信息,存入etcd 数据库中。user API 组件提供restful api,用户可以创建自己的域名信息,数据同样保持到etcd数据库中。IP status check 模块用于对系统中域名所对应的ip做探活处理,数据状态也会存入到etcd数据库中。如果某一个域名对应的某一个ip地址不能对外提供服务,DNS server 会在查询这个域名的时候,将这个不能提供服务的ip地址自动过滤掉。

ContainerDNS性能优化17W到1000W QPS实践

  KDNS(DPDK DNS 简称) 不是ContainerDNS 的否定而是其延续,我们只是替换了DNS server模块,采用C语言,并且是基于DPDK全新实现。ContainerDNS探活模块、API 模块、k8s监控模块都是可以复用的,对于工程来说具有很好的延续性。如上图所示KDNS Server进程部署物理机上,同时部署quagga 进程,对外发布一个VIP。这样可以实现KDNS Server的多活,同时Client 端经过路由器访问DNS Server 的VIP,路由器对BGP 协议的支持,不同的访问可能会访问到不同的KDNS Server 上,可以实现一定的负载均衡。这样设计方便后期的升级扩展,关闭quagga 程序数据就不会打到对应的KDNS Server,然后版本升级,开启quagga发布VIP,新的Server 对外提供服务。可以很方便在服务不中断的条件下进行KDNS Server版本的动态回滚、升级。同时可以方便的监控KDNS,如果不能提供服务可以将此物理机上的quagga杀死,这样这台服务器就不对外提供服务。这种设计方式的实现可以很方便的实现热升级、高可用。下面将主要介绍DNS server 基于DPDK的实现。

  三、 KDNS系统设计与实现

  DNS server 是提供DNS的主体模块实现架构如下图:

ContainerDNS性能优化17W到1000W QPS实践

  如上图所示,系统中用到的CPU核有两种角色:主核和从核。主核相当于控制通道,域名数据、交给协议栈的数据都是主核处理。从核主要处理数据通道的数据,通过DPDK网口收包,报文解析处理,解析结果发包给用户。

  Data Plane Development Kit(DPDK):是运行在Linux用户态,实现X86通用平台网络报文快速处理的库和驱动的集合,其主要特点:

  * 多核编程框架及CPU亲和性

  * 巨页(HugePage),减少页表项数目,降低TLB miss

  * 无锁队列

  * UIO支持,用户态驱动,减少报文copy

  * poll-mode网卡模式,无中断轮询收包

  为了充分利用DPDK的特性,我们根据功能将KDNS 划分为:数据收发模块、协议解析处理模块、转发报文处理模块、域名信息数据处理模块、ARP/BGP协议报文处理模块等。数据收包、协议解析都是跑在从核中,转发报文处理模块、域名信息数据处理模块为单独的处理线程处理,ARP/BGP协议等协议的支持由主核通过DPDK的KNI 接口发送给内核协议栈,发送数据由主核发出。每个包数据、域名数据在各个模块中的交互,通过DPDK的无锁队列实现。并且对外提供RESTful API 接口,用户可以对域名数据的更新。为了结合ContainerDNS其他组件的使用,我们这里开发了DNS-agent 进程,此进程go 语言实现会监控etcd的数据变化,实时将域名信息通过RESTful API 接口更新到KDNS Server 中。当然KDNS完全可以脱离ContainerDNS的生态环境运行,只要通过RESTful API 接口就可以更新域名数据,这样KDNS 就可以正常工作起来。

  下面是主核的处理流程:

ContainerDNS性能优化17W到1000W QPS实践

  首先创建和Restful API 数据交互的domain-msg-ring,域名变更处理线程检测到域名变更数据,会将数据放入domain-msg-ring中,主核后面会进入一个死循环,首选处理域名变更,并将变更的数据分发到所有的从核,从核同样在循环处理的时候回首先更新域名数据到本地缓存中。然后主核会检测是否有从核发过来的数据(例如ARP 请求),主核将数据转给DPDK 的KNI 接口交由Linux协议栈处理。然后主核调用接口从KNI收取数据,如果有协议栈发过来的数据,调用DPDK的发包函数rte_eth_tx_burst 将数据发送出去。然后主核判断是否有转发域名数据,如果有同样调用rte_eth_tx_burst 将转发域名请求的结果发送出去。这里主核只负责发包,转发查询上级DNS Server 全部由后台的转发处理线程处理,不会影响主核的处理速度。

  从核的处理逻辑较为简单,首先调用处理主核发过来的域名数据,从而及时改变自己的域名数据缓存信息。然后调用DPDK收包函数rte_eth_rx_burst 收取报文,如果有数据则进行数据包解析处理,参考下面的协议解析处理模块。如果是本地zone 的域名查询,将查询的结果直接发送给客户端。如果是ARP请求,则将数据包放入kni-pkt-ring 中交由主核处理。如果是转发域名,则交由转发报文处理线程处理。

  数据收发模块:采用DPDK的收发包接口,开DPDK启RSS (Receive Side Scaling,多 CPU之间高效分发的网卡驱动技术),由于DNS 访问基本是UDP包端口是53,RSS采用了对IP和端口进行Hash的方式,当客户端很多的时候可以有效地实现多核的均衡。为了更好地提高性能,系统中每一个从核只处理一个收包队列上面的数据。发包也是只发送对应队列上的数据。所以收发包数据间,所有的核都是独立的,没有任何耦合不需要任何锁机制,更加快速。

  协议解析处理模块:每一个从核通过DPDK 接口收到数据包,进行数据包解析。并将结果返回给客户端。流程图如下:

ContainerDNS性能优化17W到1000W QPS实践

  转发报文处理模块:系统所有的域名数据都是基于某一个或者多个zone进行的,如果域名不在本地支持的zone内就要将请求发送给上级的DNS Server,比如我们的DNS Server 支持本地的zone是 tst.local,也就是说所有访问*.tst.local的域名本地的DNS Server 都会查询解析,如果用户访问的不是*.tst.local的域名就得转发给上级的DNS 进行解析,这个转发的处理流程较慢,我们采用了一组后台的线程进行处理,这些线程不会绑定到CPU上,完全与主核、从核的功能分离开来,这样主、从核都不会处理这样的慢速数据请求。

ContainerDNS性能优化17W到1000W QPS实践

  首先数据处理从核收到数据包,发现如果请求的域名不是本地的zone配置的域名后缀,走慢速流程即转发流程。从核会将本请求入放在和转发处理线程共享的数据队列中,这样做的好处是把从核解放出来,只处理快速的查询请求,不会block住,从而能全速的处理本地数据。转发报文处理线程是一个死循环,首先从队列中读取数据,没有数据则休眠。如果有数据则将预处理数据并调用转发接口转发给上级DNS Server,并将上级DNS Server的回包处理后放入与主核共享的rte_mbuf无锁队列中。主核会及时的出队转发报文处理线程放入的数据,调用DPDK的网卡发送接口,将数据从主核的发送队列发送出去。需要说明的是这期间数据都是公用一个rte_mbuf 内存,没有任何的数据复制的过程,从而更好地提高性能。

  域名信息数据处理模块: 这个是一个后台的线程提供Restful API,可以和agent进行数据交互,从而获取系统的域名数据。

ContainerDNS性能优化17W到1000W QPS实践

  首先注册支持url和方法,目前支持GET、POST、DELETE分别对应着域名的查询、增加、删除三个接口。同时提供状态的查询接口,目前只有两种状态Init和Runing,当agent 检测到进程是刚启动(Init 状态)时,会将所有的域名信息下发到DPDK DNS Server,之后将DPDK DNS Server的状态设置为Runing状态。后面如果有域名信息变更,agent 会调用POST或者DELETE 接口将域名数据同步到DNS Server。为了提高数据的安全性,DNS Server的API支持ssl证书,这样可以有效的防止域名数据被恶意的窃取、修改。同时域名数据插入删除操作的时候,采用了Hash 表的设计,每一个域名先计算出Hash值,如果发生冲突先比较Hash值,如果相同再进行字符串匹配。由于Hash值远远大于Hash桶的长度,当发生Hash冲突的时候先匹配Hash值会增加大大提高匹配的效率。

  ARP/BGP报文处理:这模块较为简单,从核解析数据包如果发现是ARP协议报文,将数据传送给主核,主核在将数据通过DPDK的KNI将数据报文发给Linux 协议栈,主核后面再通过KNI 读取Linux协议栈处理的结果,然后调用DPDK的发送接口,将数据发送出去。BGP 协议的处理模式类似,也是交与Linux 内核协议栈处理。

  四、性能优化及测试

  1)恰当地使用rte_prefetch0(),可以减少cache-miss次数

  2)likely()和unlikely()的使用,可以减少分支预测失败的次数

  3)数据处理全程无锁,交互部分利用无锁队列实现

  4)整个处理过程无包copy,发送包复用收包的mbuf内存,减少mbuf的申请

  性能测试:

  1) 测试环境

  CPU:Intel(R) Xeon(R) CPU E5-2698 v4 @ 2.20GHz

  NIC:intel 82599ES 10-Gigabit SFI/SFP+ Network Connection

  2) 测试配置

  KDNS(八个数据核一个控制核),bind9 (16个核,版本bind-9.10.6-P1)

  3) 测试结果

  bind9 五分钟单域名测试结果(47W QPS):

ContainerDNS性能优化17W到1000W QPS实践

  上图是bind9 稳定运行5分钟的数据采集结果,平均47W QPS。

  KDNS 20分钟单个域名测试结果(1000W QPS):

ContainerDNS性能优化17W到1000W QPS实践

  如上图所示,优化后的KDNS性能达到1000万QPS,而且二十分钟运行很平稳,并发性能是bind9 20多倍、ContainerDNS(go版)的50倍。

  KDNS 多个域名测试结果,其中系统域名记录大概20亿 条。

ContainerDNS性能优化17W到1000W QPS实践

  相同网络环境下响应时间对比(queryperf 测试):

ContainerDNS性能优化17W到1000W QPS实践

  如上图所示,可以看到在相同的测试服务器、客户端主机、网络的环境下测试,利用DPDK收发包实现的DNS,对于访问的响应更加优秀和稳定,最慢响应由bind9的1140微妙提高到226微妙,平均响应时间也提高了一倍。

  五、总结

  本文主要介绍了KDNS,一种基于Intel DPDK平台开发的DNS Server。其接近网卡线速处理能力,灵活的部署,多样的监控以及可靠的稳定性,同时兼顾ContainerDNS的所有的分布式模块功能。作为TIG阿基米德平台京东数据中心操作系统(JDOS)的一个重要的组成部分,在京东数据中心发挥至关重要的作用。

0
相关文章