SEDA简介与源码解读(一)



简介:

——————————————————————————————————————————————————————–

/

 
分割线内内容纯粹引用

 原文作者:朱之光

 
原文地址:http://larryzhu.bokee.com/6779982.html

/

一、前言

Staged Event Driven Architecture (SEDA) 是加州大学伯克利分校研究的一套优秀的高性能互联网服务器架构模型。其设计目标是:支持大规模并发处理、简化系统开发、支持处理监测、支持系统资源管理。本文会先对两种目前被广泛使用的网络服务器架构模型进行介绍。然后对SEDA进行详细描述。

 

二、当前流行的两种并发处理编程模型

1、 多线程服务器 (Threaded Server)





工作原理:对于每一个requestdispatcher会为其创建并分配一个线程。该线程负责这个请求的处理。这种方式 又名(Thread-per-request)。

优点:执行粒度是整个完整的处理流程。处理逻辑清晰,容易开发。

缺点:是当随着处理请求不断增加,导致并发执行的线程数量太多。过多的线程数量导致系统在线程调度和资源争用上的开销过大。引起系统性能急剧下降。导致系统处理能力下降。

 

改进措施:线程池(Bounded Thread Pools

系统最多只能创建一定数量的线程。当所有线程都饱和运行时,新到达的处理请求只能等待,或者被抛弃。

缺点:

执行粒度仍然是完整的处理流程。难以检测系统性能瓶颈的根源以及进行相应调整。

 

2、 事件驱动并发处理(Event-Driven Concurrency)



将处理流程分割成多个步骤,每一个步骤都实现为一个有限状态机(FSM)。

工作原理:所有的处理请求会作为Event进入系统。由Scheduler负责传递给相应FSMFSM的处理结果也以Event形式输出给Scheduler。新的Event会再次被Scheduler进行转发给下一个FSM。直至处理完成。

 

优点:

1、随着处理量的增加,系统负荷是以线形增长。当达到系统饱和处理能力后。系统的处理能力不会下降。

2、由于将各个处理步骤独立实现,可以很容易的进行系统监测和调整。

 

缺点:

Scheduler的设计和实现过于复杂。针对于不同的应用和系统的逻辑变更需要不同的实现。

 

三、SEDA架构



(近似于Event-Driven Concurrency,但是没有其中的Scheduler)

将每一个处理步骤独立为一个Stage



Stage结构:

1、 一个接受输入的Event Queue

2、 一个应用开发者编写的Event Handler

3、 一个Controller用于对执行过程进行控制。包括并发线程数量,批处理数量,…;

4、 一个Thread Pool用于并发处理;

Stage的输入通过Event Queue获得。Stage的输出会以Event形式推送到其他StageEvent Queue中。Stage之间的这种连接关系由应用开发人员指定。

 

带来的问题:Event Queue尽管减少了模块间的耦合性,但是会降低响应速度。

 

四、小结:

SEDA架构将应用的整个处理过程分割为多个步骤即Stage。每个Stage可以独立进行开发。同时Stage之间通过Event Queue来进行通信,可以降低耦合性。可以以很小的成本来适应将来的系统逻辑变化。

同时系统提供了标准的资源控制,使得应用开发人员只需要专注于实现Event Handler的内部逻辑。而无须关注多线程、资源共享、

同时可以在运行时对于每一个Stage的运行情况进行监测以及调整。
———————————————————————–分割线————————————————————————————

最近我也拜读了这篇来自哈佛的研究论文【1】,本来想翻译的,翻译了大概五分之一,感慨文章之长,速度之慢,再加上自己事情又多,热情日益减退,也就不想继续下去了。

其实,值得我们借鉴的是它的设计思想(虽然有些人认为,它学术味道浓厚,并没有考虑到实际应用中的复杂逻辑),事实上淘宝开源的Netty【2】(一个基于事件的异步通信框架)采用的就是标准的SEDA架构。

源码分析

网络上已经有一些对于SEDA的介绍和研究。在论文里,提到了一个基于SEDA的实现——sandstorm,最近也在看源码。近期想推出自己对于该项目源码的解读(源码在最后提供下载)。

下图是sandStorm的开放接口(位于package:seda.sandstorm.api),虽然我已经尽力想让UML图清晰一点,但由于关系比较复杂,还是显得有些混乱。
                                  

接下来,让我们来将其分割,看看各个接口提供哪些功能:

StageIF:表示一个应用程序的层。应用程序并不会直接实现StageIF,而是实现EventHandlerIF。StageIF用来被一个事件处理器使用,用它来访问其他层。StageIF的实例通过ManageIF.getStage()获得。

EventHandlerIF:表示一个事件处理器,SandStorm组件的基本单元。它是一个应用程序所有模块需要实现的一个基本接口。

SourceIFSinkIFQueueIF这三个接口用来实现事件队列。继承关系参见下图:
                                    

其中,SinkIF、SourceIF分别表示一个队列的两端。SinkIF表示“槽”,形象化地理解为连接着前面一个层的“水槽”,事件都从这里“流入”队列,所以它只支持事件的入队操作。SourceIF表示“源端”,理解为我们要从事件队列获取的“事件源”以进行处理,所以它只支持出队操作。QueueIF直接继承了这两个接口,并没有增加自己额外的操作,从而形成了一个 “队列”。

可以看到,这样的设计十分地灵活。它并没有直接在物理上实现一个队列的数据结构,而是用实现入队、出队操作在逻辑上实现了一个“队列”。这样可以获得两个好处,你可以自行实现入队、出队时的处理逻辑;同时,只要你获得了该接口,你就获得了可以将事件入队、出队的“权力”。

QueueElementIF:表示一个事件基接口,既然QueueIF表示一个事件队列,那么它的元素自然是一个事件。任何定义的“事件”都必须实现该接口。已经提供了一些定义事件如下图:
                                      

ClassQueueElementIF:基于“等级”的事件;

SinkCloggedEvent:Sink被阻塞事件(当Sink满或者某些条件阻止一个给定的元素被服务时,会触发该事件);

SinkClosedEvent:该事件表示一个Sink已经被关闭,要么是应用程序有意关闭,要么是由于一个错误条件被无意地关闭。当一个sink不再被服务时,就认为它被关闭了;

SinkDrainedEvent:该事件表示一个sink已经被处理;

SinkFlushedEvent:该事件表示一个给定的sink被成功地刷新,它通常由一个sinkIF.flush()的调用生成。

ProfilerIF:一个ProfilerIF表示对于系统行为的跟踪记录,如果系统运行在跟踪记录模式,应用程序可以对ProfilerIF获得处理(通过调用ManagerIF.getProfiler());

ProfilableIF:一个实现了ProfilableIF的对象可以被一个ProfilerIF进行跟踪记录。通常,这意味着该对象有一个被分配的大小(比如队列长度,列表长度,内存大小等);
ManagerIF:这是一个管理接口,提供对上面一些接口的访问等;

                                             

它给应用程序提供了一些运行时服务;

 

SignalIF:它也是一个事件,表示一个信号事件,所有需要实现一个信号通知的事件都需要实现它;

StagesInitializedSignal:它是对SignalIF的一个实现,它表示指定在sandStorm的配置文件中的所有层,或者在init()方法被调用期间基于指定的初始化配置创建的层已经被实例化。注意,额外的层可以在实例化完成之后创建;

SignalMgrIF:对于系统级别的“信号”、层可能希望使用的消息事件的实例化或控制;

EnqueuePredicateIF:入队断言接口,它允许用户指定一个方法,该方法能够在sink上执行入队操作的时候屏蔽元素——要么接受、要么拒绝。该方案能够被用来实现很多有趣的“条件式加载”策略,比如,简单的阈值控制、速率控制、基于信用的流量控制等。注意:入队断言运行在enqueue()的上下文,这意味着它必须简单而且快速;

ConfigDataIF:它用于给层传递配置参数,当一个层被实例化时,一个ConfigDataIF被传递到它的init方法;

SingleThreadedEventHandlerIF:空接口,它指示系统,一个事件处理器必须是单线程的;

源码下载

SEDA-Release

SEDA-Eclipse工程可直接运行

*引用


【1】:http://www.eecs.harvard.edu/~mdw/papers/seda-sosp01.pdf

【2】:http://rdc.taobao.com/team/jm/archives/423



版权声明:本文为博主原创文章,未经博主允许不得转载。