从架构风格的角度,重新理解 DNS 的设计
一场复古式的互联网应用设计之旅

缘起
从架构风格的角度,重新理解 DNS 的设计 因为想重建我的个人网站,就顺便也复习了一些相关知识,于是最近一个月就想着把用浏览器上网的过程中涉及到的一些技术写一写。之前用三篇文章把 DNS 的基础知识大概都说完了,还有一些不常用的就没写,我觉得我干互联网十几年了都没用过的,应该也不太重要吧。
最后留了个尾巴,就是要讲讲 DNS 相关的架构风格。“架构风格”一词是来自于 Fielding 博士的一篇论文《架构风格与基于网络应用软件的架构设计》,讲的是 REST 架构风格。前两天我也把好多年前写的关于 REST 架构风格的文章发了出来,有兴趣的朋友可以看看。
所以我今天所说的“架构风格”是来自于这篇论文的定义,而不是其他场景下泛泛而谈的风格。之所以如此,是因为这篇论文中所讨论的架构风格是有其目的性的,它可以帮助我们了解一个互联网应用的设计过程。
目标
之前也说过了,在网络中想找到一个节点,就要知道它的 IP,但 IP 并不容易识别和记忆,于是人们就想用一串便于识别记忆的字符串来替代 IP,于是有了主机名机制。但随着网络规模的扩大,主机名机制渐渐不足以满足人们对网络的需求,便出现了 DNS。
那么我们来看看 DNS 是用来解决什么问题的。首先,最基本的就是提供一个查询功能,可以根据域名查到 IP,这是最核心的功能需求。不过我们今天讨论的架构风格与功能需求无关,主要关注的是性能需求。
因为网络规模在不断扩大,不仅仅是局域网的规模变大,甚至已经出现了互联网,未来的网络规模是不可预测的。所以 DNS 必须能够满足无限网络,无限域名,无限 IP 的需求。
也恰恰因为网络的不可预测性,就必须得保证系统足够健壮,不能因为个别网络节点不可用,或者个别线路联通性不好,就导致整个系统异常。
显然,我们需要 DNS 在满足基本的功能需求外,还要满足一些性能需求。最明显的就是要有良好的可进化性、可移植性、可靠性,还要保证性能和效率等。这些性能要求,就是 Fielding 博士在他的论文中所说的架构属性。
架构风格
而架构风格,就是用来满足架构属性的,也就是用来满足这些性能需求的。依据 Fielding 博士的理论体系,DNS 对应的架构风格为:分层-客户端-缓存-无状态-服务器。
但从字面意思看,在如今的互联网应用中,这几种架构风格已经是习以为常的了,以至于很多人可能都意识不到它们的存在,也不太会去想它们当初被设计出来究竟是为了解决什么问题的。
当年学计算机基础课时,讲冯诺伊曼结构,讲运算器、控制器、存储器、输入设备和输出设备,我就很不理解,这有什么可讲的,计算机不就是这样吗,没有存储,没有输入输出那计算机还怎么用。
过了好多年我才想明白,是有了冯诺伊曼结构,才有了我用的计算机。我没见过其他结构的计算机,所以才觉得一切都是理所当然的。
之前我说 DNS 有五种架构风格,但并不是分层-客户端-缓存-无状态-服务器这字面意义上的五个词,它是一种组合式的架构风格,包括它自己在内,一共涉及到五种架构风格:
- 客户端-服务器
- 分层-客户端-服务器
- 客户端-无状态-服务器
- 客户端-缓存-无状态-服务器
- 分层-客户端-缓存-无状态-服务器
客户端-服务器 和 分层-客户端-服务器
如今的互联网应用几乎都是分层-客户端-服务器风格了。
客户端-服务端的目的是分离关注点,客户端可以只负责用户界面的逻辑,只需在用户使用时运行,负责发起请求,等待响应。而服务端则负责核心业务功能,并持续运行提供服务,等待请求,随时处理。对于 DNS 来说,客户端甚至都没有所谓的用户界面,使得功能及其简单。
分层系统使得应用可以被拆分为多个有单调依赖关系的解偶模块,改善了可进化性、可重用性、可伸缩性等。例如在家庭宽带中,我们只关注运营商提供的 DNS 服务器,所有的域名解析记录都是从它那获取的,至于它是怎么递归查询的,我们根本不关心。于是用户可以自主选择适合自己的 DNS 服务,而 DNS 层面的更新改造也不会影响到用户端使用。
客户端-无状态-服务器
所谓的“状态”是指由服务端保存的客户端的会话或上下文信息,我一直觉得无状态这个词不是那么直观,但也不知道还有什么更好的说法。
通俗的解释就是,每个客户端发起的请求都应该是相互独立的,不能依赖其他请求。不过这样一来,每个请求也就必须包含请求相关的全部信息了。
保持了无状态,那么每个请求的数据自身就能说明它在做什么,也就是改善了可见性。如果有请求异常,那么重试一次就好,很容易从故障中恢复,也就是改善了可靠性。服务端也无需在多个请求间维护共享的数据,这样增加与释放资源也变得简单了,也就是改善了可伸缩性。
当然,这样做也会有些负面影响,因为每个请求都要包含全部数据,那必然会降低网络性能。还有前面说的分层系统,也是如此,分了多层也必然会增加整个请求所消耗的时间。但也正是因为有了这两个风格,才使得可以添加一个提升用户可感知性能的大杀器——缓存。
客户端-缓存-无状态-服务器
虽然分层和无状态使得每个请求的耗时都增加了,但只要加上缓存,就能大大缩短请求链路,甚至本地缓存命中时都无需请求,于是整体上又大大提升了用户可感知的性能。
在 DNS 中,每条解析记录都包含一个 TTL,用来指示这条解析记录可以缓存多长时间。也就是在此时间之内,无需重新请求,复用之前得到的结果就行。于是,在有限的时间内,相同域名的请求无论并发有多高,实际发出的请求数都只是一个常数了。
回看主机名机制,其实它类似于是一个超大的本地缓存,只不过它的更新机制并不灵活,是定时全量更新。而 DNS 是每条解析记录都有自己的过期时间,实现了按需更新,大大节省了网络消耗。
当然,有缓存就有数据更新不及时的问题。当我们配置了新的域名解析后,因为缓存的存在,尤其是一个分布式系统中,每个节点,每一层都有缓存,想要完全更新,总是需要等待一段时间。甚至有个别节点总是不更新,也是经常遇到的。
像个别小运营商,为了提升可用性和效率,会无视权威 TTL,而是按自己的规则来更新数据,若是真赶上了,就只能多等等,或者联系他们手动刷新。
当然,如果个别节点出现了故障,过期的缓存也不是不能用,对于域名解析这种修改频率较低的内容,用过期的也比没有强,所以整个系统也变得更可靠了。
分层-客户端-缓存-无状态-服务器
将以上的架构风格都组合起来,就是最终 DNS 的架构风格了。它像大多数互联网应用一样,使用了从客户端到服务器的分层系统,改善了可伸缩性、简单性、可进化性、可复用性、可移植性。通过无状态风格来改善可见性、可靠性、可伸缩性等。通过缓存风格来改善性能、效率、可靠性等。
通过这些架构风格,基本上满足了一开始对 DNS 系统的性能要求。当然,这些论述主要针对的还是早期的 DNS 系统。经过几十年的发展,如今的 DNS 系统在安全、隐私、可扩展等方面已经有了很大的改进。
而这些改进能够顺利完成,其实也得益于 DNS 早期可扩展,可进化的设计。对部分节点升级后,如何让新节点与老节点兼容,如何让老节点可以忽略新节点的变化,这都离不开早期设计。
总结
今天讨论的架构风格,来自几十年前,属于比较古早的一套理论框架了。跟今天的互联网应用相比,它们已经成为了基础设施一般的存在。
像我们用 Spring 全家桶来开发项目时,那些已经在多数开发者中形成共识的代码模版,各种规约范式,早已经涵盖了多数的基础架构风格了,这可能让开发人员觉得那不过仅仅是一个功能特性而已。
只有在项目长期反复迭代,经历了业务量持续地增长,应对了一波又一波的流量高峰后,你或许才会意识到原来缓存是需要这么来设计的,原来无状态的特性是可以那样被利用的。
浏览器是如何上网的,DNS 的部分就说到这了,后面该说 HTTP 协议了,还没想好怎么写,我还是希望先写一个科普的版本,让不懂技术的人也能看懂,然后再一点点深入,可能还需要点时间。