随着互联网和物联网的高速发展,使用网络的人数和电子设备的数量急剧增长,其也对互联网后台服务程序提出了更高的性能和并发要求。本文的主要目的是阐述如何进行高并发、高性能通信系统的架构设计,以及这样的系统需要用到的常用技术,但不对其中的技术细节进行讨论和实现。本篇只起抛砖引玉的之效,如有更好的设计方案和思路,望您不舍赐教! [注:此文中使用select来讲解,如果想使用epoll,设计思路是一致的]
我们首先来看看课本上或学习资料上关于处理并发网络编程的三种常用方案,以及对应的大体思路和优缺点,其描述如下所示:
1)、IO多路复用
思路:单进程(非多线程)调用select()函数来处理多个连接请求。
优点:单进程(非多线程)可支持同时处理多个网络连接请求。
缺点:最大并发为1024个,当并发数较大时,其处理性能很低。
2)、子进程处理
思路:当连接请求过来时,主进程fork产生一个子进程,让子进程负责与客户端连接进行数据通信,当客户端主动关闭连接后,子进程结束运行。
优点:模式简单,易于理解;连接请求很小时,效率较高。
缺点:当连接请求过多时,系统资源很快被耗尽。比如:当连接请求达到10k时,难道要启动10k个进程吗?
3)、线程处理
思路:首先启动多个工作线程,而主线程负责接收客户端连接请求,工作线程负责与客户端通信;当连接请求过来时,ACCEPT线程将sckid放入一个数组中,工作线程中的空闲线程从数组中取走一个sckid,对应的工作线程再与客户端连接进行数据通信,当客户端主动关闭连接后,此工作线程又去从指定数组中取sckid,依次重复运行。
优点:拥有方案②的优点,且能够解决方案②的缺点。
缺点:不能支持并发量大的请求和量稍大的长连接请求。
通过对以上三种方案的分析,显然以上方案 均不能满足高并发、高性能的服务器的设计要求。针对以上设计方案问题的存在,那我们该如何才能设计一个既能满足高并发,又有高效率的网络通信系统呢?
IO多路复用中1个select()最多可管理1024个Socket FD,而如果要求并发量达到10k时,其显然大大超过了1个select()的管理能力。此问题该如何解决?
很容易便可发现10k并发约等于10 * 1024并发,因此,需要大约10个select()才能有效管理,那该如何调用10个select()来管理10k的并发呢?
因FD在进程之间是独立的,虽然子进程在创建之时,会继承父进程的FD,但后续新的连接产生的FD却无法继承,因此,要实现对10k并发的有效管理,使用 多线程实现高并发要求 是唯一的选择。即:每个线程调用1个select(),而每个select()最多可以管理1024个并发。
在理想情况下,启动10个接收线程,便可管理10 * 1024 = 10k的网络并发。通过此种方式,便可达到高并发的设计要求。
为了进一步的提高处理效率,我们可以对线程进行分工划分:
Recv线程:专门负责与多个客户端连接进行通信,也就是负责接和发送数据;
Work线程:专门负责处理Recv线程接收到的数据。
Recv线程和Work线程协助处理的流程大体过程:
①、Recv线程将接收到的数据随机的放入到某个接收队列中;[注:随机放入起着负载均衡的作用]
②、接收完毕之后,再随机通知其中一个Work线程到指定的队列中取数据;[注:随机通知起着负载均衡的作用]
③、Work线程接收通知后,到指定的接收队列中取走数据;
④、Work线程根据取到的数据做相应的处理。
故,其大体设计架构如下图所示:
图1 数据接收与处理
看到这时你可能还会有如下几个疑问:
①、Accept线程通过何种方式将新连接的Socket FD分发给Recv线程呢?
②、Recv线程通过何种方式通知Work线程到指定的接收队列取数据呢?
在之前的一篇博文 《日志系统系能对比分析》中对TCP、UDP、Unix-TCP、Unix-UDP的性能做了对比测试和分析,可发现在这几种传输协议中,Unix-UDP的效率是最高的,因此,本系统选择Unix-UDP作为该系统内部消息通知机制的传输协议。
此系统技术要点:
① 非阻塞IO ② 事件触发机制 ③ 线程池机制 ④ 负载均衡机制 ⑤Unix-UDP传输消息 ⑥ 内存池机制 ⑦ 高效算法和技巧的使用等等。
在此只是对此系统的设计思路的大概描述,不做细节的讨论和分析。此设计思路在其他的应用场合可做适当的扩展变更,产生更适合具体应用场景的设计方案。
作者:邹祁峰
2014.05.04 16:00