Author:放翁(文初)
Date: 2010/11/23
Email:fangweng@taobao.com
mblog: http://t.sina.com.cn/fangweng
blog: http://blog.csdn.net/cenwenchu79/
问题的诞生与思考:
一. 依赖之苦
做过不少业务系统,最痛苦,最无奈的就是性能和稳定性依赖与外部系统处理能力和可用时间。而TOP是典型的Proxy模式,它自身的性能在传统的Web容器处理模式下依赖与后端服务处理能力。
TOP在Web容器线程连接有限的情况下,最差的处理能力就是min(A,B,C),也就是一个时刻的处理都是在处理最慢的系统的请求。因此产生这么几个问题:a.通过压力测试评估TOP自身的处理能力,并且来预估所需要的服务器容量将变得很不可靠。 b.可能由于某一个后端服务的不正常导致正常服务也无法通过TOP被外部访问到,使得局部不可用演变成为整体不可用。
延展考虑,由于容器端线程不支持根据业务情况分配,因此无法实现静态或者动态的线程资源按业务重要性或者服务健康状况做调整,这样对于一个集成了众多重要程度不同,能力参差不齐的服务平台来说很难最有效的将合理的资源倾向于重要且健康的服务。
二. 轮询之苦
首先,耗时的业务处理,例如对于历史订单的数据查询,后台操作会消耗较多时间,而传统请求是阻塞式的,因此超时设置成了一个难题,设置太大,传统容器的连接资源有限,设置短了无法满足业务需求,为今之计只能够将一个请求拆成两个请求,一个是发起处理的通知请求,后面是轮询获取结果的请求。
其次,在业务系统设计中,或多或少的会有基于状态变更事件来触发事件处理的场景,淘宝的业务体系更是如此。买家和卖家分别是淘宝的两个角色,相互之间的操作贯穿于整个交易主流程(下单,付款,发货,确认收货),两个角色之间是通过交易这个虚拟对象的状态迁移来实现交互的,而状态迁移的动作是由任何一方无预见性的实施的,因此做工具的应用需要能够接受到状态变化事件通知,当前只能通过应用轮询获取数据来实现。
轮询一方面使得开发者软件设计复杂度高,自身系统消耗大(时间间隔设置,容错机制等等),另一方面使得TOP服务器压力增大,无效请求浪费系统资源。
三. 容器资源之苦
有人会说轮询这件事情干吗不直接将数据推送过去,告知这些ISV,反正他们也都是B/S结构,服务器提供回调地址就可以了。的却这是最常见的Notify的模式,淘宝内部也有一个Notify的中间件。但是在现在的网络状况下,主动推送数据到ISV的服务器上基本不靠谱,对方响应速度的快慢直接影响到我们投递这些数据需要多少服务器,投递的策略如何?(如何处理失败的投递消息,重试机制如何),从这里可见容器连接池之苦。另一方面从第一个苦描述中可以看到,其实如果容器资源足够多,那么就可以无限制放大入口,也就不会受之于后端的依赖系统处理能力,但今天大家看看自己传统的Web应用服务器(jboss,tomcat非apache,nginx)连接池配置的数字就知道这是不靠谱的。
技术背景概述:
管道化子任务切分:
我外婆以前是做白铁加工的,做一个锅子基本需要这些步骤,每个步骤都需要一些工具,传统最简单的做法就是一个人做到底,然后工具都摆在身边。(这也就是我们现在传统容器的模式,从请求进入到整个业务处理结束),要提高效率的做法如下:
1. 增加人手,依然采用一人处理到底的模式。在人和工具无限量的情况下是最简单和行之有效的方式。
2. 切割流程,最大化资源利用率,每个子任务所需的资源不同,因此在完成子任务后就将资源共享给其他人而不是占用所有资源到整个流程结束。这种优化带来的最明显的效果:
a) 轻量级子任务完成所需要消耗的资源最小化。(例如第一阶段处理消耗时间很短,那么锤子和钳子的需求量将会最小)
b) 重量级子任务能够得到更多的资源和线程来处理。如果第一阶段和第二阶段本身资源消耗是相互影响的,比如第一阶段资源分配消耗内存,第二阶段资源分配也消耗内存,那么第一阶段的资源占用少了以后,自然可以给第二阶段资源分配提供了便利,其次如果总资源有限,第一阶段也在等待资源的ready,那么此时的线程将会等待在第一阶段的资源分配上,此时线程空消耗,但如果降低了第一阶段的消耗,线程满负荷运作,则线程完成第一阶段所有任务后可以支援第二阶段的工作。
3. 当子任务可以“降级”的情况下,分解任务,根据资源状况来并行处理,并且适当的“丢弃”非关键子任务可以提高效率,增加稳定性。
总体上来说,可以把原本一个任务拆成管道形式的子任务(管道化也就是每个子任务的上一个任务的输出是当前子任务的输入),然后根据子任务的情况来选择是否交由不同的线程并行化执行。(这个判断很简单,子任务是否是有限资源且较轻量化的,子任务的资源占用多少是否会影响到其他任务的资源分配情况,如果这两点都不成立,则保持简单处理模式即可,最多可以将某一些子任务“降级”)
管道化的作用:1.将任务各个阶段梳理清楚。(降低子任务之间的耦合度,为并行或异步处理提供基础)。2.最大化资源利用率,便于流程整体优化。
事件驱动模式:
事件驱动模式其实在设计中被大规模使用,思路概括起来就是:对象脱离线程,状态脱离事务。回到第一个做锅子三个流程的实例说明,也许在第二阶段,某人拿起了一个已经完成第一阶段的半成品在等待第二阶段的资源Ready,这时候如果他放下这个半成品,先去做已经可以做工作的第一或者第三阶段的半成品,然后等到另一个人做完后释放第二阶段资源时通知他时,他在去安排做第二阶段的工作,那么效率会更高。
那么可以发现,管道化是从释放资源被占用的角度去提升整体工作效率,而事件驱动模式是从分离工作实施者和工作资源的角度去提升工作实施者的工作效率。一个是工作者是有限而宝贵资源,一个是子任务在完成过程中所需资源是宝贵资源。
由此看来,事件驱动模式与管道模式不同,应该是不需要有评判标准都可以实施的一种优化策略。其实不然,事件驱动模式也有自己的弱点:1.设计复杂。(过于松耦合的结构,使得原本事务中有顺序的操作需要更多的检查,容错和调度)2.性能可能会受到影响。(线程上下文的切换,中间结果的拷贝)3.延时问题产生。对于事件的产生一种是主动推送,一种是轮询,轮询就牵涉到时间片大小的问题,在性能和及时性权衡的情况下最后得出合理的设置,但是对于整个事务来说一定是消耗了。
上面描述的两个概念在后面的Web请求异步化处理及订阅模式中都会用到,同时在支持Servlet3及Comet Streaming(Comet push)的容器整体架构上都会被用到,优劣上面做了简单的描述,后面从业务架构到系统架构都会有最实在的设计说明。
业务架构设计:
基于上述问题,通过两步走来解决。首先采用支持打破传统http request生命周期管理的Web容器(很多人说可以自己写,其实Web容器写起来并不是最麻烦的,如何做好兼容和照顾好每一个细节才是漫长发展的道路)。其次在容器新的线程生命周期管理基础上封装业务框架,为开发者屏蔽底层异步化和事件驱动模式带来的复杂流程管理内容。
待续...