Web服务请求异步化介绍(实践篇)
Author:放翁(文初)
Date: 2010/8/5
Email:fangweng@taobao.com
围脖: http://t.sina.com.cn/fangweng
在概念篇介绍完以后,开始实际的对TOP开始做技术改造。(这篇东西更像是对短期工作的总结和汇报,写的不是很详实,后续会有一个ppt来深化异步化的一些思想)下面将第一阶段的工作做个总结,第一阶段主要做了以下几个方面的事情:
1. 典型taobao后台应用(主要是用到了多个内部组件)的Jetty迁移。
2. TOP管道化体系的异步改造。
3. 测试不同容器不同模式下的应用处理能力,并通过数据得出结论。
一.应用迁移及容器部署
这次迁移主要的工作就是在服务框架的迁移,服务框架内部是小的OSGI容器,和当初在阿软做服务平台一样,当时是SCA容器,道理都一样,如何打通SCA和OSGI与应用服务器,主要涉及的就是ClassLoader的互通。
服务框架组的同学当时做过一个Jetty的内置互通支持版本,其是修改了Jetty的代码,在启动过程中植入了外部应用容器的初始化和循环互通。考虑到将来Jetty升级的方便,自己还是从新考虑做一个外部互通的支持版本。(期间波折就不再此说了,大概说一两点关键之处),首先是要启动外部容器,由于Jetty可以支持LifeCycle的Bean在Jetty容器启动时优先装载,因此外部容器就实现接口,在Jetty的Server配置中设置即可。然后需要将两个容器互通(相互可以引用对方的服务接口),这需要在应用上下文构建的时候相互关联两者的classLoader,一种方式在Server的AppDeployer部署过程中植入,一种在指定的AppContext部署中植入。这两种方式就是Jetty支持的两种模式,一种配置在etc目录下的jetty总配置文件中(Server配置中),一种配置在contexts目录下(这种就是现在比较推崇的片段化部署),我选择了后一种,因为对我来说不是所有应用都需要支持容器互通的,当前只有TOP这个应用。
容器部署,Jetty真的是太干净了,首先类似于xml的解析实现没有(jdk可只有框架接口),log4j没有,jndi需要另外引入插件支持,lib下的jetty插件按需载入(在start.ini,start.config和启动脚本中可指定),遥想当年的jboss也应该是很干净,回顾今天的部署应用的jboss已经被贴了N多膏药。因此最终要的几个配置就是:启动脚本,etc下的jetty.xml(可以指定其他配置),start.config (可以从jetty的jar包中获取出来自己指定和配置),start.ini(启动的默认配置),contexts下的应用上下文配置。
容器部署和外部插件迁移虽然不是异步化的工作,但是在异步化以前一定要搞定,否则就无法谈到后续的应用迁移,总的来说jetty的模块化和扩展做的很好,基本上任何步骤都能够替换和实现新的逻辑。(有需要jetty配置和hsf外部插件支持的同学可以直接找我)
二.TOP管道化体系异步改造
TOP的服务接入层就是由很多个管道切面组成的,流控,安全,业务校验,路由,协议转换,响应格式转换等等,因此TOP自身很适合采用管道化流程体系来构建,同时采用管道化体系构建能够简单的隔离业务逻辑,实现服务降级,新功能beta发布。引入异步化概念后,对于开发者其实不需要过多了解,仅仅只需要配置异步化的管道,交由管道框架和容器协作来完成异步化的请求处理。
这里顺带在提一下上一篇中说到的异步化的作用:差别化耦合系统的体系设计,差别化流程中流程处理。异步化不会节省业务事务处理时间(反而会增加),也不一定会提高系统可用性和稳定性(起码全局上来看,整体复杂度增加,异常波及会被隔离,但是可能发现也较晚),也不一定会节省资源(异步化往往是空间换时间,将业务状态独立于处理,提高处理线程的利用率,代价是增加了交互和存储的成本)。
因此一直困扰TOP的服务分流和隔离可以通过异步化方式得以实现,方式就是将系统处理和业务处理流程隔离,系统处理用少量线程就可以支持大并发请求,同时将后续的业务处理交给业务工作池,而业务工作池的资源分配完全可以通过业务特征设置权重,也可以通过后台服务质量的反馈来自动调整,最终实现对服务使用者服务差别化,对不同质量的服务提供差别化的流量引入。
同样和上一篇文章谈到的多种模式一样,改造支持两种模式,适合不同场景。纯粹的异步方式往往需要借助于类似于epoll的io事件驱动模式来彻底高效的分开两个系统,否则就是采用伪异步。
1. Pull & Check Status & Resume Mode
这种模式对于后端服务的要求较高,首先服务使用需要支持异步方式,其次要求在完成服务后修改任务状态,而对于依赖方来说,会通过轮询的方式去获取结果。流程图如下,不过第二个是第一个的改进,效果不错,前者是检查所有的任务状态,确定是否完成,放入任务的是前端系统,后者是不需要检测任务状态,凡是获得任务,即表示任务完成,放入任务的是后端服务提供系统。
2. Push & Complete Mode
这种模式下对于后端服务来说可以只提供同步服务或者也支持异步服务,TOP一期改造采用这种模式,后端服务采用同步模式,具体测试结果参看后面的测试部分。
三.测试
场景:
后台服务执行时间为1秒,no think time,容器为Nginx+Jetty
并发用户数
|
Jetty连接池线程数量
|
业务线程池线程数量
|
load
|
TPS
|
200(异步)
|
200
|
200
|
0.8
|
180
|
400(异步)
|
200
|
200
|
0.7
|
208
|
200(同步)
|
200
|
200
|
1.16
|
190
|
400(同步)
|
200
|
200
|
1
|
190
|
200(异步)
|
200
|
400
|
0.95
|
195
|
400(异步)
|
200
|
400
|
0.84
|
390
|
200(同步)
|
200
|
400
|
0.82
|
195
|
400(同步)
|
200
|
400
|
0.88
|
195
|
200(同步)
|
400
|
400
|
0.7
|
174
|
400(同步)
|
400
|
400
|
0.85
|
350
|
结论:TPS还是取决于两个线程池的线程多少,在异步化后系统消耗并没有明显增加,容器连接池的放大和业务线程池的放大都可以提高处理效率,同时业务线程池较为轻量,容量翻倍处理基本也是翻倍,容器的线程池放大处理能力会有衰减。
场景:
200并发用户,调用user.get服务,no think time,容器线程池400,业务线程池500.
容器
|
load
|
TPS
|
Nginx + Jetty
|
6
|
800
|
Jetty
|
8
|
620
|
结论:没有前段Nginx对于数据缓冲处理,jetty处理效率一般,因此需要假设Jetty作为前段预处理容器(必要的时候也可以作为反向代理)
场景:
200并发用户,调用user.get服务,no think time,容器线程池200,业务线程池500.
容器
|
并发用户
|
请求处理模式
|
TPS
|
Apache + Jboss
|
200
|
同步
|
507
|
Nginx + Jetty
|
200
|
同步
|
1060
|
Nginx + Jetty
|
200
|
异步
|
1027
|
Jetty
|
200
|
异步
|
670
|
结论:同步模式下Apache+Jboss与Nginx+Jetty相差很大的级别(load也相差一倍多)。异步模式下的Nginx+Jetty与同步模式相差不大,因此异步带来的损耗可以忽略。没有Nginx作为前端整体性能下降很大。
场景:
200并发用户,no think time,容器线程池200,业务线程池500.
容器
|
请求服务
|
请求处理模式
|
TPS
|
Nginx + Jetty
|
Time.get
|
同步
|
1534
|
Nginx + Jetty
|
Time.get
|
异步
|
1068
|
Nginx + Jetty
|
User.get
|
同步
|
1060
|
Nginx + Jetty
|
User.get
|
异步
|
1027
|
结论:异步占用事务整体时间的比例越高,TPS的影响也越大。
长时间运行测试:
半个多小时Full GC一次(当前内存开到1.5G),线上64位可以开的再大一些,没有内存泄漏现象。