高并发网络编程

www.52im.net/thread-561-… 笔记。html

1. 并发限制因素

1.1 文件句柄限制

一个tcp链接都要占一个文件描述符,一旦这个文件描述符使用完了,新的链接到来返回给咱们的错误是“Socket/File:Can't open so many files”linux

  • 进程限制shell

    执行 ulimit -n 输出 1024,说明对于一个进程而言最多只能打开1024个文件,因此你要采用此默认配置最多也就能够并发上千个TCP链接。临时修改:ulimit -n 1000000,可是这种临时修改只对当前登陆用户目前的使用环境有效,系统重启或用户退出后就会失效。编程

    重启后失效的修改(不过我在CentOS 6.5下测试,重启后未发现失效),编辑 /etc/security/limits.conf 文件, 修改后内容为:数组

    soft nofile 1000000
    hard nofile 1000000
    复制代码

    永久修改:编辑/etc/rc.local,在其后添加以下内容:缓存

    ulimit -SHn 1000000
    复制代码
  • 全局限制服务器

    执行 cat /proc/sys/fs/file-nr输出 9344 0 592026,分别为:网络

    • 已经分配的文件句柄数,
    • 已经分配但没有使用的文件句柄数,
    • 最大文件句柄数。

    但在kernel 2.6版本中第二项的值总为0,这并非一个错误,它实际上意味着已经分配的文件描述符无一浪费的都已经被使用了 。多线程

    咱们能够把这个数值改大些,用 root 权限修改 /etc/sysctl.conf 文件:架构

    fs.file-max = 1000000
    net.ipv4.ip_conntrack_max = 1000000
    net.ipv4.netfilter.ip_conntrack_max = 1000000
    复制代码

2. C10k问题

最初的服务器都是基于进程/线程模型的,新到来一个TCP链接,就须要分配1个进程(或者线程)。而进程又是操做系统最昂贵的资源,一台机器没法建立不少进程。若是是C10K就要建立1万个进程,那么单机而言操做系统是没法承受的(每每出现效率低下甚至彻底瘫痪)。

2.1 C10K问题的本质

建立的进程线程多了,数据拷贝频繁(缓存I/O、内核将数据拷贝到用户进程空间、阻塞), 进程/线程上下文切换消耗大, 致使操做系统崩溃,这就是C10K问题的本质!

可见,解决C10K问题的关键就是尽量减小这些CPU等核心计算资源消耗,从而榨干单台服务器的性能,突破C10K问题所描述的瓶颈。

2.2 C10K问题的解决方案探讨

思路:每一个进程/线程同时处理多个链接(IO多路复用)

该思路的实现存在如下历程:

  • 方式1: 一个线程挨个处理多个链接,等一个socket处理完成以后,再去处理下一个socket
    • 问题:socket是阻塞的,没有数据处理的时候,会阻塞整个线程。(非阻塞socket暂时不涉及)
  • 方式2: select方案。select要解决上面阻塞的问题,思路很简单,若是我在读取文件句柄以前,先查下它的状态,ready 了就进行处理,不 ready 就不进行处理,这不就解决了这个问题了嘛?因而有了 select 方案。
    • 问题:句柄上限+重复初始化+逐个排查全部文件句柄状态效率不高。
  • 方式3: poll方案。poll 主要解决 select 的前两个问题:经过一个 pollfd 数组向内核传递须要关注的事件消除文件句柄上限,同时使用不一样字段分别标注关注事件和发生事件,来避免重复初始化。
    • 问题:逐个排查全部文件句柄状态效率不高。
  • 方式4: epoll方案。既然逐个排查全部文件句柄状态效率不高,很天然的,若是调用返回的时候只给应用提供发生了状态变化(极可能是数据 ready)的文件句柄,进行排查的效率不就高多了么。因此epoll模型成为了C10K问题的终极解决方案。
    • 依赖特定平台(linux)
  • 方式5: libevent/libuv方案。将各个平台的IO多路复用封装。

3. C10M问题

截至目前,40gpbs、32-cores、256G RAM的X86服务器在Newegg网站上的报价是几千美圆。实际上以这样的硬件配置来看,它彻底能够处理1000万个以上的并发链接,若是它们不能,那是由于你选择了错误的软件,而不是底层硬件的问题。

能够预见在接下来的10年里,由于IPv6协议下每一个服务器的潜在链接数都是数以百万级的,单机服务器处理数百万的并发链接(甚至千万)并不是不可能,但咱们须要从新审视目前主流OS针对网络编程这一块的具体技术实现。

3.1 解决思路

Unix的设计初衷并非通常的服务器操做系统,而是电话网络的控制系统。因为是实际传送数据的电话网络,因此在控制层和数据层之间有明确的界限。问题是咱们如今根本不该该使用Unix服务器做为数据层的一部分。

不要让OS内核执行全部繁重的任务:将数据包处理、内存管理、处理器调度等任务从内核转移到应用程序高效地完成,让诸如Linux这样的OS只处理控制层,数据层彻底交给应用程序来处理。

综上所述,解决C10M问题的关键主要是从下面几个方面入手:

**网卡问题:**经过内核工做效率不高 **解决方案:**使用本身的驱动程序并管理它们,使适配器远离操做系统。

**CPU问题:**使用传统的内核方法来协调你的应用程序是行不通的。 **解决方案:**Linux管理前两个CPU,你的应用程序管理其他的CPU,中断只发生在你容许的CPU上。

**内存问题:**内存须要特别关注,以求高效。 **解决方案:**在系统启动时就分配大部份内存给你管理的大内存页。

以Linux为例,解决的思路就是将控制层交给Linux,应用程序管理数据。应用程序与内核之间没有交互、没有线程调度、没有系统调用、没有中断,什么都没有。

4. 从C10K到C10M高性能网络应用的理论探索

4.1 CPU亲和性 & 内存局域性

不管是多进程模型仍是多线程模型,都要把全部的调度任务交给操做系统,让操做系统帮咱们分配硬件资源。咱们经常使用的服务器操做系统都属于分时操做系统,调度模型都尽量的追求公平,并无为某一类任务作特别的优化,若是当前系统仅仅运行某一特定任务的时候,默认的调度策略可能会致使必定程度上的性能损失。我运行一个A任务,第一个调度周期在0号核心上运行,第二个调度周期可能就跑到1号核心上去了,这样频繁的调度可能会形成大量的上下文切换,从而影响到必定的性能。

数据局域性是一样相似的问题。当前x86服务器以NUMA架构为主,这种平台架构下,每一个CPU有属于本身的内存,若是当前CPU须要的数据须要到另一颗CPU管理的内存获取,必然增长一些延时。因此咱们尽量的尝试让咱们的任务和数据在始终在相同的CPU核心和相同的内存节点上,Linux提供了sched_set_affinity函数,咱们能够在代码中,将咱们的任务绑定在指定的CPU核心上。一些Linux发行版也在用户态中提供了numactltaskset工具,经过它们也很容易让咱们的程序运行在指定的节点上。

4.2 RSS、RPS、RFS、XPS

这些技术都是近些年来为了优化Linux网络方面的性能而添加的特性,RPS、RFS、XPS都是Google贡献给社区,RSS须要硬件的支持,目前主流的网卡都已支持,即俗称的多队列网卡,充分利用多个CPU核心,让数据处理的压力分布到多个CPU核心上去。

RPS和RFS在linux2.6.35的版本被加入,通常是成对使用的,在不支持RSS特性的网卡上,用软件来模拟相似的功能,而且将相同的数据流绑定到指定的核心上,尽量提高网络方面处理的性能。XPS特性在linux2.6.38的版本中被加入,主要针对多队列网卡在发送数据时的优化,当你发送数据包时,能够根据CPU MAP来选择对应的网卡队列,低于指定的kernel版本可能没法使用相关的特性,可是发行版已经backport这些特性。

4.3 IRQ 优化

关于IRQ的优化,这里主要有两点,第一点是关于中断合并。在比较早期的时候,网卡每收到一个数据包就会触发一个中断,若是小包的数据量特别大的时候,中断被触发的数量也变的十分可怕。大部分的计算资源都被用于处理中断,致使性能降低。后来引入了NAPI和Newernewer NAPI特性,在系统较为繁忙的时候,一次中断触发后,接下来用轮循的方式读取后续的数据包,以下降中断产生的数量,进而也提高了处理的效率。第二点是IRQ亲和性,和咱们前面提到了CPU亲和性较为相似,是将不一样的网卡队列中断处理绑定到指定的CPU核心上去,适用于拥有RSS特性的网卡。

这里再说说关于网络卸载的优化,目前主要有TSO、GSO、LRO、GRO这几个特性,先说说TSO,以太网MTU通常为1500,减掉TCP/IP的包头,TCP的MaxSegment Size为1460,一般状况下协议栈会对超过1460的TCP Payload进行分段,保证最后生成的IP包不超过MTU的大小,对于支持TSO/GSO的网卡来讲,协议栈就再也不须要这样了,能够将更大的TCPPayload发送给网卡驱动,而后由网卡进行封包操做。经过这个手段,将须要在CPU上的计算offload到网卡上,进一步提高总体的性能。GSO为TSO的升级版,不在局限于TCP协议。LRO和TSO的工做路径正好相反,在频繁收到小包时,每次一个小包都要向协议栈传递,对多个TCPPayload包进行合并,而后再传递给协议栈,以此来提高协议栈处理的效率。GRO为LRO的升级版本,解决了LRO存在的一些问题。这些特性都是在必定的场景下才能够发挥其性能效率,在不明确本身的需求的时候,开启这些特性反而可能形成性能降低。

4.4 Kernel 优化

关于Kernel的网络相关优化咱们就不过多的介绍了,主要的内核网络参数的调整在如下两处:net.ipv4.*参数和net.core.*参数。

主要用于调节一些超时控制及缓存等,经过搜索引擎咱们能很容易找到关于这些参数调优的文章,可是修改这些参数是否能带来性能的提高,或者会有什么弊端,建议详细的阅读kernel文档,而且多作一些测试来验证。

相关文章
相关标签/搜索