单台服务器百万并发长连接支持

内容拷贝from:

http://www.linuxde.net/2013/08/15150.html

http://www.csdn.net/article/2013-05-16/2815317-The-Secret-to-10M-Concurrent-Connections

http://www.blogjava.net/yongboy/archive/2013/04/11/397677.html


淘宝技术分享 HTTP长连接200万尝试及调优

  •  2013/08/26
  •  HTTP
  •  应用加速与性能调优
  •  3
  •  13,428

对于一个server,我们一般考虑他所能支撑的qps,但有那么一种应用, 我们需要关注的是它能支撑的连接数个数,而并非qps,当然qps也是我们需要考虑的性能点之一。这种应用常见于消息推送系统,也称为comet应用,比 如聊天室或即时消息推送系统等。comet应用具体可见我之前的介绍,在此不多讲。对于这类系统,因为很多消息需要到产生时才推送给客户端,所以当没有消 息产生时,就需要hold住客户端的连接,这样,当有大量的客户端时,就需要hold住大量的连接,这种连接我们称为长连接。

首先,我们分析一下,对于这类服务,需消耗的系统资源有:cpu、网络、内存。所以,想让系统性能达到最佳,我们先找到系统的瓶颈所在。这样的长连 接,往往我们是没有数据发送的,所以也可以看作为非活动连接。对于系统来说,这种非活动连接,并不占用cpu与网络资源,而仅仅占用系统的内存而已。所 以,我们假想,只要系统内存足够,系统就能够支持我们想达到的连接数,那么事实是否真的如此?如果真能这样,内核来维护这相当大的数据结构,也是一种考 验。

要完成测试,我们需要有一个服务端,还有大量的客户端。所以需要服务端程序与客户端程序。为达到目标,我的想法是这样的:客户端产生一个连接,向服务端发起一个请求,服务端hold住该连接,而不返回数据。

1. 服务端的准备

对于服务端,由于之前的假想,我们需要一台大内存的服务器,用于部署Nginx的comet应用。下面是我用的服务端的情况:

Summary:        Dell R710, 2 x Xeon E5520 2.27GHz, 23.5GB / 24GB 1333MHz  
System:         Dell PowerEdge R710 (Dell 0VWN1R)  
Processors:     2 x Xeon E5520 2.27GHz 5860MHz FSB (16 cores)  
Memory:         23.5GB / 24GB 1333MHz == 6 x 4GB, 12 x empty  
Disk-Control:   megaraid_sas0: Dell/LSILogic PERC 6/i, Package 6.2.0-0013, FW 1.22.02-0612,  
Network:        eth0 (bnx2):Broadcom NetXtreme II BCM5709 Gigabit Ethernet,1000Mb/s  
OS:             RHEL Server 5.4 (Tikanga), Linux 2.6.18-164.el5 x86_64, 64-bit  

服务端程序很简单,基于nginx写的一个comet模块,该模块接受用户的请求,然后保持用户的连接,而不返回。Nginx的status模块,可直接用于监控最大连接数。

服务端还需要调整一下系统的参数,在/etc/sysctl.conf中:

net.core.somaxconn = 2048  
net.core.rmem_default = 262144  
net.core.wmem_default = 262144  
net.core.rmem_max = 16777216  
net.core.wmem_max = 16777216  
net.ipv4.tcp_rmem = 4096 4096 16777216  
net.ipv4.tcp_wmem = 4096 4096 16777216  
net.ipv4.tcp_mem = 786432 2097152 3145728  
net.ipv4.tcp_max_syn_backlog = 16384  
net.core.netdev_max_backlog = 20000  
net.ipv4.tcp_fin_timeout = 15  
net.ipv4.tcp_max_syn_backlog = 16384  
net.ipv4.tcp_tw_reuse = 1  
net.ipv4.tcp_tw_recycle = 1  
net.ipv4.tcp_max_orphans = 131072  

/sbin/sysctl -p 生效

这里,我们主要看这几项:

net.ipv4.tcp_rmem用来配置读缓冲的大小,三个值,第一个是这个读缓冲的最小值,第三个是最大值,中间的是默认值。我们可以在程序中修改读缓冲的大小,但是不能超过最小与最大。为了使每个socket所使用的内存数最小,我这里设置默认值为4096。

net.ipv4.tcp_wmem用来配置写缓冲的大小。

读缓冲与写缓冲在大小,直接影响到socket在内核中内存的占用。

而net.ipv4.tcp_mem则是配置tcp的内存大小,其单位是页,而不是字节。当超过第二个值时,TCP进入 pressure模式,此时TCP尝试稳定其内存的使用,当小于第一个值时,就退出pressure模式。当内存占用超过第三个值时,TCP就拒绝分配 socket了,查看dmesg,会打出很多的日志“TCP: too many of orphaned sockets”。

另外net.ipv4.tcp_max_orphans这个值也要设置一下,这个值表示系统所能处理不属于任何进程的 socket数量,当我们需要快速建立大量连接时,就需要关注下这个值了。当不属于任何进程的socket的数量大于这个值时,dmesg就会看 到”too many of orphaned sockets”。

另外,服务端需要打开大量的文件描述符,比如200万个,但我们设置最大文件描述符限制时,会遇到一些问题,我们在后面详细讲解。

2. 客户端的准备

由于我们需要构建大量的客户端,而我们知道,在一台系统上,连接到一个服务时的本地端口是有限的。由于端口是16位整数,也就只能是0到 65535,而0到1023是预留端口,所以能分配的只是1024到65534,也就是64511个。也就是说,一台机器只能创建六万多个长连接。要达到 我们的两百万连接,需要大概34台客户端。

当然,我们可以采用虚拟ip的方式来实现这么多客户端,如果是虚拟ip,则每个ip可以绑定六万多个端口,34个虚拟ip就可以搞定。而我这里呢,正好申请到了公司的资源,所以就采用实体机来做了。

由于系统默认参数,自动分配的端口数有限,是从32768到61000,所以我们需要更改客户端/etc/sysctl.conf的参数:

net.ipv4.ip_local_port_range = 1024 65535  

/sbin/sysctl -p 

客户端程序是基于libevent写的一个测试程序,不断的建立新的连接请求。

3. 由于客户端与服务端需要建立大量的socket,所以我们需要调速一下最大文件描述符。

客户端,需要创建六万多个socket,我设置最大为十万好了,的在/etc/security/limits.conf中添加:

admin    soft    nofile  100000  
admin    hard    nofile  100000  

服务端,需要创建200万连接,那我想设置nofile为200万,好,问题来了。

当我设置nofile为200万时,系统直接无法登陆了。尝试几次,发现最大只能设置到100万。在查过源码后,才知道,原来在2.6.25内核之前有个 宏定义,定义了这个值的最大值,为1024*1024,正好是100万,而在2.6.25内核及其之后,这个值是可以通过/proc/sys/fs /nr_open来设置。于是我升级内核到2.6.32。

升级内核后,继续我们的调优,如下:

sudo bash -c 'echo 2000000 > /proc/sys/fs/nr_open' 

现在再设置nofile就可以了:

admin    soft    nofile  2000000  
admin    hard    nofile  2000000 

最后,在测试的过程中,根据dmesg的系统打出的信息不断调整服务端/sbin/sysctl中的配置,最后我们的测试完成了200万的长连接。

为了使内存占用尽量减少,我将Nginx的request_pool_size从默认的4k改成1k了。另外,net.ipv4.tcp_wmem与net.ipv4.tcp_rmem中的默认值也设置成4k。

两百万连接时,通过nginx的监控得到数据:

单台服务器百万并发长连接支持_第1张图片

两百万连接时系统内存情况:
单台服务器百万并发长连接支持_第2张图片

千万级并发实现的秘密:内核不是解决方案,而是问题所在!

发表于 2013-05-16 14:4140689次阅读| 来源 HighScalability101 条评论| 作者 Todd Hoff
内核 Linux C10K Errata Security Unix Apache
allowtransparency="true" frameborder="0" scrolling="no" src="http://hits.sinajs.cn/A1/weiboshare.html?url=http%3A%2F%2Fwww.csdn.net%2Farticle%2F2013-05-16%2F2815317-The-Secret-to-10M-Concurrent-Connections&type=3&count=&appkey=&title=C10K%E9%97%AE%E9%A2%98%E8%AE%A9%E6%88%91%E4%BB%AC%E6%84%8F%E8%AF%86%E5%88%B0%EF%BC%9A%E5%BD%93%E5%B9%B6%E5%8F%91%E8%BF%9E%E6%8E%A5%E8%BE%BE%E5%88%B010K%E6%97%B6%EF%BC%8C%E9%80%89%E6%8B%A9%E4%B8%8D%E5%90%8C%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%EF%BC%8C%E7%AC%94%E8%AE%B0%E6%9C%AC%E6%80%A7%E8%83%BD%E5%8F%AF%E8%83%BD%E4%BC%9A%E8%B6%85%E8%BF%8716%E6%A0%B8%E6%9C%8D%E5%8A%A1%E5%99%A8%E3%80%82%E5%AF%B9%E4%BA%8EC10K%E9%97%AE%E9%A2%98%EF%BC%8C%E6%88%91%E4%BB%AC%E6%88%96%E7%BB%95%E8%BF%87%EF%BC%8C%E6%88%96%E5%85%8B%E6%9C%8D%EF%BC%9B%E7%84%B6%E8%80%8C%E9%9A%8F%E7%9D%80%E5%B9%B6%E5%8F%91%E9%80%90%E6%B8%90%E5%A2%9E%E5%A4%9A%EF%BC%8C%E5%9C%A8%E8%BF%99%E4%B8%AA%E5%90%8E10K%E7%9A%84%E6%97%B6%E4%BB%A3%E9%87%8C%EF%BC%8C%E4%BD%A0%E6%98%AF%E5%90%A6%E6%9C%89%E6%83%B3%E8%BF%87%E5%A6%82%E4%BD%95%E5%8E%BB%E5%85%8B%E6%9C%8DC10M%E3%80%82&pic=&ralateUid=&language=zh_cn&rnd=1468821081405" width="22" height="16"> 摘要:C10K问题让我们意识到:当并发连接达到10K时,选择不同的解决方案,笔记本性能可能会超过16核服务器。对于C10K问题,我们或绕过,或克服;然而随着并发逐渐增多,在这个后10K的时代里,你是否有想过如何去克服C10M。

既然我们已经解决了 C10K并发连接问题,应该如何提高水平支持千万级并发连接?你可能会说不可能。不,现在系统已经在用你可能不熟悉甚至激进的方式支持千万级别的并发连接。 

要知道它是如何做到的,我们首先要了解Errata Security的CEO Robert Graham,以及他在Shmoocon 2013大会上的“无稽之谈”—— C10M Defending The Internet At Scale。 

Robert用一种我以前从未听说的方式来很巧妙地解释了这个问题。他首先介绍了一点有关Unix的历史,Unix的设计初衷并不是一般的服务器操作系统,而是电话网络的控制系统。由于是实际传送数据的电话网络,所以在控制层和数据层之间有明确的界限。问题是我们现在根本不应该使用Unix服务器作为数据层的一部分。正如设计只运行一个应用程序的服务器内核,肯定和设计多用户的服务器内核是不同的。

也就是他所说的——关键要理解内核不是解决办法,内核是问题所在。 

这意味着: 

  • 不要让内核执行所有繁重的任务。将数据包处理,内存管理,处理器调度等任务从内核转移到应用程序高效地完成。让Linux只处理控制层,数据层完全交给应用程序来处理。

最终就是要设计这样一个系统,该系统可以处理千万级别的并发连接,它在200个时钟周期内处理数据包,在14万个时钟周期内处理应用程序逻辑。由于一次主存储器访问就要花费300个时钟周期,所以这是最大限度的减少代码和缓存丢失的关键。 

面向数据层的系统可以每秒处理1千万个数据包,面向控制层的系统,每秒只能处理1百万个数据包。

这似乎很极端,请记住一句老话:可扩展性是专业化的。为了做好一些事情,你不能把性能问题外包给操作系统来解决,你必须自己做。 
现在,让我们学习Robert如何创建一个能够处理千万级别并发连接的系统。 

C10K问题——最近十年  

十年前,工程师处理C10K可扩展性问题时,尽量避免服务器处理超过1万个的并发连接。通过改进操作系统内核以及用事件驱动服务器(如Nginx和Node)代替线程服务器(Apache),这个问题已经被解决。人们用十年的时间从Apache转移到可扩展服务器,在近几年,可扩展服务器的采用率增长得更快了。

Apache的问题 

  • Apache的问题在于服务器的性能会随着连接数的增多而变差
  • 关键点:性能和可扩展性并不是一回事。当人们谈论规模时,他们往往是在谈论性能,但是规模和性能是不同的,比如Apache。
  • 持续几秒的短期连接,比如快速事务,如果每秒处理1000个事务,只有约1000个并发连接到服务器。
  • 事务延长到10秒,要维持每秒1000个事务,必须打开1万个并发连接。这种情况下:尽管你不顾DoS攻击,Apache也会性能陡降;同时大量的下载操作也会使Apache崩溃。
  • 如果每秒处理的连接从5千增加到1万,你会怎么做?比方说,你升级硬件并且提高处理器速度到原来的2倍。发生了什么?你得到两倍的性能,但你没有得到两倍的处理规模。每秒处理的连接可能只达到了6000。你继续提高速度,情况也没有改善。甚至16倍的性能时,仍然不能处理1万个并发连接。所以说性能和可扩展性是不一样的。
  • 问题在于Apache会创建一个CGI进程,然后关闭,这个步骤并没有扩展。
  • 为什么呢?内核使用的O(N^2)算法使服务器无法处理1万个并发连接。
  • 内核中的两个基本问题:
  • 连接数=线程数/进程数。当一个数据包进来,内核会遍历其所有进程以决定由哪个进程来处理这个数据包。
  • 连接数=选择数/轮询次数(单线程)。同样的可扩展性问题,每个包都要走一遭列表上所有的socket。
  • 解决方法:改进内核使其在常数时间内查找。
  • 使线程切换时间与线程数量无关。
  • 使用一个新的可扩展epoll()/IOCompletionPort常数时间去做socket查询。
  • 因为线程调度并没有得到扩展,所以服务器大规模对socket使用epoll方法,这样就导致需要使用异步编程模式,而这些编程模式正是Nginx和Node类型服务器具有的;所以当从Apache迁移到Nginx和Node类型服务器时,即使在一个配置较低的服务器上增加连接数,性能也不会突降;所以在10K连接时,一台笔记本电脑的速度甚至超过了16核的服务器。

C10M问题——未来十年

不远的将来,服务器将要处理数百万的并发连接。IPv6协议下,每个服务器的潜在连接数都是数以百万级的,所以处理规模需要升级。

  • 如IDS / IPS这类应用程序需要支持这种规模,因为它们连接到一个服务器骨干网。其他例子:DNS根服务器,TOR节点,互联网Nmap,视频流,银行,Carrier NAT,VoIP PBX,负载均衡器,网页缓存,防火墙,电子邮件接收,垃圾邮件过滤。
  • 通常人们将互联网规模问题归根于应用程序而不是服务器,因为他们卖的是硬件+软件。你买设备,并将其应用到你的数据中心。这些设备可能包含一块Intel主板或网络处理器以及用来加密和检测数据包的专用芯片等。
  • 截至2013年2月,40gpbs, 32-cores, 256gigs RAM的X86服务器在Newegg网站上的报价是5000美元。该服务器可以处理1万个以上的并发连接,如果它们不能,那是因为你选择了错误的软件,而不是底层硬件的问题。这个硬件可以很容易地扩展到1千万个并发连接。 

10M的并发连接挑战意味着什么: 

  1. 1千万的并发连接数 
  2. 100万个连接/秒——每个连接以这个速率持续约10秒 
  3. 10GB/秒的连接——快速连接到互联网。 
  4. 1千万个数据包/秒——据估计目前的服务器每秒处理50K的数据包,以后会更多。过去服务器每秒可以处理100K的中断,并且每一个数据包都产生中断。 
  5. 10微秒的延迟——可扩展服务器也许可以处理这个规模,但延迟可能会飙升。 
  6. 10微秒的抖动——限制最大延迟 
  7. 并发10核技术——软件应支持更多核的服务器。通常情况下,软件能轻松扩展到四核。服务器可以扩展到更多核,因此需要重写软件,以支持更多核的服务器。 

我们所学的是Unix而不是网络编程 

  • 很多程序员通过W. Richard Stevens所著的《Unix网络编程》学习网络编程技术。问题是,这本书是关于Unix的,而不只是网络编程。它告诉你,让Unix做所有繁重的工作,你只需要在Unix的上层写一个小服务器。但内核规模不够,解决的办法是尽可能将业务移动到内核之外,并且自己处理所有繁重的业务。
  • 这方面有影响的一个例子是Apache每个连接线程的模型。这意味着线程调度程序根据将要到来的数据确定接下来调用哪一个read()函数,也就是把线程调度系统当作数据包调度系统来用。(我真的很喜欢这一点,从来没有想过这样的说法)。
  • Nginx宣称,它不把线程调度当作数据包调度程序,而是自己进行数据包调度。使用select找到socket,我们知道数据来了,就可以立即读取并处理数据,数据也不会堵塞。
  • 经验:让Unix处理网络堆栈,但之后的业务由你来处理。

怎样编写规模较大的软件?

如何改变你的软件,使其规模化?许多只提升硬件性能去支撑项目扩展的经验都是错误的,我们需要知道性能的实际情况。 

要达到到更高的水平,需要解决的问题如下: 

  1. 数据包的可扩展性 
  2. 多核的可扩展性 
  3. 内存的可扩展性 

实现数据包可扩展——编写自己的个性化驱动来绕过堆栈 

  • 数据包的问题是它们需经Unix内核的处理。网络堆栈复杂缓慢,数据包最好直接到达应用程序,而非经过操作系统处理之后。
  • 做到这一点的方法是编写自己的驱动程序。所有驱动程序将数据包直接发送到应用程序,而不是通过堆栈。你可以找到这种驱动程序:PF_RING,NETMAP,Intel DPDK(数据层开发套件)。Intel不是开源的,但有很多相关的技术支持。
  • 速度有多快?Intel的基准是在一个相当轻量级的服务器上,每秒处理8000万个数据包(每个数据包200个时钟周期)。这也是通过用户模式。将数据包向上传递,使用用户模式,处理完毕后再返回。Linux每秒处理的数据包个数不超过百万个,将UDP数据包提高到用户模式,再次出去。客户驱动程序和Linux的性能比是80:1。
  • 对于每秒1000万个数据包的目标,如果200个时钟周期被用来获取数据包,将留下1400个时钟周期实现类似DNS / IDS的功能。
  • 通过PF_RING得到的是原始数据包,所以你必须做你的TCP堆栈。人们所做的是用户模式栈。Intel有现成的可扩展TCP堆栈

多核的可扩展性 

多核可扩展性不同于多线程可扩展性。我们都熟知这个理念:处理器的速度并没有变快,我们只是靠增加数量来达到目的。 
大多数的代码都未实现4核以上的并行。当我们添加更多内核时,下降的不仅仅是性能等级,处理速度可能也会变得越来越慢,这是软件的问题。我们希望软件的提高速度同内核的增加接近线性正相关。 
多线程编程不同于多核编程

  • 多线程
  • 每个CPU内核中不止一个线程
  • 用锁来协调线程(通过系统调用)
  • 每个线程有不同的任务
  • 多核
  • 每个CPU内核中只有一个线程
  • 当两个线程/内核访问同一个数据时,不能停下来互相等待
  • 同一个任务的不同线程
  • 要解决的问题是怎样将一个应用程序分布到多个内核中去
  • Unix中的锁在内核实现。4内核使用锁的情况是大多数软件开始等待其他线程解锁。因此,增加内核所获得的收益远远低于等待中的性能损耗。
  • 我们需要这样一个架构,它更像高速公路而不是红绿灯控制的十字路口,无需等待,每个人都以自己的节奏行进,尽可能节省开销。
  • 解决方案:
  • 在每个核心中保存数据结构,然后聚合的对数据进行读取。
  • 原子性。CPU支持可以通过C语言调用的指令,保证原子性,避免冲突发生。开销很大,所以不要处处使用。
  • 无锁的数据结构。线程无需等待即可访问,在不同的架构下都是复杂的工作,请不要自己做。
  • 线程模型,即流水线与工作线程模型。这不只是同步的问题,而是你的线程如何架构。
  • 处理器关联。告诉操作系统优先使用前两个内核,然后设置线程运行在哪一个内核上,你也可以通过中断到达这个目的。所以,CPU由你来控制而不是Linux。

内存的可扩展性 

  • 如果你有20G的RAM,假设每次连接占用2K的内存,如果你还有20M的三级缓存,缓存中会没有数据。数据转移到主存中处理花费300个时钟周期,此时CPU没有做任何事情。
  • 每个数据包要有1400个时钟周期(DNS / IDS的功能)和200个时钟周期(获取数据包)的开销,每个数据包我们只有4个高速缓存缺失,这是一个问题。
  • 联合定位数据
  • 不要通过指针在满内存乱放数据。每次你跟踪一个指针,都会是一个高速缓存缺失:[hash pointer] -> [Task Control Block] -> [Socket] -> [App],这是四个高速缓存缺失。
  • 保持所有的数据在一个内存块:[TCB |socket| APP]。给所有块预分配内存,将高速缓存缺失从4减少到1。
  • 分页
  • 32GB的数据需占用64MB的分页表,不适合都存储在高速缓存。所以存在两个高速缓存缺失——分页表和它所指向的数据。这是开发可扩展的软件不能忽略的细节。
  • 解决方案:压缩数据,使用有很多内存访问的高速缓存架构,而不是二叉搜索树
  • NUMA架构加倍了主存访问时间。内存可能不在本地socket,而是另一个socket上。
  • 内存池
  • 启动时立即预先分配所有的内存
  • 在对象,线程和socket的基础上进行分配。
  • 超线程
  • 每个网络处理器最多可以运行4个线程,英特尔只能运行2个。
  • 在适当的情况下,我们还需要掩盖延时,比如内存访问中一个线程在等待另一个全速的线程。
  • 大内存页
  • 减小页表规模。从一开始就预留内存,让你的应用程序管理内存。

总结

  • 网卡
  • 问题:通过内核工作效率不高
  • 解决方案:使用自己的驱动程序并管理它们,使适配器远离操作系统。
  • CPU
  • 问题:使用传统的内核方法来协调你的应用程序是行不通的。
  • 解决方案:Linux管理前两个CPU,你的应用程序管理其余的CPU。中断只发生在你允许的CPU上。
  • 内存
  • 问题:内存需要特别关注,以求高效。
  • 解决方案:在系统启动时就分配大部分内存给你管理的大内存页

控制层交给Linux,应用程序管理数据。应用程序与内核之间没有交互,没有线程调度,没有系统调用,没有中断,什么都没有。 
然而,你有的是在Linux上运行的代码,你可以正常调试,这不是某种怪异的硬件系统,需要特定的工程师。你需要定制的硬件在数据层提升性能,但是必须是在你熟悉的编程和开发环境上进行。 

原文连接:The Secret To 10 Million Concurrent Connections -The Kernel Is The Problem, Not The Solution (文/周小璐,审校/仲浩)

100万并发连接服务器笔记之1M并发连接目标达成

第四个遇到的问题:tcp_mem

在服务端,连接达到一定数量,诸如50W时,有些隐藏很深的问题,就不断的抛出来。 通过查看dmesg命令查看,发现大量TCP: too many of orphaned sockets错误,也很正常,下面到了需要调整tcp socket参数的时候了。

第一个需要调整的是tcp_rmem,即TCP读取缓冲区,单位为字节,查看默认值

cat /proc/sys/net/ipv4/tcp_rmem
4096 87380 4161536

默认值为87380 byte ≈ 86K,最小为4096 byte=4K,最大值为4064K。

第二个需要调整的是tcp_wmem,发送缓冲区,单位是字节,默认值

cat /proc/sys/net/ipv4/tcp_wmem
4096 16384 4161536

解释同上

第三个需要调整的tcp_mem,调整TCP的内存大小,其单位是页,1页等于4096字节。系统默认值:

cat /proc/sys/net/ipv4/tcp_mem
932448 1243264 1864896

tcp_mem(3个INTEGER变量):low, pressure, high

  • low:当TCP使用了低于该值的内存页面数时,TCP不会考虑释放内存。
  • pressure:当TCP使用了超过该值的内存页面数量时,TCP试图稳定其内存使用,进入pressure模式,当内存消耗低于low值时则退出pressure状态。
  • high:允许所有tcp sockets用于排队缓冲数据报的页面量,当内存占用超过此值,系统拒绝分配socket,后台日志输出“TCP: too many of orphaned sockets”。

一般情况下这些值是在系统启动时根据系统内存数量计算得到的。 根据当前tcp_mem最大内存页面数是1864896,当内存为(1864896*4)/1024K=7284.75M时,系统将无法为新的socket连接分配内存,即TCP连接将被拒绝。

实际测试环境中,据观察大概在99万个连接左右的时候(零头不算),进程被杀死,触发out of socket memory错误(dmesg命令查看获得)。每一个连接大致占用7.5K内存(下面给出计算方式),大致可算的此时内存占用情况(990000 * 7.5 / 1024K = 7251M)。

这样和tcp_mem最大页面值数量比较吻合,因此此值也需要修改。

三个TCP调整语句为:

echo "net.ipv4.tcp_mem = 786432 2097152 3145728">> /etc/sysctl.conf
echo "net.ipv4.tcp_rmem = 4096 4096 16777216">> /etc/sysctl.conf
echo "net.ipv4.tcp_wmem = 4096 4096 16777216">> /etc/sysctl.conf

备注: 为了节省内存,设置tcp读、写缓冲区都为4K大小,tcp_mem三个值分别为3G 8G 16G,tcp_rmemtcp_wmem最大值也是16G。

目标达成

经过若干次的尝试,最终达到目标,1024000个持久连接。1024000数字是怎么得来的呢,两台物理机器各自发出64000个请求,两个配置为6G左右的centos测试端机器(绑定7个桥接或NAT连接)各自发出640007 = 448000。也就是 1024000 = (64000) + (64000) + (640007) + (64000*7), 共使用了16个网卡(物理网卡+虚拟网卡)。 
终端输出

......
online user 1023990
online user 1023991
online user 1023992
online user 1023993
online user 1023994
online user 1023995
online user 1023996
online user 1023997
online user 1023998
online user 1023999
online user 1024000

在线用户目标达到1024000个!

服务器状态信息

服务启动时内存占用:

                 total       used       free     shared    buffers     cached     Mem:         10442        271      10171          0         22         78     -/+ buffers/cache:        171      10271     Swap:         8127          0       8127

系统达到1024000个连接后的内存情况(执行三次 free -m 命令,获取三次结果):

                 total       used       free     shared    buffers     cached     Mem:         10442       7781       2661          0         22         78     -/+ buffers/cache:       7680       2762     Swap:         8127          0       8127                  total       used       free     shared    buffers     cached     Mem:         10442       7793       2649          0         22         78     -/+ buffers/cache:       7692       2750     Swap:         8127          0       8127                  total       used       free     shared    buffers     cached     Mem:         10442       7804       2638          0         22         79     -/+ buffers/cache:       7702       2740     Swap:         8127          0       8127

这三次内存使用分别是7680,7692,7702,这次不取平均值,取一个中等偏上的值,定为7701M。那么程序接收1024000个连接,共消耗了 7701M-171M = 7530M内存, 7530M*1024K / 1024000 = 7.53K, 每一个连接消耗内存在为7.5K左右,这和在连接达到512000时所计算较为吻合。 
虚拟机运行Centos内存占用,不太稳定,但一般相差不大,以上数值,仅供参考。

执行top -p 某刻输出信息:

    top - 17:23:17 up 18 min,  4 users,  load average: 0.33, 0.12, 0.11
    Tasks:   1 total,   1 running,   0 sleeping,   0 stopped,   0 zombie     Cpu(s):  0.2%us,  6.3%sy,  0.0%ni, 80.2%id,  0.0%wa,  4.5%hi,  8.8%si,  0.0%st     Mem:  10693580k total,  6479980k used,  4213600k free,    22916k buffers     Swap:  8323056k total,        0k used,  8323056k free,    80360k cached       PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                            2924 yongboy   20   0 82776  74m  508 R 51.3  0.7   3:53.95 server 

执行vmstate:

vmstat
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
 r b swpd free buff cache si so bi bo in cs us sy id wa st
 0 0 0 2725572 23008 80360 0 0 21 2 1012 894 0 9 89 2 0 

获取当前socket连接状态统计信息:

cat /proc/net/sockstat
sockets: used 1024380
TCP: inuse 1024009 orphan 0 tw 0 alloc 1024014 mem 2
UDP: inuse 11 mem 1
UDPLITE: inuse 0
RAW: inuse 0
FRAG: inuse 0 memory 0

获取当前系统打开的文件句柄:

sysctl -a | grep file
fs.file-nr = 1025216 0 1048576
fs.file-max = 1048576

此时任何类似于下面查询操作都是一个慢,等待若干时间还不见得执行完毕。

netstat -nat|grep -i "8000"|grep ESTABLISHED|wc -l 
netstat -n | grep -i "8000" | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

以上两个命令在二三十分钟过去了,还未执行完毕,只好停止。

小结

本次从头到尾的测试,所需要有的linux系统需要调整的参数也就是那么几个,汇总一下:

    echo "* - nofile 1048576" >> /etc/security/limits.conf     echo "fs.file-max = 1048576" >> /etc/sysctl.conf     echo "net.ipv4.ip_local_port_range = 1024 65535" >> /etc/sysctl.conf     echo "net.ipv4.tcp_mem = 786432 2097152 3145728" >> /etc/sysctl.conf     echo "net.ipv4.tcp_rmem = 4096 4096 16777216" >> /etc/sysctl.conf     echo "net.ipv4.tcp_wmem = 4096 4096 16777216" >> /etc/sysctl.conf

其它没有调整的参数,仅仅因为它们暂时对本次测试没有带来什么影响,实际环境中需要结合需要调整类似于SO_KEEPALIVE、tcpmax_orphans等大量参数。

本文代表一次实践,不足之处,欢迎批评指正。

你可能感兴趣的:(并发处理)