锁定老帖子 主题:(业务层)异步并行加载技术分析和设计
精华帖 (9) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (2)
|
|||||||
---|---|---|---|---|---|---|---|
作者 | 正文 | ||||||
发表时间:2011-02-22
最后修改:2011-06-22
背景前段时间在做应用的性能优化时,分析了下整体请求,profile看到90%的时间更多的是一些外部服务的I/O等待,cpu利用率其实不高,在10%以下。 单次请求的响应时间在50ms左右,所以tps也不会太高,测试环境压力测试过程,受限于环境因素撑死只能到200tps,20并发下。
I/O目前一般的I/O的访问速度: L1 > L2 > memory -> disk or network
常见的IO:
思路
正因为考虑到I/O阻塞,长的外部环境单个请求处理基本都是在几十ms,刚开始的第一个思路是页面做ajax处理。
使用ajax的几个缺陷:
顺着ajax的思路,是否有一种方式可以很好的解决I/O阻塞,并且又尽量的透明化,也不存在ajax如上的一些问题。
所以就有了本文的异步并行加载机制的研究。原理其实和ajax的有点类似:
一般ajax的请求:
说明:不同浏览器有不同的机制,默认执行js都是串行处理。
看一下异步并行机制的设计时序图: 说明: 结合ajax的思路,异步并行加载机制在原理设计上有点不同,就是针对ajax的请求发起都是并行的。 引入的问题: 但同样,引入并行加载的设计后,需要考虑的一个点就是如果A和B的数据之间是有一定的依赖关系时怎么处理。 例子 if(modelA.isOk()){//先依赖modelA的请求 modelB.getXXX() }
解释下依赖关系,帖子中很多人理解上有一些偏差。
B服务依赖A,指B服务的请求发起或者请求参数,取决于A的返回的结果。 那假定这个A返回结果是一个pojo bean,那依赖结果的概念是指依赖bean具体的属性。因为你直接依赖A,那只能是if(modelA == null)这样的一种关系, 一般更多情况是说我要看下if(modelA.level = 'PM')
一种解决思路:
半自动化处理。 任何异步并行加载的时机点,全都取决于代码编写的顺序。 如果有依赖关系的存在,比如例子中的B依赖A的结果,则B会阻塞等待至A的结果返回,最后A和B的处理就又回归到一个有顺序序的请求处理。
最后从技术实现上看:
例子:
ModelA modelA = serviceA.getModel(); //1. 异步发起请求 ModelB modelB = serviceB.getModel(); //2. 异步发起请求 // 3. 此时serviceA和serviceB都在各自并行的加载model if(modelA.isOk()){//4. 此时依赖了modelA的结果,阻塞等待modeA的异步请求的返回 ModelC modelC = servicec.getModel(); //5. 异步发起请求 } // 6. 此时serviceB和serviceC都在各自并行的加载model ...... modelB.xxxx() //7. 数据处理,modelB已经异步加载完成,此时不会阻塞等结果了 modelC.xxxx() //8. 数据处理,modelB已经异步加载完成,此时不会阻塞等结果了
来看个对比图: 很明显,一次request请求总的响应时间就等于最长的依赖关系请求链的相应时间。
(业务层)异步并行机制的优点:
相比于ajax的其他优势:
从目前来看,异步并行机制还是有比较大的应用场景。具体是否能做到对一线开发者透明,以及对应业务的开发成本,那就得看一下具体的代码实现
实现根据以上分析,分析核心模型:
说明:
具体的类图设计:
说明:
一些技术描述:
一个使用例子:
// 初始化config AsyncLoadConfig config = new AsyncLoadConfig(3 * 1000l); // 初始化executor AsyncLoadExecutor executor = new AsyncLoadExecutor(10, 100); executor.initital(); // 初始化proxy AsyncLoadEnhanceProxy<AsyncLoadTestService> proxy = new AsyncLoadEnhanceProxy<AsyncLoadTestService>(); proxy.setService(asyncLoadTestService); proxy.setConfig(config); proxy.setExecutor(executor); // 执行测试 AsyncLoadTestService service = proxy.getProxy(); AsyncLoadTestModel model1 = service.getRemoteModel("first", 1000); // 每个请求sleep 1000ms AsyncLoadTestModel model2 = service.getRemoteModel("two", 1000); // 每个请求sleep 1000ms AsyncLoadTestModel model3 = service.getRemoteModel("three", 1000); // 每个请求sleep 1000ms long start = 0, end = 0; start = System.currentTimeMillis(); System.out.println(model1.getDetail()); end = System.currentTimeMillis(); want.number(end - start).greaterThan(500l); // 第一次会阻塞, 响应时间会在1000ms左右 start = System.currentTimeMillis(); System.out.println(model2.getDetail()); end = System.currentTimeMillis(); want.number(end - start).lessThan(500l); // 第二次不会阻塞,因为第一个已经阻塞了1000ms,并行加载已经完成 start = System.currentTimeMillis(); System.out.println(model3.getDetail()); end = System.currentTimeMillis(); want.number(end - start).lessThan(500l); // 第三次也不会阻塞,因为第一个已经阻塞了1000ms,并行加载已经完成 // 销毁executor executor.destory(); 一些扩展 因为目前大家都比较喜欢于spring的ioc,aop等一些配置方式,类似于编程式事务和声明式事务。为方便以后使用,做了下扩展。
扩展一:AsyncLoadFactoryBean类似于spring的ProxyFactoryBean的概念,基于spring FactoryBean接口实现。
配置事例:
<!-- 并行加载容器--> <bean id="asyncLoadExecutor" class="com.alibaba.tpolps.common.asyncload.AsyncLoadExecutor" init-method="initital" destroy-method="destory"> <property name="poolSize" value="10" /> <!-- 并行线程数 --> <property name="acceptCount" value="100" /> <!-- 就绪队列长度 --> <property name="mode" value="REJECT" /> <!-- 就绪队列满了以后的处理模式 --> </bean> <bean id="asyncLoadMethodMatch" class="com.alibaba.tpolps.common.asyncload.impl.AsyncLoadPerl5RegexpMethodMatcher" > <property name="patterns"> <list> <value>(.*)RemoteModel(.*)</value> </list> </property> <property name="excludedPatterns"> <!-- 排除匹配方法 --> <list> <value>(.*)listRemoteModel(.*)</value> </list> </property> <property name="excludeOveride" value="false" /> </bean> <bean id="asyncLoadConfig" class="com.alibaba.tpolps.common.asyncload.AsyncLoadConfig"> <property name="defaultTimeout" value="3000" /> <property name="matches"> <map> <entry key-ref="asyncLoadMethodMatch" value="2000" /> <!-- 针对每个match设置超时时间 --> </map> </property> </bean> <!-- 异步加载模FactoryBean --> <bean id="asyncLoadTestFactoryBean" class="com.alibaba.tpolps.common.asyncload.impl.spring.AsyncLoadFactoryBean"> <property name="target"> <ref bean="asyncLoadTestService" /> <!-- 指定具体的服务 --> </property> <property name="executor" ref="asyncLoadExecutor" /> <property name="config" ref="asyncLoadConfig" /> </bean> 思考: 后续可考虑,基于通配符拦截对应的目标service。
扩展二: AsyncLoadTemplate基于模板模式,提供异步并行机制。可以编程方式指定进行异步并行加载的执行单元。 比如针对好几个service的调用合并为一次并行加载。 事例代码:
AsyncLoadTestModel model2 = asyncLoadTemplate.execute(new AsyncLoadCallback<AsyncLoadTestModel>() { @Override public AsyncLoadTestModel doAsyncLoad() { // 总共sleep 2000ms return asyncLoadTestService.getRemoteModel("ljhtest", 1000); } }); System.out.println(model2.getDetail());
思考
最后具体的代码可以访问: http://code.google.com/p/asyncload/ 几个单元测试例子:
ps : 大家有兴趣或者有更好的一些想法,可以一起讨论下,站内PM我。 至于其他语言的异步并行加载方案也可以一并讨论下,小成本大收益,何乐而不为呢!
说明一下:(2月24号新增的内容)
设计的初衷是追求尽量少的嵌入性,尽可能的对业务开发透明。所以会采用字节码增强的方式进行处理。
最理想的情况: 1. 普通开发人员完成业务编码开发,按照正常的方式,不需要关心异步加载这一套。 2. 最后项目的技术经理或者架构师,配置一份xml,决定哪些service的哪几个方法要进行异步并行加载。 3. 项目进入提测,回归测试等。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|||||||
返回顶楼 | |||||||
发表时间:2011-02-23
楼主的意思是不是,原来要同步去调用外部资源的,现在改用成,先阻塞现有的线程后,让专门的线程(可以是线程池内的)去调用,然后再访回给用户是吗?
|
|||||||
返回顶楼 | |||||||
发表时间:2011-02-23
guoshiguan 写道 楼主的意思是不是,原来要同步去调用外部资源的,现在改用成,先阻塞现有的线程后,让专门的线程(可以是线程池内的)去调用,然后再访回给用户是吗?
理解有点偏差。 1. 首先不是阻塞主线程。 会是一个异步的动作,主线程不会被阻塞,继续自己干活。 2. 至于数据什么时候返回,取决于model对象的一些动作,比如我要获取具体model.getXXValue()的具体内容时,就会等阻塞等到远程服务的返回 (因请求发起和使用该value存在一定的间隔,在这段间隔时间,后台会并行的加载数据)。 假设有3个服务,每个服务的平均响应时间都是1s。首先调用A,再调用B,再调用C,那最终的响应时间就是1s,因为B,C服务都在A调用阻塞的那1s时已经完成了请求。 总的响应时间提升会明显。 所以这是一个异步+并行的过程,可以细细体会一下。 还有一个,这个和ajax不同,是工作在biz服务层,并不是直接输出html给页面。 |
|||||||
返回顶楼 | |||||||
发表时间:2011-02-23
个人总结下:
用线程池模拟一个异步调用 对调用者屏蔽了线程池和方法拦截(其实还是要在applicationContext配置,利用cgLib)。 |
|||||||
返回顶楼 | |||||||
发表时间:2011-02-23
服务越多,服务model业务之间的依赖程度越少,响应时间提升会非常明显。基本上就等价于单服务响应时间最长的那个值,而不是原先的所有服务请求时间的总和
可以说是通过提升cpu的使用率,减少请求中I/O阻塞的时间。和计算机总线设计DMA有点类似 |
|||||||
返回顶楼 | |||||||
发表时间:2011-02-23
agapple 写道 服务越多,服务model业务之间的依赖程度越少,响应时间提升会非常明显。基本上就等价于单服务响应时间最长的那个值,而不是原先的所有服务请求时间的总和
没错,这就是并行的优点。 引用 可以说是通过提升cpu的使用率,减少请求中I/O阻塞的时间。和计算机总线设计DMA有点类似
说得简单点,就是自己模拟了一个非阻塞调用,把等IO的时间用到别的上面,或者说同时进行多个IO操作。 不过,能不能share下你这个framework到底提高了多少response time 和 TPS |
|||||||
返回顶楼 | |||||||
发表时间:2011-02-23
比如有10个线程请求数据,原本情况是10个线程分别进行处理,分担压力。
使用LZ这种方式以后10个线程的压力会变小,但是executor就要处理大量的请求数据,这样来看总体的耗时反而有可能增加。 而且,并没有看到比ajax明显改善的地方。 不知道是不是我理解的不对..... |
|||||||
返回顶楼 | |||||||
发表时间:2011-02-23
有点SEDA的意思
|
|||||||
返回顶楼 | |||||||
发表时间:2011-02-23
如果没理解错的话,可切分的多个子task,分别定义成FutureTask由Executor去执行,要求子task之间依赖性较低。用在合适的场景,确实效果不错
|
|||||||
返回顶楼 | |||||||
发表时间:2011-02-23
这个思路不错,有空看看。
|
|||||||
返回顶楼 | |||||||