陈硕的 Blog

吾尝终日而思矣,不如须臾之所学也。吾尝跂而望矣,不如登高之博见也。……君子生非异也,善假于物也。

C1000k 新思路:用户态 TCP/IP 协议栈

现在的服务器支撑上百万个并发 TCP 连接已经不是新闻(余锋2010年的演讲ideawuiComet 开源项目WhatsApp 做到了 2.5M)。实现 C1000k 的常规做法是调整内核参数,提高文件数,降低每个连接的内存消耗(参考 ideawu 的博客)。

在今年的 BSDCan2014 会议上, Patrick Kelsey 介绍了把 FreeBSD 9.x 的 TCP/IP 协议栈移植到了用户态(slides, github.com/pkelsey/libuinet),并用于 WANProxy 项目。在用户态运行 TCP/IP 协议栈意味着并发 TCP 连接不再占用系统文件数,只占内存,解决了 C1000k 的一大瓶颈,内核只要提供一个收发网络 packet 的接口就行(例如 netmap)。

内核的网络协议栈强调通用性,主要是为吞吐量优化(性能指标通常是 MB/s 或 packets per second),顺带兼顾大量并发连接。为了支持 C1000k,要调整内核参数让每个连接少占资源,这与内核代码的设计初衷是违背的。

用户态协议栈捅破了这层窗户纸,可以根据应用的特点来剪裁协议栈功能。优化也更直接,不再是调黑盒参数组合,而是直接上 profiling,根据结果修改应用程序和协议栈的代码。

用户态协议栈的吞吐量比不上内核,不过对 C1000k 的应用场合(例如 comet)应该不成问题。

muduo 的 C1000k 实验

我用 muduo 做了一次 C1000k 的实验,用的是传统方案,没有用 libuinet。在一台 16GB 内存的 Dell WS490 旧工作站上创建了 50万个 TCP 连接,提供 echo 服务。系统可用内存减少了 5286MiB,即每个连接 10.8KiB(其中服务进程占用了 1421MiB 内存,即每个连接 2.9KiB,其余 8KiB 左右是内核协议栈的开销)。客户端是一台 8GB 内存的 i5-2500,内存消耗也是 5GB 多,因此这次实验只试到了 C500k。客户机绑定了 10 个 IP,每个 IP 上发出 5 万 TCP 连接,运行 pingpong 协议,每个连接轮流收发 64 字节的消息。测得 QPS 大约是 11k,服务器的 CPU 占用率约为 60%(单线程)。profile 显示 CPU 的主要开销在内核中,我对这个结果基本满意。

复活 4.4BSD-Lite2 的网络协议栈

受 libuinet 启发,我把 4.4BSD-Lite2 的网络协议栈也移植到了 Linux 用户态(github.com/chenshuo/4.4BSD-Lite2),方便《TCP/IP 详解 第2卷》的读者跟踪调试其代码。以下是 Eclipse CDT 单步跟踪的截图。

eclipse

也可以用各种现成的工具来分析函数的调用关系:

bsd44

我在《谈一谈网络编程学习经验》中说这本书的“代码只能看,不能上机运行,也不能改动试验”如今不再成立了。

我在《关于 TCP 并发连接的几个思考题与试验》中用 TAP/TUN 作为自己写的协议栈的对外接口,对 4.4BSD-Lite2 也可如法炮制,让 20 年前的 TCP/IP 协议栈与现在的机器通信。除了与本机通信,还可以通过 NAT 转发,让 4.4BSD-Lite2 连上现在的 Internet。(sudo iptables -t nat -A PREROUTING -p tcp --dport 2009 -i eth0 -j DNAT --to 192.168.0.2:2009

posted on 2014-05-20 14:12  陈硕  阅读(9085)  评论(3编辑  收藏  举报

导航