常见服务器模型对比,已经优缺点分析
1. 迭代式服务器 accept + read / write
特 点: 这种迭代器其实不是并发服务器, 而是迭代服务器, 因为他一次只能服务一个客户端, 属于单线程。
长连接:需要read – write 之间循环接受客户端请求, 但此时该模型只能服务一个客户端
短链接:如果想要支持短链接,其中的 计算 - 处理 - 打包 操作不能太耗时, 应为太过耗时会影响其他客户端连接到来。
2. fork/thread - per - connection
特 点: 此方案属于并发式服务器模型, 可以处理多个客户端请求, 但由于创建 process / thread 由一定的开销,32位系统下 linux 下用户空间有3G, 一个线程栈是 10M, 那么总共可以创建 300 个线程,切线程和进程之间切换也会耗时, 所以其并发链接数不高。
长连接: 支持长连接, 但此长连接中, 计算相应的工作量应远大于 fork() / pthread_create() 的开销,要不然得不偿失。
短链接: 不太适合短链接, 应为创建线程 / 进程的开销远远大于计算的开销。
3. pre - fork 方案
特 点: 此方案是一个过度方案, 对于方案三中创建线程/进程的开销比较大, 所欲可以预先创建很多线程, 当有客户端到来时, 直接分配一个线程进行处理。 但同时会 带来惊群现象, 即一个客户端连接到来,所有阻塞在 accept 上面的进程全部会唤醒。 但此时只有一个进程可以 accept 到客户端连接。
至此, 以上方案都属于阻塞式网络编程,程序通常阻塞在 read() 上, 等待数据到达。 但是 TCP 是个全双工协议, 同时支持 read() 和 write() 操作,当一个线程阻塞在 read() 上, 但程序又想给这个 TCP 连接发送数据, 那该怎么办?
IO multiplexing 闪亮登场, 也就是 select / poll / epoll 这一系列的 “多路复用器”,让一个线程就可以处理多个连接, 。同时使用 epoll 几乎肯定要配合 non-blocking IO,而使用 non-blocking IO 肯定要使用应用层 buffer。
Reactor 模型, 基于 IO multiplexing 总结出了该套模型, 只有一个 IO 线程, 在没有事件的时候,线程等待在 select / poll / epoll_wait 函数上。 当有事件的时候,这些函数返回去通知其他函数去处理事件, 在 epoll_wait 返回之后,到下一次调用 epoll_wait进入等待之前, 这段时间内, 线程不能被其他连接上的数据或事件抢占, 此过程中不能有阻塞 / 延迟 / sleep / 磁盘 IO 之类的耗时操作,以避免阻塞当前 IO 线程, 使得不能及时回到 epoll_wait 函数接受新的事件。
5. poll (reactor) 模型
特 点 : 首先,将监听 socket 加入到 epoll 中, 当有连接到来时, acceptor 调用 accept 接受客户端连接,将连接套接字加入到 epoll 中关注可读/可写事件, 当有事件 发生时, 进行计算-处理-打包, 这些操作都是在一个线程中完成的;
缺 点: 不能充分利用多核 CPU , 因为所有事件都是在单线程中处理的, 由于其是单线程,所以也不能执行事件较长任务;
优 点: 相比于 方案二 能处理更多的并发连接。
6. reactor + threadpoll
特 点: 每个连接到来, 在 reactor 中读取请求包, 丢到 线程池中处理, 即使请求的任务比较耗时, 比较消耗 CPU, 他们在线程池的线程中执行的, 不会影响到 IO 线程,
优 点: 适应计算密集型服务, 也适应耗时任务;
缺 点: 对于突发 IO, 有可能会有瓶颈。
注 意: 线程池将任务处理完成后, 发送是交给 IO 线程去处理的。
7. multiple reactors
特 点: 每个reactor 都是一个线程, 每个 reactor 都是一个 Event Loop, mainReactor 是主 IO 线程,将监听套接字加入进去, 他只负责 accept 客户端连接,返回的已 连接套接字会以 round-robin 的方式,依次添加到 subReacor 中去, 使得连接可以均匀的分配到 SubReactor 中去。每一个连接只能在一个reacor 中处理。
优 点: 可以适应突发 IO, 一般一个reactor 可以适应一个 千兆网口, 当有多个千兆网口时, 可以分配多个。
8. multiple reactor + threadpool
特 点: 对于方案七, 不能处理计算耗时任务, 如果可以将计算比较耗时的任务加入到线程池中,那岂不是完美了, 所以 one loop per thread + threadpoll 应运而生。 注意此处的 reactor 是不能用进程去实现的,应为进程之间不能共享线程池。
每个 subreactor 将计算处理放入线程池中 , 线程池处理完成后发送操作由相应的 subReactor 发送。
是不能用进程去实现的,应为进程之间不能共享线程池。
每个 subreactor 将计算处理放入线程池中 , 线程池处理完成后发送操作由相应的 subReactor 发送。
此模型也是比较推荐的模型,当然根据业务选则模型是最正确的姿势。