Urban Airship 公司的工程师们做到了50w的并发,最近他们发表了一篇文章介绍针对linux 内核的调优经验,下面是我对文章的一些翻译,方便我以后参考,我的英语不太好,有些地方翻译并不准确,如果高手看到请鄙视我,:),当然不当之处还请多多指教,可以帮助我更快进步。
原文地址http://blog.urbanairship.com/blog/2010/09/29/linux-kernel-tuning-for-c500k/
-----------------------
注:本文章中提到的"并发",和 The C10k problem中提到的是一样的意思,指客户端并发(或者称为sockets)。
最近,我们发表了一篇关于在Urban Airship实现了超过50w并发socket链接的文章,达到这个目标是非常困难和艰巨的,所以我们打算把在测试中遇到的经验教训分享给大家。
虽然这篇指南涉及到的是linux和亚玛逊的EC2(非EC2-centric),但是这些方法可以应用于任何linux平台。
对我们来说,使每台服务器能达到尽可能高的socket链接才是有价值的。与其用100台1w并发链接的服务器,我们更倾向于使用2台50w并发的服务器。为了实现这个目标,我们使socket服务器更像socket服务器(使之性能达到极致),任何客户端和服务器的请求都是通过一个线程控制的队列,更少的socket服务意味着更少的代码、cpu使用率和内存使用率。
为了达到上述目标,我们必须考虑linux内核的优化,很多配置参数需要调整。但是首先,我们考虑一个问题。
内核 OOM LOWMEM
我们首先在Ubuntu 64-bit、6GB 内存的本地机器上测试我们的代码,用几个彼此之间通过桥接网络适配器相连的ubuntu虚拟机链接,这样我们可以提高连接数。启动服务端和客户端后我们看看能达到多少的链接数。我们看到我们的java服务器轻松的达到512000个并发。
下一步是在EC2上测试。刚开始我们想看下能从1.7GB 32-bit虚拟机上得到什么样的效果,同样的我们启动了一些EC2客户端。我们看到随着并发数的提高,随机的有些服务器宕掉了,没有输出任何异常,他们被杀掉了。
重新测试,结果还是一样的。
通过syslog,我们发现下面一行:
Out of Memory: Killed process 2178 java
OOM-killer干掉了java进程,奇怪的是当时我们还有500M的内存空闲。
下次测试的时候,我们观察了/proc/meminfo中的内容,我们看到LowFree这一项持续下降,LOWMEM显示有可用空间,LOWMEM是用于内核数据(如socket缓存)的内核寻址内存空间。
当我们增加socket链接时,每个socket的缓存增加了LOWMEM的使用量,当LOWMEM满了之后,内核发现是某进程在搞鬼,然后干掉了它。
标准的EC2的配置中LOWMEM大约是717MB,其余的分配给了用户。但是反过来说,内核是可以聪明的为用户重新分配LOWMEM的。假设内核使用很少的内存,用户会疯掉的。我们希望的
是内核能够使用到它需要的内存,我们的java服务很少用到几百MB。
(要想深入了解这一方面,请参考 High Memory In The Linux Kernel)
一个32位系统的内核寻址内存空间是4GB。确保为内核留有适当的空间非常重要。但是64-bit (x86-64) Linux的内核寻址内存空间是64TB。在当前计算环境下,这意味着差不多是
无限的,你没有必要再去看/proc/meminfo中的lowmem指标了。
所以我们启用了一些EC2 Large instances(每个是7.5GB的 64-bit系统),再次测试我们的代码,这次没有了任何惊讶,并发数一直在无忧的增加,内核使用了它所需要的一切内存。
也就是说,在32-bit平台上你只能达到一定的并发数。
内核参数
一些和socket相关的参数可以调整优化。文件/etc/sysctl.conf中,我们调整了几个参数。
第一个是fs.file-max,最大描述文件数限制。默认值比较低,所以可以调整。警惕调高后的风险。
第二个是socket缓存参数net.ipv4.tcp_rmem和net.ipv4.tcp_wmem。他们分别是读写缓存限制。他们都需要三个整数参数配置:min、default、max。他们都对应到每个socket缓
存的字节数。设置一个宽松的最大值来减少每个socket链接所带来的内存开销。
我们这三个参数的配置是这样的:
fs.file-max = 999999
net.ipv4.tcp_rmem = 4096 4096 16777216
net.ipv4.tcp_wmem = 4096 4096 16777216
内核允许999,999个文件描述,每个socket可以用到最低和默认的4096字节、最高16MB的缓存。
我们同样修改文件/etc/security/limits.conf允许所有用户打开999,999个文件描述。
#<domain> <type> <item> <value>
* - nofile 999999
你可以通过manpage了解到更多细节。
测试
测试时,通过在客户端和服务端增加临时端口的数量,我们可以获得每个客户端64,000个链接。
echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range
这样使上述每个临时端口有1024个被使用,而不是默认值(默认值比较低,而且比较保守)。
64K并发量的传说
"单个IP最大并发量是64,000个,解决的唯一方法就是增加IP数",这是一个误解,而且绝对是错误的。
这个说法的前提是每个IP只有一定的临时端口可供使用。真实的情况是"IP对"是限制因素,或者换句话说,是客户端和服务端的IP。一个客户端IP可以对一个服务端IP64,000个次
连接。
如果这个传说是真实的,这将是一个重要的但是很简单的DDoS方法。
后记
当我们实现一个服务器50w的并发的时候,没有很好的文档记录。当然,我们知道1w个并发已经相当困难,但是比它多一个(甚至几个)数量级的情况呢?
幸运的是我们没遇到太多麻烦就成功的实现了,希望我们的方法能够为面对同样问题的人带去方便。