简述 高性能Linux服务器 模型架构 设计

主要从三个方面进行分析:

1.事件处理模式

2.并发模式

一.事件处理模式

1.Reactoor模式

定义:

  主线程只负责监听文件描述符上是否有事件发生,有的话立即将该事件通知工作线程,除此之外,主线程不做任何实质性的工作,读写数据,接受新的连接以及处理客户请求均在工作线程中完成

样例:

  使用同步IO模型epoll_wait为例实现Reactor模式的工作流程:

1).主线程往epoll内核事件表中注册socket上的读就绪事件

2).主线程调用epoll_wait等待socket上有数据可读

3).当socket上有数据可读时,epoll_wait通知主线程,主线程则将socket可读事件放入请求队列

4).睡眠在请求队列上的某个工作线程被唤醒,它从socket读取数据,并处理客户请求,然后往epoll内核事件表中注册该socket上的写就绪事件

5).主线程调用epoll_wait等待socket可写

6).当socket可写时,epoll_wait通知主线程,主线程将socket可写事件放入请求队列

7).睡眠在请求队列上的某个工作线程被唤醒,它往socket上写入服务器处理客户请求的结果


2.Proactor模式

定义:

  所有的IO操作都交给主线程和内核来处理,工作线程仅仅负责业务逻辑

样例:

  使用异步IO模型(aio_read和aio_write)为例实现Proactor模式的工作流程

1).主线程调用aio_read函数向内核注册socket上的读完成事件,并告诉内核用户读缓冲区的位置,以及读操作完成时如何通知应用程序(信号)

2).主线程继续处理其他逻辑

3).当socket上的数据被读入用户缓冲区后,内核将向用户程序发送一个信号,以通知应用程序数据已经可用

4).应用程序预先定义好的信号处理函数选择一个工作线程来处理客户的请求,工作线程处理完客户的请求之后,调用aio_write函数向内核注册socket上的写完成事件,并告诉内核用户写缓冲区的位置,以及写操作完成时如何通知应用程序

5).主线程继续处理其他逻辑

6).当用户缓冲区的数据被写入socket之后,内核将向应用程序发送一个信号,已通知应用程序数据发送完毕

7).应用程序预先定义好的信号处理函数选择一个工作线程来做善后处理,比如决定是否关闭socket


3.使用同步IO的方式模拟Procator模式

定义:

  主线程执行数据读写操作,读写完成后,主线程向工作线程通知这一完成事件,那么从工作线程的角度来看,他们就直接获得了数据的读写结果,接下来只需要对读写结果进行逻辑处理

样例:

  使用同步模型(以epoll_wait为例)模拟出Proactor模式

1).主线程往内核事件表中注册socket上的读就绪事件

2).主线程调用epoll_wait等待socket上有可读数据

3).当socket上有可读数据时,epoll通知主线程,主线程从socket循环读取数据,直到没有更多的数据可读,然后将读取到的数据封装成一个请求对象并插入到请求队列

4).睡眠在请求队列上的某个工作线程被唤醒,它获得请求对象并处理客户请求,然后往epoll内核事件表中注册socket上的写就绪事件5).主线程调用epoll_wait等待socket可写

6).当socket可写时,epoll_wait通知主线程,主线程往socket上写入服务器处理客户请求的结果


ps:Reactor模式注册的是文件描述符的就绪事件,而Procator模式注册的是完成事件


二.并发模式

1.并发编程存在的意义及背景介绍:

  对计算密集型程序来说,并发编程并没有任何优势

  对IO密集型程序来说,并发编程具有非常大的优势,可用显著的提高性能,由于IO操作的速度远远没有CPU计算速度快,所以让程序阻塞于IO操作将浪费大量的CPU时间,如果采用多线程,那么在这个时间段,CPU就可用去做更加有意义的事情,而不是傻傻的等待IO操作的完成


在IO模型和并发模型中,同步和异步具有不同的意义

IO模型中:同步和异步区分的是内核向应用程序通知的是何种IO事件(就绪事件还是完成事件),以及由谁来完成IO读写(应用程序/内核)

并发模型中:同步和异步指的是程序的执行顺序(完全按照代码的顺序执行/执行顺序由系统事件来驱动)

按照同步方式运行的线程叫做同步线程,效率低,实时性差,但逻辑简单

按照异步方式运行的线程叫做异步线程,效率高,实时性强,但逻辑复杂

对于服务器这种既要求实时性又要求能同时处理多个客户请求的应用程序,可以同时采用同步线程和异步线程的方式实现,即半同步半异步模式


2.半同步半异步模式

概念:

  该模式中,同步线程用来处理客户逻辑,异步线程用于处理IO事件

  异步线程监听到客户请求后,就将其封装成请求对象并插入请求队列中,请求队列将通知某个工作在同步模式的工作线程来读取并处理请求对象


在服务器程序中,如果考虑到事件处理模式和IO模型,那么半同步半异步模式存在好几种变种模式:半同步半异步的Reactor模式,半同步半异步的Procator模式


2.1 半同步半异步的Reactor模式(其实就是上面的Reactoor模式的样例)

概念:

  异步线程只有一个,由主线程来充当

  主线程负责监听所有socket上的事件

  如果监听socket上有读事件发生,即有新的连接到来,主线程接受新连接的socket,然后往epoll内核事件注册表中注册该socket上的读写事件

  如果连接socket上有读写事件发生,即有新的客户请求到来或者有数据要发送至客户端,主线程就将该就绪的连接socket插入到请求队列中

  所有工作线程都睡眠在请求队列上,当有任务来到时,他们将通过竞争获得任务的接管权(空闲的工作线程来处理任务)


2.2 半同步半异步的Procator模式(其实就是上面的Procator模式的样例)

概念:

  和半同步半异步的Reactor模式的不同之处在于半同步半异步的Reactor模式的主线程插入请求队列的是就绪的连接socket,而半同步半异步的Procator模式的主线程则会将应用程序数据,任务类型等信息封装成一个任务对象然后将其插入任务队列,两种模式的根本区别在于插入任务队列的东西不一样,一个是就绪的事件,一个是事件的结果


半同步半异步模式的缺陷:

1).请求队列需要加锁进行保护,无论是插入任务还是拉取任务都需要获得任务队列的锁,这将白白耗费CPU时间

2).工作线程数量少则无法满足需求,工作线程数量多则线程的切换将耗费大量CPU时间


2.3 高效的半同步半异步模式

概念:

  每个工作线程负责一个连接socket上的所有IO操作,这样每个工作线程都能同时处理多个客户连接(主线程对监听socket调用epoll_wait,工作线程对连接socket调用epoll_wait),无论是工作线程还是主线程都维持自己的事件循环,各自独立的监听不同的事件

不存在请求队列,主线程直接分发连接socket给工作线程

大大的减少了线程切换的次数

这种半同步半异步的模式其事件机制既可以采用Reactor也可以采用Proactor,可以主线程先直接读完socket上到来的数据,然后封装转递给工作线程,也可以由工作线程自己去读,建议采用读完封装传递的方式,这样响应更快,省去了线程切换的时间


3.领导者/追随者模式

概念:

  多个工作线程轮流获得事件源集合,轮流监听,分发,并处理事件的一种模式

  在任意时间点,程序都仅有一个领导者线程,负责监听所有IO事件,而其他线程都是追随者,追随者休眠在线程池中等待成为领导者

  当前领导者如果检测到IO事件,则先从追随者中选出一位新的领导者,然后检测到IO事件的当前领导者处理IO事件,此时,新领导者等待新的IO事件,而原来的领导者处理IO事件,二者实现了并发

  这样以来,监听到IO事件的线程可以直接去处理IO事件,而不需要转交给其他线程从而浪费时间,也不需要将就绪IO事件直接放入请求队列或者读完封装放入请求队列中,不再需要请求队列,也不需要给请求队列加锁

  但是需要给线程集加锁,因为推选新的领导者和等待成为新的领导者都需要操作线程集,必须加锁避免竞态条件

  同样这种模式也仅仅支持一个事件源集合,无法像高效半同步半异步模式那样让每个工作线程独立管理多个客户连接 

  但总的来说还是非常高效的


-----------------------------------------------------------------------------------------------------------------

参考:《Linux高性能服务器编程》游双著

猜你喜欢

转载自www.cnblogs.com/yinbiao/p/11466118.html
今日推荐