适于互联网的SEDA高并发架构

要点:
troughput=1000*threadNum/perTaskCost
threadNum的变化会影响perTaskCost


一、前言

最近看了一篇博士毕业论文(Matthew David Welsh--An Architecture for Highly Concurrent, Well-Conditioned Internet Services),于是将主要思想写了出来,旨在传播别人的思想,这篇论文探讨一种使用于Internet Services高并发的可扩展性架构,比较了传统的模型Thread-based模型和Event-driven模型,并提出了自己的模型SEDA。

二、背景

     随着互联网的高速发展,各种各样的互联网服务相继出现,现在不再局限于静态页面的访问,如股票交易,电子商务,即时消息服务,点对点文件共享,应用托管等 等,与以前的静态页面发布服务相比,这些动态的服务,每一请求需要消耗更多的CPU,I/O等资源。导致管理和分发这些服务的系统日益复杂,包括Web Server, Cache, Middle-tier Application Server, Database等。

三、传统的并发编程模型

      传统的并发编程模型主要有两种,一种是Thread-based concurrency,另一种是Event-driven concurrency。下面分别介绍这两种模型,并比较这两种模型的优劣。

3.1 Thread-based concurrency模型

22939760_1300886218PsOW.png

1 Thread-based concurrency模型,系统为每一个请求分配一个ThreadProcess,这个ThreadProcess负责处理这个请求并把结果返回给客户.这幅图中边代表控制流。

      这是一种最常见的并发编程模型,为每一个请求分配一个线程或进程。如今的现代编程语言和操作系统对这种模型有很好的支持。

      在这种Thread-per-request模型下,如图1所示,每一个被接受的请求被指派给一个线程或进程去处理。锁,信号量等被用于同步Thread/Process对共享数据结构的访问。操作系统负责调度这些处理每个请求的Thread,来保证每个请求可以透明的分享计算机的CPU,I/O等资源。

       这种Thread-per-request模型,编程起来简单,可以将处理一个完整请求的代码编写在一个代码路径中,并且据有很好的隔离性,每一个请求的处理都被隔离在一个Thread/Process中。请求请求之间的交换只有通过访问共享数据结构来实现。

      但是thread-per-request也引起了一些挑战,资源管理,可扩展性等。资源管理粒度太粗,一个系统支持的线程数是有限制的,导致可扩展性差。

22939760_13008863858VKu.png

2 thread-per-request模型的性能This benchmark measures a simple threaded server that dispatches a separate thread for each concurrent request in the system. After receiving a request, each thread performs an 8 KB read from a disk file; all threads read from the same file, so the data is always in the buffer cache. Threads are pre-allocated in the server to eliminate thread startup overhead from the measurements, and requests are generated internally to negate network effects. The server is implemented in C and is running on a 4-way 500 MHz Pentium III with 2GB of memory under Linux 2.2.14. As the number of concurrent requests increases, throughput initially increases until about 8 threads are in use. Adding additional threads causes throughput to degrade substantially. Response time becomes unbounded as request queue lengths increase; for comparison, we have shown the ideal linear response time curve (note the log scale on thehorizontal axis).

      很重要的一点就是设计多线程(进程)模型是为了共享计算机的资源。随着线程(进程)数的上升,这个模型引起了更高的资源消耗,操作系统在这些线程(进程)之间的频繁切换,内存交换,缺页更加频繁,锁竞争更加激烈等,将急剧降低系统的性能,急剧降低系统的吞吐量和增加每个请求的响应时间。从图2中我们可以看出,随着线程(进程)数的增加,在线程(进程)数较低时,系统的性能是上升的(系统的吞吐量增加,每个请求的响应时间线性上身),但是到达一个临界值,随着线程(进程)数的增加系统的性能急剧下降(系统的吞吐量急剧下降,每个请求的响应时间急剧上升几乎是指数上升)。更重要的是一个系统所能支持的线程(进程)数是有限的,线程(进程)数超过一个理想值后将引起系统的性能急剧下降。

      当然为了控制系统的线程(进程)数在一个理想值,避免过多的线程(进程)导致系统性能下降,可以引入线程(进程)池。在这种方式下,所有的请求被分配给一组固定大小的线程(进程)。当所有的线程(进程)忙于处理请求时,新的请求被安放在队列中等待处理。

Thread-per-request模型的资源管理问题,Thread-per-request模型中的一些重要信息,如每一个请求的资源消耗,Blocking I/O等信息隐藏在OS的Thread scheduler中,应用层面基本上不可能去合理调度每一个请求的处理,这些是被OS给剥夺了。所以服务器基本上不可能合理的去调度每一个请求,只能祈祷OS调度了。当然我们也可以去实现应用层的线程,这样的复杂度何代价很大。

3.2 Event-driven concurrency模型

     考虑到Thread-pre-request模型的可扩展性问,人们发明了Event-driven concurrency模型。在这种模型中,服务器由一组线程/进程(一般是one per CPU)循环处理各种来自队列的事件(Event)。OS,应用都可以产生这些事件,表示某些操作需要执行,如:network或disk I/O准备就绪,或者完成通知,定时器,或者应用层的自定义事件。

22939760_1300886551Ir5r.png

3 Event-driven concurrency模型

      在Event-driven concurrency模型中,每一个请求在系统被表示成一个Finite State Machine(FSM,有限状态机)。每一个FSM的状态表示请求的一系列的操作。比如一个静态页面的请求可能包含这些状态,如图4:

      1. Read request from network;
      1. Parse request (e.g., process HTTP headers);
      1. Look up disk file corresponding to request;
      1. Read file data;
      1. Format reply packet;
      1. Write reply to network.

22939760_13008867301PM5.png

4 Finite state machine for a simple HTTP server requestThis figure depicts a static HTTP server request as a finite state machine (FSM) as used in an event-driven system. Each state represents some aspect of request processing, and edges represent transitions between states, triggered by incoming events or the completion of the processing for a given state.

      当一个请求的事件到来,系统分配线程组中的一个线程处理这个请求的对应状态的操作。这些对应状态的操作一般很短,处理完后请求转换到FSM的下一个状态等待系统处理,这个线程又会被系统分配去处理下一个请求的这个状态。

       Event-driven concurrency模型中实际上通过FSM来在应用层调度每一个请求,通过在请求之间切换来实现,通过使用一组过定数量的线程来处理请求的各个状态。一个线程可以处理每个请求的一种状态,或者是多个状态,或者是多个线程,这就依赖于具体实现了。这种模型要求每一个状态的操作是短暂的并且是非阻塞的,所以Event-driven concurrency模型一般都用了非阻塞的I/O接口。Event-driven concurrency模型中的事件调度类似于OS的thread调度,这样Event-driven concurrency模型中可以设计应用层的调度策略。

       Event-driven concurrency模型一般比Thread-per-request模型更据可扩展性,利用FSM来表示每一个请求,比Thread-per-request模型用Thread(Process)来表示一个请求更据可扩展性,也降低了系统中的Thread(Process)数,降低了系统消耗的资源。

     如果设计的好,Event-driven concurrency模型可以支持很高的并发度,并且随着并发度的增加,系统的整体性能的降低在一个可以接受的范围内,如图5所示。这种高性能得益于Event-driven concurrency模型降低了系统中过多的线程(进程)的使用,而降低了系统的资源消耗。

     如图5所示,随着请求数的增加,系统的吞吐量增加,每个请求的响应时间缓慢线性上升,直到达到饱和,这时受限到计算机的物理硬件,CPU,内存等,达到饱和后没个请求的响应时间急剧上升,因为系统中有太多的请求等待调度。当然随着请求数的进一步上升,系统的吞吐量也会急剧下降,这时由于系统的物理内存消耗殆尽,引起频繁的缺页何内存交换,TLB失效等。

22939760_130088685883K2.png

5 Event-driven server throughput: This benchmark measures an event-driven version

of the server from Figure 3. In this case, the server uses a single thread to process tasks, whereeach task reads 8 KB from a single disk file. Although the filesystem interface provided by the operating system used here (Linux 2.2.14) is blocking, because the disk data is always in the cache,this benchmark estimates the best possible performance from a nonblocking disk I/O layer. As thefigure shows, throughput remains constant as the load is increased to a very large number of tasks(note the change in the horizontal axis scale from Figure 3), and response time is linear (note thelog scale on the horizontal axis).

      Event-driven concurrency模型能很好的支持高并发,但是同时也对开发者提出了更高的要求,一个很重要的就是要求事件处理代码短小精干,尽可能的执行非阻塞操作,避免拖延或阻塞事件处理线程,来保证每一个请求的公平性。

      Event-driven concurrency模型的关键是设计一个高效公平的事件调度器,同时考虑高效和公平,这里就有很多要考虑的,如请求的优先级,事件的优先级,事件处理的顺序,资源的消耗等。考虑到请求的优先级,事件的优先级,事件的资源消耗都于具体的应用相关,所以Event-driven concurrency模型的通用性必然不理想。

四、SEDA并发模型

4.1 SEDA整体介绍

         SEDA将应用分通过事件队列链接的网状Stage,每个Stage由一个线程池,一个业务相关的事件处理器(Event Handler),一个事件输入队列和一个多个资源控制器组成。如图6所示。

22939760_1300887035y4z4.png

6 Staged event-driven (SEDA) HTTP server: This is a structural representation of Haboob, the SEDA-based Web server, described in detail in Chapter 6. The application is composed as a set of stages separated by queues. Edges represent the flow of events between stages. Each stage can be independently managed, and stages can be run in sequence or in parallel, or a combination of the two. The use of event queues allows each stage to be individually load-conditioned, for example, by performing admission control on its event queue. For simplicity, some event paths and stages have been elided from this figure.

SEDA的几点重要设计:

      Efficient, event-driven concurrency:SEDA并发模型依赖于Event-driven concurrency模型来支持高并发。利用一组线程来处理每个请求,而不是每一个请求一个线程,利用Nonblocking I/O来避免资源的阻塞。

      Dynamic thread pooling:为了避免设计事件调度和达到非阻塞操作的要求,SEDA利用一系列线程组(Thread pool),每一个事件处理器利用一个动态的线程池。这样就可以利用OS的线程调度来调度事件的处理,并且可以允许事件处理代码阻塞一小段时间,因为有多个线程(一个动态线程池)可以处理一个事件。

Structured queues for code modularity and load management:通过事件队列将一个应用分割成一系列的Stage,可以让应用开发者只关注具体的业务逻辑(事件处理器),然后通过队列将各个State组装成应用,也可以动态的添加何卸载Stage。这样可以独立开发每一个Stage。

       应用也可以在每个Stage中控制每一个请求的执行,如执行路径的改变,或终止请求等。每一个Stage可以更具自身的状态来控制请求的执行,如某一个Stage的负载太高,可能会拒绝处理这个请求。当然也可以让资源管理与控制在更细的粒度,没一个Stage都可以有自己的资源管理宇控制。

Self-tuning resource management:

4.2 Stage

        Stage是SEDA中的核心基本单元,每一个Stage是一个自包含的应用组件,包含一个事件处理器(Event Handler),一个输入事件队列(incoming event queue),一个线程池(thread pool)和一个或多个资源控制器(Controller)。资源控制器负责资源消耗的管理,调度,线程分配和回收等。Stage的线程从事件输入队列批量取出事件,并调用事件处理器代码,事件处理器处理每一个事件,并输出0个或多个事件到其他的Stage的事件输入队列。如图7所示。

22939760_1300887157157h.png

7 A SEDA Stage: A stage consists of an incoming event queue, a thread pool, and an application-supplied event handler. The stage’s operation is managed by a set of controllers, which dynamically adjust resource allocations and scheduling.

你可能感兴趣的:(架构模式)