关于pipeline架构的几点说明

最近要将系统改造成pipeline架构,所以研究了一下pipeline相关的理论,并看了看servlet filter, struts2 interceptor, spring mvc interceptor, webx pipeline和netty pipeline的一些源码。 这里总结一些小点:

结构:

1.
Pipeline的类模型由Pipeline, Valve 和 Context 组成。 Pipeline代表一个执行流,Valve代表执行流中的一个节点,Context是执行时的上下文信息,它一般由两部分组成: request/response + 当前流的执行状态。 

2.
有的系统只能有一个Pipeline, 有的则允许配多个。 在struts2中,一种interceptor的组合,就代表了一个Pipeline;多种组合,意味着多个Pipeline.

用法:

1.
有的pipeline流是在同一层次上,每个valve处理的request/response对象基本上是同质的。比如servlet filter和各种mvc框架,所处理的事情都是web层的,所处理的context对象就是http request或框架自定义的javabean.

2.
有的pipeline流则是纵向的,从上层流到下层,或从下层流到上层,这时每个valve所处理的request/response对象一般是异构的。协议栈就是个典型的例子:下层valve处理字节流,上层Valve处理字符流。 不过,为了适应pipeline架构,这些异构的context对象必须有共同的基类,并且把这个基类作为各个valve的输入参数。

3. 从请求处理的角度来说,
Pipeline可以代表整个执行流,也可以只用作请求被最终处理前的Interceptor.  Webx, netty中的Pipeline会将最后一个Valve作为请求处理者(一般称为Request Handler),而servlet filter和struts2 interceptor只作请求拦截、加工,真正的请求处理者则是servlet和action.

程序流:

1. 一个完整的Pipeline执行流一般是个环路: Valve1 => Valve2 => Valve3 => Valve2 => Valve1.
从拦截的角度说,valve既是前置拦截(下一个valve执行前),又是后置拉截(下一个 valve执行后)

2. 为了达到前后双拦截的目标,程序实现有两种方法。
有一种方法是使用forEach方式分别执行每个拦截器的前置拦截方法,然后又以相反的顺序分别执行每个拦截器的后置拦截方法。这种做法比较直观,可以说直接映射了人类的思考方式。 Spring MVC Interceptor差不多就是这种做法。

3.
另一种做法,也是主流的作法,则会把一个valve的执行嵌套在前一个valve的执行里面: 每个valve的执行中代码中会有一句"nextValve.invoke()",然后在这句代码的前后做一些拦截。这种方式有点费解,而且由于嵌套过多容易造成比较深的调用栈。 不过它有一个好处: 更方便地中断执行流并处理异常(下详)。

4.
执行流须提供中断机制和异常处理机制,比如一个鉴权filter在发现用户未登录时需要立即返回403并中断流,当一个valve出现异常时这个异常需要被捕捉、处理。 如果Valve是嵌套执行的,这些机制会很方便实现: 鉴权filter发现用户未登录时,不调用nextValve.invoke()即可中断pipeline; 至于异常处理,Struts2默认使用ExceptionMappingInterceptor作为Pipeline中的第1个valve, 它会把nextIntercetpor.invoke()放在自己的try/catch块中。如果valve不是嵌套执行,而是由pipeline通过forEach编排执行的,那么这些非正常流的“扳道工”职责就必须由pipeline自己来承担, 这可能会使pipeline不够精简;如果来了新的逻辑(比如异步执行),又得改pipeline,导致pipeline不太稳定。

设计模式:

状态/无状态, 单例/非单例. 如果不怕麻烦,可以每来一个请求,就生成一个Pipeline对象和一堆Valve对象,再把request和上下文信息用作这些对象的状态;但是,用单例的风格更符合当代程序员的习惯,也更容易与spring结合。 那么,全单例并且都是无状态? 不尽然。虽然Pipeline的结构本身是静态的,一个pipeline结构可以作成一个单例;但Pipeline的执行流是有状态的,比如当前执行到了哪个valve,是需要一个变量来维护的。 所以,可以做一个单例的对象Pipeline, 代表Pipeline的结构;另做一个对象PipelineContext,代表Pipeline当前的执行流;至于Valve,它相当于一个stateless service, 做成单例即可.

Leave a Comment

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.