第8章 高性能服务器框架

第8章 高性能服务器框架

这一章是全书的核心,也是后续章节的总览。在这一章中,我们按照服务器程序的一般原理, 将服务器结构为如下三个主要模块:

  •  I/O处理单元。本章将介绍I/O处理单元的四种I/O模型和两种高效时间处理模式。

  •  逻辑单元。本章将介绍逻辑单元的两种高效并发模式,以及高效的逻辑处理方式----有限状态机。

  •  存储单元。本书不讨论存储单元,因为它只是服务器程序的可选模块,而且其内容与网络编程本身无关。

最后,本章还介绍了提高服务器性能的其他建议。


8.1 服务器类型

8.1.1 C/S 模型

TCP服务器和TCP客户端的工作流程(图)

C/S模型非常适合资源相对几重的场合,并且它的实现也很简单,但其缺点也很明显: 服务器是通信的中心,当访问量过大时,可能所有客户端都将得到很慢的响应。

8.1.2 P2P模型

P2P模型使得每台机器在消耗服务的同事也给别人提供服务,这样自由能够充分、自由地共享。 P2P模型的缺点也很明显:当用户之间传输的请求过多时,网络的负载将加重。

P2P模型还存在一个显著的问题,即主机之间很难互相发现。所以实际使用的P2P模型通常带有一个专门的发现服务器。 这个发现服务器通常还提供查找服务,使每个客户都能尽快地找到自己需要的资源。


8.2 服务器编程框架

  •  I/O 处理单元

  •  请求队列

  •  逻辑单元

  •  请求队列

  •  网络存储单元(可选)

I/O 处理单元是服务器管理客户连接的模块。

一个逻辑单元通常是一个进程或线程。

网络存储单元可以是数据库、缓存和文件。

请求队列是个单元之间的通信方式的抽象。


8.3 I/O 模型

  •  阻塞 I/O

  •  I/O 复用

  •  SIGIO 信号

  •  异步 I/O

8.4两种高效的事件处理模式

服务器通常要处理3类事件:I/O事件、信号及定时事件。

8.4.1 Reactor 模式

Reactor是这样一种模式,它要求主线程(I/O处理单元,下同)只负责监听文件描述上是否有事件发生, 有的话就立即将该事件通知工作线程(逻辑单元,下同)。除此之外,主线程不做任何其他实质性的工作。 读写数据,接收新的连接,以及处理客户端请求均在工作线程中完成。

8.4.2 Proactor 模式

Proactor模式将所有I/O操作都交给主线程和内核来处理,工作线程仅仅负责业务逻辑。


8.5 两种高效的并发模式

从实现上来说,并发编程主要有多进程和多线程两种方式。

并发模式是指I/O处理单元和多个逻辑单元之间协调完成任务的方法。

服务器主要有两种并发编程模式:

  • 半同步/半异步(half-sync/half-async)模式
  • 领导者/追随者(Leader/Followers)模式

8.5.1 半同步/半异步模式

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

在并发模式中,“同步”指的是程序完全按照代码序列的顺序指向;“异步”指的是程序的执行需要由系统事件来驱动。 常见的系统事件包括中断、信号等。

半同步/半异步模式中,同步线程用于处理客户逻辑,异步线程用于处理I/O事件。

8.5.2 领导者/追随者模式

领导者/追随者模式是多个工作线程轮流获得事件源集合,轮流监听、分发并处理事件的一种模式。 在任意时间点,程序都仅有一个领导者线程,它负责监听I/O事件。 而其他线程则都是追随者,它们休眠在线程池中等待成为新的领导者。 当前领导者如果检测到I/O事件,首先要从线程池中推选出新的领导者线程,然后处理I/O事件。 此时新的领导者等待新的I/O事件,而原来的领导者则处理I/O事件,二者实现了并发。

领导者/追随者模式包含如下几个组件:

  • 句柄集(HandleSet)
  • 线程集(ThreadSet)
  • 事件处理器(EventHandler)
  • 具体的事件处理器(ConcreateEventHandler)

8.6有限状态机

前面两节探讨的是服务器的I/O处理单元、请求队列和逻辑单元之间协调完成任务的各种模式, 这一节我们介绍逻辑单元内部的一种高效编程方法:有限状态机(finite state machine)

// code:8.1 

STATE_MACHINE( Package  _pack )
{
    PackageType  _type = _pack.GetType();

    switch( _type )
    {
        case type_A:
            process_package_A( _pack );
            break;
            
        case type_B:
            process_package_B( _pack );
            break;
    }
}
// code:8.2

STATE_MACHINE()
{
    State cur_State = type_A;
    while( cur_State != type_C )
    {
        Package  _pack = getNewPackage();
        
        switch( cur_State )
        {
            case type_A:
                process_package_state_A( _pack );
                cur_State = type_B;
                break;

            case type_B:
                process_package_state_B( _pack );
                cur_State = type_C;
                break;             
        }
    }
}

 


8.7提高服务器性能的其他建议

8.7.1池

根据不同的资源类型,池可分为很多种,常见的有内存池、进程池、线程池和连接池。

  • 内存池通常用于socket的接收缓存和发送缓存
  • 进程池和线程池都是并发编程常用的“伎俩”
  • 连接池通常用于服务器或服务器机群的内部永久连接

8.7.2数据复制

高性能服务器应该避免不必要的数据复制,尤其是当数据复制发生在用户代码和内核之间的时候。

8.7.3上下文切换和锁

并发程序必须考虑上下文切换(context switch)的问题,即进程切换或线程切换导致的系统开销。

并发程序需要考虑的另外一个问题是共享资源的枷锁保护。锁通常被认为是导致服务器效率低下的一个因素, 因为由它引入的代码不仅不处理任何业务逻辑,而且需要访问内核资源。 如果必须使用“锁”,则可以考虑减小锁的粒度。


这里只是整理大概内容,更详细内容,请看书📖《Linux高性能服务器编程》 游双 著,机械工业出版社]

posted @ 2019-11-24 14:49  He_LiangLiang  阅读(131)  评论(0编辑  收藏  举报