今天发布了 COW 0.8 版,这个版本重要的改动是在不同的客户端连接之间共享 web 服务器连接。对 Firefox, Safari 或者是在局域网内服务多个用户的情况下这个改动应该能够提高 COW 的网络性能。
首先介绍一下背景。(如果你不熟悉 HTTP 协议,推荐 HTTP Made Really Easy,这是我见过最简洁清晰而且较为全面的 HTTP 协议介绍。)HTTP 1.1 定义了 persistent connection,允许用一个连接完成多个 HTTP 请求。重用单个连接完成多个请求的主要好处有两个:
- 减少建立 TCP 连接的开销,从而减小延迟
- 更好的利用网络带宽
- 新建立的 TCP 连接并不会全速发送数据包,而会通过 slow start 算法慢慢涨上去
COW 处理并发 client 连接采用的方式是每个连接由一个 goroutine 处理,称之为 client goroutine。由于 Go 能利用多核,为避免在 client goroutine 之间共享数据引入同步开销,以前的设计是每个 client goroutine 创建的 web 服务器连接是私有的。这个设计的缺点是减少了重用服务器连接的机会:
- client 关闭连接时,对应的 client goroutine 结束执行,同时会关闭所有服务器连接
- 新创建的 client goroutine 再次处理针对这些服务器的请求时不得不建立新的连接
- 浏览器会与代理服务器创建多个连接,每个连接都会进行对不同网站的请求
- 考虑下面的场景:
- 假定浏览器跟代理有两个连接 A, B,分别正在处理对 taobao.com, 和 tbcdn.cn 的请求
- 连接 B 先处理完 tbcdn.cn 的请求,浏览器马上通过 B 发送 taobao.com 的请求
- 连接 A 处理完 taobao.com 的请求,浏览器再通过 A 发送 tbcdn.cn 的请求
- 第二次 tbcdn.cn 的请求在服务器连接私有的设计下无法得到重用
- 考虑下面的场景:
在实现 COW 之前我没什么网络编程的经验,当时没有意识到建立连接的开销,slow start 我也压根就没想到。当时错误得以为避免同步开销的好处更大,所以采用了服务器连接私有的设计。
发布 0.8 之前,为了解决 taobao 首页加载缓慢的问题添加了统计连接情况的代码,然后发现不同浏览器在创建和使用代理服务器连接上的行为非常不同。(之前我在推上发布过一些数据,不过很抱歉,最开始的数据有误,而且当时没注意到更有意思的现象。)Chrome, Firefox, Safari 通过 COW 打开 taobao 首页的 log 我放在了 gist 上,有兴趣的同学可以自己观察一下按 client connection 分组的日志 (文件名带 grouped 后缀)。
打开 taobao 首页需要连接的 host 大概在 20 个,各个浏览器使用代理服务器连接的特点如下:
- Chrome
- 每个代理服务器连接只处理一个 host 的请求
- 与代理服务器建立的最大并发连接数 34 个
- 这对服务器连接私有的设计来说是 best case,既避免了同步开销,同时又重用了连接
- Firefox
- 每个连接会处理不同 host 的请求
- 最大并发连接数 32 个
- 这个是 worst case,产生非常多的 idle connection,导致 open fd 增加,大量连接无法重用(注意
grep 'close idle connections' firefox
的输出,跟 Chrome 做对比)
- Safari
- 每个连接会处理不同 host 的请求
- 最大并发连接数 8 个
- 受私有连接的影响介于 Chrome 和 Firefox 之间
在观察到这个现象之后我决定修改 COW,实现共享连接的功能。选择私有连接还是共享连接,其实是在同步开销和重用连接之间进行取舍,回头想想其实一开始就应该实现共享连接。TCP 建立连接需要两个 RTT,建立之后还得 slow start,时间开销在几十几百毫秒的量级;同步的开销用 pthread mutex 无竞争的情况下完成一次拿锁解锁操作耗时也就 20 多纳秒,我最终实现的 connection pool,用了 mutex, map, channel,时间开销跟毫秒应该还差好几个量级,而目前 COW 也没有使用很多核的必要,所以即使共享也不会出现严重的竞争。
从去年八月份写下 COW 的第一行代码到现在已经快一年了,其实在第一次发布以前就一直吃自己的狗粮。目前的工作方式已经让我自己感到满意,搭配 shadowsocks 自己觉得还是挺方便的。我自己能发现的 bug 都已经解决,剩下的只能依靠其他用户来发现了。遗憾的是代码不够整洁,很多时候看 Go 标准库时会自叹设计能力太差,偶尔会直接抄一些代码用到 COW 中。
接下去的打算是继续修 bug,重构代码。很早之前就有考虑过开发 Mac 的 GUI,不过这方面的经验基本为 0,而且时间不允许。如果有同学有兴趣做的话欢迎跟我联系。