近些年,随着互联网的大发展,高并发服务器技术也快速进步,从简单的循环服务器模型处理少量网络并发请求,演进到解决C10K,C10M问题的高并发服务器模型。本文结合自己的理解,主要以TCP为例,总结了几种常见的网络服务器模型的实现方式,优缺点,以及应用实例。
单线程循环
优点:
简单、易于实现
没有同步、加锁这些麻烦事,也没有这些开销
缺点:
1、阻塞模型,网络请求串行处理
2、没有利用多核cpu的优势,网络请求串行处理
总之,没有充分利用CPU资源。
适用场景:测试、演示
典型应用:thrift TSimpleServer
多线程/多进程
解析:
主要特点是每个网络请求由一个进程/线程处理,线程内部使用阻塞式系统调用,在实际场景中使用预先分配的进程池/线程池,以减少频繁创建销毁线程的开销,往往可以得到更好的性能。
在线程的职能划分上,可以由一个单独的线程处理accept连接,其余线程处理具体的网络请求(收包,处理,发包);还可以多个进程单独listen、accept网络连接(在linux2.6内核之前会产生惊群,多个进程被唤醒accept建立连接)
优点:
1、实现相对简单
2、利用到CPU多核资源
缺点:
1、线程内部还是阻塞的,举个极端的例子,如果一个线程在handle的业务逻辑中sleep了,这个线程也就挂住了。
典型应用:
单线程IO复用
解析:linux高并发服务器中常用epoll作为IO复用机制,select和poll等其他机制不展开讨论,区别和特点可以自行搜索。线程将需要处理的socket读写事件都注册到epoll中,当有网络IO发生时,epoll_wait返回,线程检查并处理到来socket上的请求。
优点:
实现简单
减少锁开销
减少线程切换开销
缺点:只能使用单核cpu,handle时间过长会导致整个服务挂死。
适用场景:高IO、低计算,handle处理时间短
典型应用:redis
多线程/多进程IO复用
解析:每个子进程都监听服务,并且都使用epoll机制来处理进程的网络请求,子进程 accept() 后将创建已连接描述符,然后通过已连接描述符来与客户端通信。
优点:支撑较高并发
缺点:异步编程不直观、容易出错
适用场景:支撑高并发
典型应用:Nginx
多线程划分IO角色
解析:
一个accept thread处理新连接建立
一个IO thread pool处理网络IO
一个handle thread pool处理业务逻辑
优点:
按不同功能划分线程,各线程处理固定功能,效率更高
可以根据业务特点配置线程数量来性能调优
缺点:
1、线程间通信需要引入锁开销
2、逻辑较复杂,实现难度大
电销应用:thrift TThreadedSelectorServer
AIO
linux AIO机制尚不成熟,没有广泛应用,不展开,感兴趣同学可以参考:
http://man7.org/linux/man-pages/man7/aio.7.html
协程
解析:就是在应用层用户态模拟线程,在用户态管理协程的调度与切换。
优点:
1、减少上下文切换开销
2、编程友好,同步的方式写出异步代码
缺点:多个协程运行在一个线程上,一个协程阻塞将导致整个线程阻塞
参考:
libco
小结:上面介绍了常见的网络服务器模型,现实中可能存在其他的组合和变形,重要的是理解每种场景中所面临的问题和每种模型的特点,设计出符合应用场景的方案才是好方案。