一台服务器并发TCP连接数可以有多少?
如何支持从硬件和操作系统上支持单台服务器支持上万并发,支持百万千万,甚至上亿的并发
著名的C10K并发连接问题是什么?C10M并发问题又是什么?
接下来我们逐一的介绍说明
在linux下编程,网络服务器程序我们都知道每一个TCP连接都要占用一个文件描述符,一旦这个文件描述符使用完了,新的连接到就返回错误“Socket/File:Can't open so many files”
也就是,我们需要明白操作系统对可以打开的最大文件数的限制
进程限制
执行命令 ulimit -n 输出256,说明对于一个进程最多打开256个文件,所以如果采用默认的配置最多也就是200多个并发,那么就要修改ulimit打开文件的限制,在/etc/rc.local文件最后,添加ulimt -SHn 100000,这样就支持一个进程打开10万个文件,理论并发是10万
全局限制
执行命令cat /proc/sys/fs/file-nr输出9344 0 592026分别为:
已经分配的文件句柄数,已经分配但是还没有使用的文件句柄,最大文件句柄数
我们可以把这个数值改大些,用 root 权限修改 /etc/sysctl.conf 文件:
端口范围限制
端口的范围0-65535,其中1024以下是系统使用的,从1024-65535是给用户使用的,由于一个TCP连接都要占用一个端口号,所以我们最多可有60000多个并发,其实这样理解的朋友应该不在少数,这是一个错误的理解
如何标识一个TCP连接
系统用一个4元数组来表示唯一的一个连接{local ip,local port,remote ip,remote port},作为服务器端只使用了bind时的这个端口,也就是说,不同的进程,不同的remote IP 都有不同的端口来对应
Server最大TCP连接数
server通常固定在某个本地端口上监听,等待client连接请求,即使sever端有很多的IP,本地监听端口也是独占的,因此server端tcp的四个元组只有remote ip 和 remote port 是可变的,因此最大的TCP连接为客户端的IP x 客户端的port数,最大的TCP连接数约为2^32次方(IP数)x 2^16次方(port数),也就是说server端单击最大tcp连接数约为2^48次方
结论
单个机器理论TCP并发连接数有这么多,但是实际上并发连接肯定受硬件资源(内存),网络资源(带宽)的限制,那么如何计算内存和带宽呢?
假设一个请求(UDP)大小按照548个字节计算,内存4G,带宽按照5M独立带宽计算,那么理论并发X为:
一个UDP的长度在Internet上大约是548个字节,理论长度65535个字节,我们计算暂时按照548个字节
x * 548 < 内存大小, 并且还得小于带宽大小
计算的结果:5M带宽,理论并发在9000左右
在目前大部分计算机的计算能力以及分布式架构来说,并发的最大瓶颈还是带宽的能力
在互联网web1.0的时代,互联网大部分使用场景下载一个html页面,用户在浏览器上查看网页信息,这个时期不存在C10K的问题
随着web2.0的时代到来,用户群体几何倍数增长,也不再是单纯的浏览网页,逐渐的开始进行交互,从简单的表达提交,到即时通讯和在线实时互动,因为每个用户都需要和服务器保持TCP连接才能进行数据实时交互,就像腾讯这样的网站同一个时间的并发TCP连接可能就过亿
早期的腾讯QQ采用了UDP的这种原始包交换协议来实现的,绕开了C10K的难题,当然了这个过程肯定是痛苦的,如果当时有epoll技术,他们肯定用TCP,众所周知,后台的手机QQ,微信都采用了TCP协议和UDP协议的配合
实际上,当时也有异步模式,如select/poll模型,这些技术都有一定的缺点,如select最大不能超过1024,poll没有限制,但是每次收到的数据都需要遍历每一个连接查看是哪个连接有数据请求
最初的服务器都是基于进程/线程模型的,新连接一个TCP连接,就需要分配一个1个进程,而进程又是操作系统最昂贵的资源,一台机器无法创建很多进程,如果是C10K就要创建1万个进程,那么对于单机操作系统而言是无法承受的(效率低下,甚至完全瘫痪),如果采用分布式系统,维持1亿用户在线需要10万台服务器成本巨大
那么如何突破单机性能的局限,这是网络编程最直接要面临的问题
C10K
在之前的旧服务器上基于select的程序处理1000个并发吞吐量,在2倍性能的机器上往往是处理不了2000个并发的,大量的操作消耗和当前的连接数N成线性关系,大量的操作和消耗和当前的连接数N成线性关系,会导致单个任务的资源消耗和当前的连接数的关系是0(n),而服务程序同时需要数万计的socket进行I/0处理,积累下的资源消耗会相对的可观,这显然会导致系统吞吐量不能和机器性能匹配
C10K最大的特点是,解决了其性能和连接及机器性能的关系是非线性的
一些没有太多大并发的实践经验的技术同行,所实现的诸如IM及时通信,所谓的理论负载动不动就号称单机支持上万,上十万甚至上百万的情况,是经不起考验的
C10K的本质问题
是操作系统的问题,传统的同步阻塞I/0模式都是一样的,处理的方式都是requests per second,并发10K和100的区别关键在于CPU
创建的进程线程多了,数据拷贝频繁(缓存I/O,内核将数据拷贝到用户进程空间,阻塞),进程/线程上下文切换消耗巨大,导致操作系统崩溃,这就是C10K的本质问题
解决C10K问题的关键就是:尽可能的减少这些CPU等核心的计算资源消耗,从而榨干单台服务器的性能,突破C10K的关键问题
C10K问题的解决方案
主要思路有2个
思路一:一个是对应每一个连接处理分配一个独立的进程/线程
思路二:另外一个思路是用同一个进程/线程同时处理若干个连接
思路一的扩展性差,占用资源过多,是最老的一种服务器连接处理的方式,我们暂时忽略,直接说第二种的思路
一个进程/线程处理多个连接(IO多路复用)
IO复用从技术实现上分很多种,接下来依次说明各个方式的实现和优劣:
截止目前,40gpbs、32-cores、256G RAM的X86服务器在Newegg网站上的报价是几千美元,实际上这样的配置,它完全可以处理1千万个以上的并发连接
在未来IPV6协议下,每个服务器的潜在连接数据都是百万级,单个服务器处理数百万的并发甚至上千万的并发
不要让OS内核执行所有的繁重任务,将数据包处理,内存管理,处理器调度等任务从内核转移到应用程序高效的完成,诸如让linux这样的OS只处理控制层,数据层完全交给应用程序来处理
面向数据层的系统可以每秒处理1千万个数据包,面向控制层的系统,每秒只能处理1百万个数据包,这虽然是很极端的,但是可扩展性是专业化,为了做好这些事情,不能把性能问题外部给操作系统来解决,而是你自己必须要做
回顾一下C10K问题
在之前,开发人员处理C10K的扩展性问题的时候,尽量避免了服务器处理超过1万的并发连接,改进操作系统内核及事件驱动服务器(用Nginx和Node)和代替了线程服务(Apache)
Apache的问题,在于服务器的性能会随着连接数增多而变差,,比如Apache持续几秒的短连接,快速的事物,如果每秒处理1000个事物,只能有约1000个并发连接到服务器,如果事务延长到了10秒,要维持每秒1000个事物则必须打开一个万个并发连接,这种情况下:尽管不顾DoS攻击,Apache也会性能陡降,同时大量的下载操作也会是Apache崩溃
OS内核中两个基本的问题:
1、连接数=线程数/进程数:当一个数据包进来,内核会遍历所有的进程以决定由那个进程来处理这个数据包
2、连接数=选择数/轮训次数(单线程):同样的扩展性问题,每个包都要走一遭列表上所有的Socket
通过上述针对Apache所表现出的问题,实际上彻底解决并发性能问题的解决方法的根本就是改进OS内核使其在常数时间内查找,使线程切换时间与线程数量无关,使用一个新的可扩展epoll()/IOCompletionPort常数时间去做socket查询。
因为线程调度并没有得到扩展,所以服务器大规模对socket使用epoll方法,这样就导致需要使用异步编程模式,而这些编程模式正是Nginx和Node类型服务器具有的。所以当从Apache迁移到Nginx和Node类型服务器时,即使在一个配置较低的服务器上增加连接数,性能也不会突降。所以在处理C10K连接时,一台笔记本电脑的速度甚至超过了16核的服务器。这也是前一个10年解决C10K问题的普遍方法。
实现C10M意味着什么
为什么说实现C10M的挑战不在硬件而在软件
硬件不是10M问题的性能瓶颈所在处,真正的问题出在软件上,尤其是*nux操作系统。理由如下面这几点:
首先:最初的设计是让Unix成为一个电话网络的控制系统,而不是成为一个服务器操作系统。对于控制系统而言,针对的主要目标是用户和任务,而并没有针对作为协助功能的数据处理做特别设计,也就是既没有所谓的快速路径、慢速路径,也没有各种数据服务处理的优先级差别。
其次:传统的CPU,因为只有一个核,操作系统代码以多线程或多任务的形式来提升整体性能。而现在,4核、8核、32核、64核和100核,都已经是真实存在的CPU芯片,如何提高多核的性能可扩展性,是一个必须面对的问题。比如让同一任务分割在多个核心上执行,以避免CPU的空闲浪费,当然,这里面要解决的技术点有任务分割、任务同步和异步等。
再次:核心缓存大小与内存速度是一个关键问题。现在,内存已经变得非常的便宜,随便一台普通的笔记本电脑,内存至少也就是4G以上,高端服务器的内存上24G那是相当的平常。但是,内存的访问速度仍然很慢,CPU访问一次内存需要约60~100纳秒,相比很久以前的内存访问速度,这基本没有增长多少。对于在一个带有1GHZ主频CPU的电脑硬件里,如果要实现10M性能,那么平均每一个包只有100纳秒,如果存在两次CPU访问内存,那么10M性能就达不到了。核心缓存,也就是CPU L1/L2/LL Cache,虽然访问速度会快些,但大小仍然不够,我之前接触到的高端至强,LLC容量大小貌似也就是12M。
解决C10M问题的思路总结
网卡问题:通过内核工作效率不高
解决方案:使用自己的驱动程序并管理它们,使适配器远离操作系统。
CPU问题:使用传统的内核方法来协调你的应用程序是行不通的。
解决方案:Linux管理前两个CPU,你的应用程序管理其余的CPU,中断只发生在你允许的CPU上。
内存问题:内存需要特别关注,以求高效。
解决方案:在系统启动时就分配大部分内存给你管理的大内存页。
以Linux为例,解决的思路就是将控制层交给Linux,应用程序管理数据。应用程序与内核之间没有交互、没有线程调度、没有系统调用、没有中断,什么都没有。 然而,你有的是在Linux上运行的代码,你可以正常调试,这不是某种怪异的硬件系统,需要特定的工程师。你需要定制的硬件在数据层提升性能,但是必须是在你熟悉的编程和开发环境上进行。
引言:代码还没有开始写,就要考虑万一哪一天IM用户量破百万,千万该怎么办,这个是程序员的基本修养
高并发是互联网系统架构的一个性能指标之一,它通常是指单位时间内系统能够同时处理的请求数,简单的说:QPS(Queries per second)
对于大多数互联网应用来说,CPU不是该系统的瓶颈,系统的大部分的时间的情况下CPU都是在I/O(硬盘/内存/网络)的读/写操作
控制变量法
一个经典的C/S的HTTP请求流程:
Client -1-> 负载均衡LVS,Nginx -2-> 服务应用层(JAVA Python Go)-3-> 数据缓存层(redis Memcached)-4-> 持久层(MySQL MongoDB)-4-> 返回给客户端
C/S的HTTP请求流程
1、客户端经过DNS服务器解析,请求到达负载均衡的集群
2、负载均衡服务会配置规则,请求分摊到服务层,服务层是我们的业务核心层
3、在经过缓存层
4、最后持久化数据
5、返回数据给客户端
要达到高并发,我们需要负载均衡、服务层、缓存层、持久层都是高可用、高性能的。
并行:两个事件同一时刻完成
并发:两个事件在同一时间段内交替发生,从宏观上看,两个事件都发生了