skoo's notes

努力记录一些自己觉得有趣的东西...
09 March 2014
by skoo

最近两个月一直在做阿里CDN的DNS调度器重构开发,其实就是重写,谈不上是重构,所以到头来还是没有积累上正确重构的经验。

DNS调度器的开发让我第一次深入研究了DNS服务器以及DNS协议内容,DNS协议属于二进制协议,设计得比较简单,但也有一些明显的不足之处,所以DNS才搞出了opt additional rr进行协议上的扩展以期满足更加丰富的需求。RFC规定了DNS请求可以走UDP,也可以走TCP,所以一个DNS服务器应该同时支持这两种传输层;由于TCP服务器在实现的时候需要严格考虑TCP的字节流传输机制,所以在编程上面比UDP复杂很多,DNS规范为了让DNS的TCP服务器开发更加的简单,于是就给协议数据包加上了2个字节的长度头部,有了长度头就可以严格检测服务器是否收全了一个完整的DNS请求,只有在收全的时候才开始做DNS协议解析,从而避免流式的解析协议。其实,对于DNS来说,这两个字节的长度头是完全多余的,没有长度头通过流式的解析协议也可以正确无误的解析出一个DNS请求,只是对编程的要求更高了而已。这次自己对DNS协议实现了一遍,更加的让自己对协议设计有了深刻的认识,一个好的协议首先是满足当前的业务需求;其次协议是易于扩展的(对于扩展性这一条来说,DNS设计得并不算好,opt additional rr在语意上来说真是一个很鸡肋的设计,但除此之外又没别的办法了。),协议应该是高效的(比如:HTTP可以全流式解析;DNS进行了域名压缩,可以减少传输的数据量;SPDY进行了连接复用,支持并发请求等等),高效的协议也许我们还应该关注一点的就是可缓存,缓存就是一张“狗皮膏药”,哪里不行贴哪里,哈哈。最后,协议设计非常重要的一点就是易于实现,一个好的协议应该实现是简单的,大多数情况简单就代表不容易出错,简单就代表好升级,简单就代表易于扩展等等。设计出好的协议的前提还是得对服务器开发有着深刻的理解和丰富的经验才行。

这次也碰到了如何实现一个高效的UDP服务器的问题,我们都知道UDP没有连接机制,于是服务器只有一个socket fd用来收发UDP数据包,现在是多核时代,服务器都是2,30个cpu,用这么多的线程去操作一个共享的socket,也很难完全将服务器的资源全部利用起来。所以google为了解决他们的DNS服务器性能问题,就搞出了REUSEPORT这个内核patch,它已经出现在了3.9的内核中。当然reuseport对UDP/TCP都有效的,但个人觉得对于长连接的TCP服务器来说效果不见得明显,但是对于UDP和短连接为主的tcp来说性能应该是有质的提高才对,回头我将详细的写篇文章介绍reuseport的测试情况。

再提一下Google的edns0-client-subnet协议,这货就是Google用来扩展DNS协议,觉得DNS协议不够用,不够用主要还是体现在CDN的DNS调度器这种场景下。client-subnet的目的就是让一个DNS查询请求携带上客户端IP,以供CDN做出精确调度使用。Google是一家喜欢制定标准的公司,它有魄力和野心去定义互联网。

重写完DNS调度器的时候,发现自己在C程序设计上面,深受了Nginx的影响了。在没有接触Nginx之前,对模块化开发只能说是非常初级的地步,甚至是根本不懂什么是C程序的模块化开发。C语言是一门语法上太简单的语言,没有太多的工具给你使用,因此C语言最难的地方应该就是如何用有限的工具堆砌出一个优雅的程序。用C语言设计出高可扩展的程序不是一件易事,这次调度器的开发加深了自己对模块化设计的掌握。编程真的需要有一点追求,才能迫使自己做出更好的设计,本次开发中为了调整一个模块的代码风格,一直搞到凌晨3点才完事,可能你觉得我是在自找苦吃,其实我真的是在享受这个过程,哈哈。很多人都认为编程语言不重要,但编程语言真的决定了你的思考方式。我喜欢C和Go语言,你呢?

最后,我想说一下代码风格,我坚定的认为一个项目、一份代码、一个程序应该采用统一的风格,不管是多少个人一起完成的,大家都应该严格的遵守。程序员真的是一个非常怪异的群体,每个人都有自己的风格或者说是性格,不喜欢受规矩束缚,更不喜欢被人指指点点,不管多么的不喜欢,我还是坚定的认为一起做一件事情遵循同一个规范是一件很重要的事情。目前正努力学习让自己可以随时融入到任何一种规范、做事风格之中。