网络编程大并发实战-C100K

上篇进行了一个C10K的实战,并得到了一个初步的测试数据来支撑C10K的结果,那么本篇我们进行一个升级版C100K的实战。

对此我们还是使用之前的xlink 库,测试下epoll 及aio 的性能能否达到我们续期,C100K。

epoll

对于C100K问题,首先我们要解决的不是程序问题,而是系统设置问题,连接数的限制,文件句柄的限制,同时,对于客户端来说,由于单个IP端口数的限制,端口是16位的其最大端口数为 2的16次方全部利用起来也就只能支持60K多的连接(如果是土豪有很多测试机器可以当客户端端口限制基本可以忽略,不够测试就堆机器吧)针对如上问题解决如下:

句柄问题:

1.修改/etc/security/limits.conf (改为1024000 <NR_OPEN是为之后 1M问题做准备),

* soft nofile 1024000
* hard nofile 1024000

 

2.修改/etc/sysctl.conf:

fs.file-max = 1048576

[root@localhost ~]# sysctl -p
端口问题(客户端):

将可用端口全部开放,设置/etc/sysctl.conf:

net.ipv4.ip_local_port_range = 1024 65535

的只这样还是不够C100K,所以还需要多加几块网卡,或者添加虚拟地址。好吧咱就用最实惠的方式加虚拟IP吧。

还有一些TCP参数调优的东西,C100K就不列了,毕竟也就是个初步测试,主要是检验下实战代码的,咱就略了,等到C1M再说。

环境还是同前述,就直接来看看结果吧:

连接数 102399 注册数 102399
连接耗时
(ms)
2893 注册耗时
(ms)
6563

结论:C100K在3秒左右就轻松解决,而且该机器性能一般,同时该C100K全部活跃的QPS测试出来大致达到了20K/S。所以C100K中有20K的活跃度都是轻松搞定。

本来还想拿去年写的IOCP 压一下出个结果,看来还是放到接下来的C1M问题去试试吧,C100K应该完成没压力。(主要原因是懒,还没想好把IOCP跟xlink 合并在一起)。就先这样吧,C100K这种对于当前金融公司的产品应该完全够用了,基本都不需要分布式了,当然对于像淘宝这种有大客户体量的公司来说,只是C100K是不够的。接下来就试试C1M吧。

网络编程大并发实战-C10K

网络通信是由用户需求推动,早期,大家都玩单机游戏,上个互联网有个上百人就算是比较大型应用,当时并没有那么大的需求。但是,随着PC普及,互联网的爆发期,游戏经历单机,到局域网,到互联网多人大型游戏;互联网的发展也从1.0的浏览下网页,到2.0的各种社群交互互动,C10K问题首先涌现。

问题

早期的IO模型基本都是一个进程或者一个线程一个连接,C10K,物理资源是他的天花板。不管是否分布式,那都是及其耗费物理资源,成本巨大。

分析

既然问题是由于进程或者线程引起的物理资源问题,那么突破口也资源这里,IO线程模型使用就是让一个进程或者线程处理多个连接,网络IO就使用多路复用。在早期由一个比较成熟的就是select 模型,而后又发展出了poll,epoll,同时还有aio,这些在前面章节都有涉及,在这里我们就直接上代码来验证他们能否解决C10K问题,并进行分析。在此我们通过写一个简单的网络库,来验证各种实现方式模型是否可行。简单就叫其link吧。使用reactor 模式,该库包含select,poll,epoll等。线程模型为单线程接受处理,单线程接受多线程处理通信两种方式。统一level_tringer触发,统一处理成事件回调模式。通过测试对比每种实现方式来看看是如何解决C10K,及其中的一些问题及奥秘的。

select

在 linux 下使用select 有诸多连接限制,并且修改麻烦,同时 select 模型 在windows 也是参照linux下的select 模型移植过来的,并且windows 下只要重新定义 FD_SETSIZE 大小就可以改变原来 每个 接收所以就在本机windows 下测试。

逻辑:建立完成所有连接后,所有连接同时发送一个业务注册包,服务收到后立马应答。

客户端发送10239个链接,服务端稳定的接收了该10239个连接并注册成功,但是其响应时间有点延迟:

结果:内存占用连接数10239 大概100M,CPU 0- 5%的波动。

连接数 127 1023 10239 注册数 128/128 1023/1023 10239/
10239
连接耗时(MS) 23 179 4230 注册耗时
(MS)
16 36 7590

测试环境:以上测试数据在两台局域网的PC机器中进行,i5双核4线程,8G内存,其中还开了其他应用不是非常纯净,服务器测试程序开了1个连接接收线程,4个连接处理线程。

分析:同时测试服务器应该还有可改进的空间,为了能把select ,epoll等所有模型都放到LINK 库中,回调嵌套太多,内存拷贝等还有很多可优化,同时服务器调优下。所以如果再认真点做好,windows 下的这个 非阻塞 select 模型C10K 都应该不是问题,至少连接上,可以处理,但是发包应答的延迟还是挺致命的,有机会后续可以优化看看,那么如果直接使用windows提供的eventselect 模型应该更加容易达成这个结果了。当然这个结果已经出乎意料,linux 下的select 没有试过,但是windows下的这个结果,

结论:select模型下,初略估算C10K的连接,每秒2000的活跃连接并发还是能轻松支持。

epoll

架构模型同上面的,在做这个测试之前我们其实已经知道epoll 是高效能的,所以100,1000级别的咱就没必要测试了,直接看下 C10K的测试结果

结果:内存占用连接数10239 大概79M,CPU 0- 5%的波动。

连接数 10239 注册数 10239
连接耗时(ms) 469 注册耗时
(ms)
454
测试环境:Intel(R) Xeon(R) CPU E5-2430 0 @ 2.20GHz,4核,8G内存,测试服务器非常不纯净,将就用,服务器测试程序开了1个连接接收线程,4个连接处理线程。
结论:还是非常轻松的就能支持C10K连接,并且并发轻易上20K。

总结:

改测试库写的非常仓促,还是有很多改进空间。如果只是需要C10K的连接数的话,select 跟epoll 都能满足需求,就目前测试情况来看,select 也没有传说中的不稳定,还是挺稳定,但是select 并活跃并发数虽然也能上到 10K,但是其相应时间应该已经超出了正常业务场景的需求,所以如果并发数量不多,使用select 也能一战,这也是为什么以前的游戏服务器,虽然 epoll ,IOCP已经成熟的情况下,select 还是能撑起半壁江山,也是不无道理。所以如果要上生产大并发的话,epoll 还是不二选择。

网络编程大并发实战-IO框架介绍

根据前面的几个模型我们来看看以前都有哪些的IO框架服务器,当然我们也会给我们当前的一些服务器归类下。主要就分成3类:封装的TCP/IP的通信框架,再此基础上进行http接入代理的服务器,还有一类就是带上应用功能的应用容器。我们主要讲解下前面两类。

IO通信框架

主要就是根据BIO,NIO,AIO这样的一个历程呢,IO通信框架也在不断的发展优化。

http服务器

apache

这是个典型的 一个连接一个进程的并发模型的web服务器,在并发少的时候,其性能不错,但是当连接增大的时候,就会处理不过来了,比如C10K。当然即使是现在,操作C10K,C1M的还是少数,所以大多数web服务器使用apache 也还是能好好的用着的,毕竟不是每个企业都像淘宝谷歌一样有这样打的用户数基础。

nginx

这个就是典型的 IO复用+多进程/多线程的代表,nginx的出现就可以比较完美的解决了C10K的问题,目前一般还是应用成多进程比较多,多线程的方式也可以,据说没多进程稳定。

以上是现今主流的http服务器,还有一些非常适合学习的http服务器如下:Tinyhttpd,lighttpd,

这类web服务可以进行反向代理,静态资源获取,甚至负载均衡处理。但是还缺少一种能力就是,动态资源生成。这样就需要下面应用容器出场。

应用服务器(容器)

tomcat/Jetty

这类都是应用容器,用于处理动态应答数据的。当然应用容器不止这些,但是我们只举了当下比较流行的JAVA 运行时下的基于serlet的http应用容器。

 

以上举例的都是web服务器方面的,而真正可以脱离上层限制的的比较接近底层的就是 IO通信框架了,他们可以作为上述两类服务器的IO通信基础,也可以作为应用分布式中间件互联的高性能通信基础。

IO通信框架

这个使用选择得就会比上述连个服务器类型多得很多,对于JAVA运行时来说,netty以及mina都是非常不错的。现今都是已NIO为基础实现,甚至支持AIO。

当然对于C/C++来说,这种偏底层的东西种类会更加繁多:libeventBoost.AsiolibcurlZeroMQ

 

 

说完这些,接下来就要进入实战阶段了,想想还是有些小期待,小兴奋。

网络IO补充说明

之前也提到过了网络IO的一些模型,同步异步,这里再稍微补充下网络IO中的数据流向,方便理解。

数据流向

网络IO操作实际过程涉及到内核和调用这个IO操作的进程。以read为例,read的具体操作分为以下两个部分:

  (1)内核等待数据可读

  (2)将内核读到的数据拷贝到进程

image

阻塞非阻塞

这个比较好理解,就是一个请求能否马上得到应答结果。可以就是非阻塞,反之就是阻塞。

image

同步异步

这个概念在阻塞与非阻塞的基础之上进行理解,同时其适用的上下文环境是针对应用程序的用户空间与网络IO系统操作的内核空间之间的交互。当应用程序提交一个IO操作,需要等待或者轮询其结果的时候就是同步。如果IO操作由内核直接做完,并会在完成后通知应用程序来取结果,那么就是异步的。

image

网络编程大并发实战-IO并发线程模型

本篇章只讨论基于前文介绍的IO模型引申出来的狭义的IO并发编程模型,而非纯粹并发编程模型。基本上就是线程,进程相关的并发编程模型,需要了解其他诸如协程等其他并发编程模型,请边上走。

进程与线程

早期的单进程或者单线程的方式我们就不说,这里一般就是讨论的多进程及多线程的方式,涉及到多进程多线程就会产生资源的竞争,CPU的系统资源的竞争就会需要上下文切换,这是要耗费资源的。这里只对进程及线程的切换可能的影响做一个比较:

进程上下文切换内容:

  • 通用寄存器、状态寄存器、程序计数器等各种CPU寄存器
  • 内核栈、用户栈
  • page table, 用于虚拟地址空间和物理地址空间映射
  • process table,保存进程id,优先级,环境变量等
  • file table,保存当前进程打开文件的状态信息

线程上下文切换内容:

切换CPU中的各种寄存器。

一般来说一种方式的 优缺点都是相对的,并且一般都是由于某种特性引起的,那么多进程的优缺点也是由于进程如下特性引起的:

有独立的地址空间、可执行的代码段、打开的文件句柄、 堆、栈。

多进程的优点:简单。(正因为其地址空间的独立性,其不存在资源的竞争。所以简单)

多进程的缺点:效率低,并发能力被限制。进程切换开销:(1)用户空间与内核空间转化,(2)任务调度器的计算复杂度, (3)进程上下文切换。(进程调度是抢占式的,正因为其地址空间的独立性,当进行进程切换的时候,上下文内容就特别多,需要保存的进程缓存就比较多)

多线程的优点:上下文切换的开销要小很多。由于共享地址空间,同一个进程 的不同线程切换时,只需切换CPU中的各种寄存器,而不必进行昂贵的地址转换、缓存刷新操作。 寄存器的切换是非常高效的,通常,它的开销与一次内核态、用户态的切换相当。此外,线程间不需要 低效的IPC通信,可以访问共享的地址空间。由于线程独立顺序执行,具有较好的数据局部性(data locality)。

多线程的缺点:需要引入了原子操作、锁、条件变量、信号量等机制来解决线程互斥与同步的问题。

一个连接一个进程/线程

一般来说就是典型的 多进程/多线程 + blocking IO.   通过前面进程与线程的描述其实我们也知道,使用多进程及多线程是耗费资源的,特别是当连接数,并发数增加到一定量级之后,进程或者线程本身及其切换就会成为瓶颈。所以这种模型适合连接数或者并发数不多的情况,一般用用也是够了。

连接与进程可不可以不用一一对应,因为计算机的资源是有限。下面这种模式就解决了这个问题。

IO多路复用+多进程/线程(事件模型)

这是一个 1+n 的线程模型,一个主进程/线程,专门负责监听处理连接,当客户端连接上来之后就直接扔到select/epoll 上,轮询到有数据通信发生的时候,把处理放到多个工作线程中去,同时为了不让前面的悲剧发生,减少不必要的切换,工作线程数的设置与CPU数线性相关(一般设置为CPU内核数或者2倍(超线程))。

在这个模型中,已经可以实现大并发大连接,但是还是基于同步IO,即IO还是有阻塞。虽然连接数上来了,但是并发性能上还有优化空间,因为IO还有阻塞。那么就需要下面这个相近的并发模型了。

异步IO+多线程

这个模型跟上面的模型使用的区别就是实现真正的异步IO,其他都差不多。但这个需要操作系统内核的支持。WINDOWS系统有比较完善成熟的一套IOCP异步IO,而Linux  内核的异步IO支持稍晚,比较流行成熟的大并发还是已epoll 作为IO模型。