常见并发服务器方案
- 循环式/迭代式服务器
整个服务器的流程是
- 创建套接字,绑定监听。
- 在一个循环中执行:
- 接受客户端连接
- 读
- 解码 处理 编码
- 写
- 关闭连接
- 关闭连接
这种连接方式实际上是短链接,而循环式服务器只能是短连接。整个程序是一个单线程的应用程序,不能是长连接的原因是因为如果需要进行长链接持续读,那么需要在写后在进行读,此时如果来一个新的连接请求就得不到响应。
另外整个连接处理过程不能太长,如果太长就会影响用户的响应时间。
总的来说这种方式具有下面几个缺点:
- 不能进行长连接
- 不能处理复杂的业务请求
- 不能利用cpu多核优势
- cocurrent服务器
这个服务器的流程如上图所示,通过多进程的方式来处理多个客户端请求,并且可以处理长连接。
pre-fork or pre threaded
这种称为预先创建线程或者进程的方式,每个进程或者线程负责一个客户端的请求。这种方式的一个缺点是会出现“惊群”现象。
所谓惊群现象,就是父进程创建socket,bind、listen后,通过fork创建多个子进程,每个子进程继承了父进程的socket,调用accpet开始监听等待网络连接。这个时候有多个进程同时等待网络的连接事件,当这个事件发生时,这些进程被同时唤醒,就是“惊群”。这样会导致什么问题呢?我们知道进程被唤醒,需要进行内核重新调度,这样每个进程同时去响应这一个事件,而最终只有一个进程能处理事件成功,其他的进程在处理该事件失败后重新休眠或其他。reactor模式
- reactor可以使用select/poll/epoll来实现。
- 我们首先注册我们关注的监听套接字;
- 用客户端发来请求,acceptor来接受连接,然后将对应的文件描述符加入reactor中关注可读事件;
- 如果发生可读事件,开始dispatch(分派);
- 然后进行业务逻辑的处理。
以上所有的操作都是在单线程上完成的,因此不能处理较复杂的业务逻辑,也不能利用多核cpu的优势。
- 过渡方案
- reactor + thread per request
每来一个请求,创建一个线程,在请求较多的情况下系统的负载显著上升。 - reactor + worker thread
每个连接在一个工作者线程中完成,这种方式不如cocurrent方式,因为多了reactor。
reactor + threadpool
与reactor不同的地方是,将业务代码通过线程池来实现。
这种模式适用于计算密集型的业务。mutiple reactors
如上图所示,拥有一个mainReactor,当acceptor返回已连接套接字,就将活跃的套接字分配给subReactor。如果分配的顺序是按顺序,那么就叫做“轮叫”(round robin),这种方式能够保证每个reactor都有均匀的任务数量。
每个进程或者线程拥有一个事件循环。(reactors in threads or reactors in process)。
这种方式能够应对更大的突发IO。
一个reactor能够应对一个千兆网卡,我们可以根据网卡数量来设计线程池或者进程池的容量。mutiple reactors + thread pool
这种方式也叫做one loop per thread + thread pool。
实际上在这种模式下,一共有两个线程池,一个是reactor的线程池,一个是业务线程的线程池。proactor服务器(基于异步IO)
这种异步方式linux支持不够,暂不考虑。