dns的递归解析过程还是挺繁琐的,要知道一个域名可能有cname、ns 而请求的cname、ns可能还有cname、ns,如果按照线性的处理每个请求那逻辑就变成毛线团了
dnspod的处理还是挺巧妙的,通过一个公共的数据集dataset将所有域名对应的a、cname、ns等类型的数据作为单独的条目存入,当有需要某个域名的信息时先去dataset找,找不到在加入qlist请求根,有专门的线程不间断的将qlist轮询dataset找(这里只要次数允许,没得到想要的结果就轮询所有qlist到dataset找虽然可以简化逻辑分离的彻底但是会是个性能瓶颈,后面有方案)当根返回以后只是简单的将记录(通常是一个域名的cname、ns或者a)存入dataset(而不是继续流程,因为根据这个返回是cname还是ns或者a处理不同逻辑复杂,而这样处理对于用到相同域名的请求还有优化作用),剩下的工作交给那边不间断轮询的线程
Dnspod主要由3个run(若干个线程)组成
run_sentinel 监听53端口接收客户端请求,将请求放到队列中
run_fetcher 从队列中取出请求,根据qname取得最后一级cname,查看本地dataset 是否有记录,如果有则返回,没有则将该请求放入qlist中
run_quizzer
1.不间断的遍历qlist,只要状态为PROCESS_QUERY且dataset中没有的就向对应的根发送请求。
2.通过epoll等待根返回,解析返回的数据加入 dataset
3.检查记录的ttl,在将记录加入dataset时还会将这些记录以红黑树的形式组织起来,取得ttl最早到期的,将其放入qlist中等待刷新,注意这里不是删除,如果收不到不返回则该记录一直存在
关于dataset的实现
dataset是使用哈希表实现的,本质上是个二维数组,将域名哈希成一个值,模上数组的数量作为下标,找到对应的数组接着遍历查找,根据需要可以扩大数组的数量提升性能。
我们的优化手段
之前提到dnspod的qlist会不间断轮询,属于主动查询,对性能有不小的影响,这里我们采取的做法是被动(类似回调的方式),我们将请求的域名和类型分类,相同的放在一组,当dataset找不到向根发出请求后我们并不每次主动轮询,而是在等到应答后,触发该域名和类型的请求组,让他们根据自己的逻辑走下一步(一般是先找该域名的最后一级cname,根据这个cname查是否存在他的对应请求类型的记录,一般是a或者ns,如果没有,则找这个cname的ns)
以上可以看出dataset很重要,负载也不小,还经常需要并发访问,这里我们每次接收到根的回复后,除了将记录的答案加进dataset,还创建一个临时的dataset,只存该次回复的信息,在后面的流程会优先到这里去找,没有的再找dataset。