`
agapple
  • 浏览: 1582359 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

(业务层)异步并行加载技术分析和设计

阅读更多

背景

   前段时间在做应用的性能优化时,分析了下整体请求,profile看到90%的时间更多的是一些外部服务的I/O等待,cpu利用率其实不高,在10%以下。 单次请求的响应时间在50ms左右,所以tps也不会太高,测试环境压力测试过程,受限于环境因素撑死只能到200tps,20并发下。

 

I/O

目前一般的I/O的访问速度: L1 > L2 > memory -> disk or network
 
常见的IO: 
  1. nas上文件 (共享文件存储)
  2. output/xxx (磁盘文件)
  3. memcache client /  cat client  (cache服务)
  4. database (oracle , mysql)  (数据库)
  5. dubbo client  (外部服务)
  6. search client (搜索引擎)

思路

 

正因为考虑到I/O阻塞,长的外部环境单个请求处理基本都是在几十ms,刚开始的第一个思路是页面做ajax处理。 

 

使用ajax的几个缺陷:

 

  1. 功能代码需进行重构,按照页面需求进行分块处理。 一次ajax请求返回一块的页面数据
  2. 数据重复请求。因为代码是分块,两次ajax中获取的member对象等,可能就没法共用,会造成重复请求。
  3. ajax加载对seo不优化,公司还是比较注重seo,因为这会给客户带来流量价值,而且是免费的流量。
  4. ajax技术本身存在一些磕磕碰碰的点: 跨域问题,返回数据问题,超时处理等。
  5. ajax处理需要有嵌入性,每个开发都需要按照ajax特有的规范或者机制进行编码,有一定的约束

顺着ajax的思路,是否有一种方式可以很好的解决I/O阻塞,并且又尽量的透明化,也不存在ajax如上的一些问题。 

 

所以就有了本文的异步并行加载机制的研究。原理其实和ajax的有点类似: 

 

 

一般ajax的请求: 

 

  • request就代表html页面的一次渲染过程
  • 首先给页面的一块区域渲染一块空的div id=A内容和一块div id=B的内容
  • 浏览器继续渲染页面的其他内容
  • 在页面底部执行具体的js时,发起div id=A的请求,等A返回后填充对应的div内容,发起div id=B的请求,返回后同样填充。

说明:不同浏览器有不同的机制,默认执行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的处理就又回归到一个有顺序序的请求处理。

 

 

 

 

最后从技术实现上看: 
  从技术实现上说,在调用serviceA获取结果的时,我会直接返回一个假的LazyLoad产生的mock对象。此时对应的属性值全为null,在你具体依赖到该modelA的属性数据时,就会有一个阻塞等待,转为串行的过程。 

 

例子:

 

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请求总的响应时间就等于最长的依赖关系请求链的相应时间。 

 

(业务层)异步并行机制的优点:

  1. 继承了ajax异步加载的优点
  2. 增加了并行加载的特性

相比于ajax的其他优势:

  1. 同时不会对页面seo有任何的影响,页面输出时都是一次性输出html页面
  2. 减少了ajax异步发起的http请求
  3. 两块代码的资源不会存在重复请求,允许进行资源共享
从目前来看,异步并行机制还是有比较大的应用场景。具体是否能做到对一线开发者透明,以及对应业务的开发成本,那就得看一下具体的代码实现

实现

根据以上分析,分析核心模型:


 说明:
  1. 原本服务service。 这个不用多解释,就是原本存在的一些需要被代理的对象,比如DAO,rpc调用客户端等。
  2. 代理参数设置。 比如设置一些超时时间等
  3. 并行执行容器。 一个多线程处理的容器,执行并行加载
  4. 代理服务。  对服务service的一个包装过后的代理对象
  5. 代理服务Model 。  代理对象根据客户端的一些请求返回对应的代理Model,用于代理控制。
具体的类图设计:


 
说明: 
  1. AsyncLoadProxy就是模型中对应的代理服务
  2. AsyncLoadConfig就是模型中对应的代理参数设置
  3. AsyncLoadExecutor就是模型中对应的并行执行容器。
一些技术描述:
  • AsyncLoadEnhanceProxy是目前代理服务的一种技术实现,基于cglib的动态代理。后续可以研究下javassist技术,据说性能上比cglib要高。
  • AsyncLoadMethodMatch是针对参数设置的一个细化,类似于spring的aop的切面点(PointCut)的概念,在具体的切面上执行异步并行加载机制。
  • AsyncLoadExecutor目前是采用了jdk1.5中cocurrent包的pool池技术。支持两个队列设置:running队列,就绪队列。 针对就绪队列满了后,提供REJECT(拒绝后续请求)/BLOCK(阻塞等待队列有空位置)两种处理模式。

一个使用例子: 
// 初始化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());

 

 

思考

 

  1. 基于cglib的技术局限,存在一些限制。比如final类,java原始类型等不支持异步并行。一点技术局限性
  2. 并行加载机制不适合于cpu密集性的应用,针对I/O密集型的应用效果会比较明显,设置好对应的并行加载容器。具体参数需要细细斟酌,进行相关的压力测试和分析。
  3. 对开发的一个嵌入性,需要考虑对应的Timeout机制,比如异常处理等。同样,我们也可以设置没有timeout(个人不太建议)

最后

具体的代码可以访问:  https://github.com/agapple/asyncload

 

 

 

ps : 大家有兴趣或者有更好的一些想法,可以一起讨论下。 至于其他语言的异步并行加载方案也可以一并讨论下,小成本大收益,何乐而不为呢!

 

 

 

说明一下:(2月24号新增的内容)

 

设计的初衷是追求尽量少的嵌入性,尽可能的对业务开发透明。所以会采用字节码增强的方式进行处理。

 

最理想的情况:

1. 普通开发人员完成业务编码开发,按照正常的方式,不需要关心异步加载这一套。

2. 最后项目的技术经理或者架构师,配置一份xml,决定哪些service的哪几个方法要进行异步并行加载。

3. 项目进入提测,回归测试等。

  • 大小: 7.9 KB
  • 大小: 3.5 KB
  • 大小: 12.9 KB
  • 大小: 79.9 KB
分享到:
评论
52 楼 mojunbin 2014-08-12  
不错,最近也在思考这一块问题,希望用在页面加速,不过感觉不好实现。

独立业务可以异步加载,但是数据载入页面,通过单通过服务端技术不好实现,需要配合js动态替换内容。

这种方式,还是将页面静态化,然后动态并行加载某个页面块比较好实现,利用ssi。
51 楼 agapple 2013-11-29  
pastore123 写道
有个疑问,假如页面有两个DIV A和B,页面发起Ajax请求时,是同步的?先请求A然后再B吗?


不同浏览器对于同各域名的异步加载有线程数限制,IE默认为2个
50 楼 pastore123 2013-11-29  
有个疑问,假如页面有两个DIV A和B,页面发起Ajax请求时,是同步的?先请求A然后再B吗?
49 楼 agapple 2012-07-16  
sodarfish 写道
一般业务逻辑中,调用外部服务都要知道是否调用成功了,然后再往下走。
如果是异步的方式,这种情况怎么处理呢


不知道你说的是否是这样的一种关系:
ModelA modela= a.method();
if(modela.isSuccess()) {
  ModelB modelb = b.method();
}


这个就是文中提到的服务依赖了,这时候程序需要变为一个串行操作。(modela.isSucess会是一个阻塞等待A执行完成,并返回结果。然后才会进行b服务的调用) 其实原理蛮简单 
48 楼 sodarfish 2012-07-13  
一般业务逻辑中,调用外部服务都要知道是否调用成功了,然后再往下走。
如果是异步的方式,这种情况怎么处理呢
47 楼 agapple 2011-11-08  
xiaoZ5919 写道
和spring的asynch异步任务原理相似。通过proxy实施拦截
Future result = this.asyncExecutor.submit(new Callable<Object>() {
public Object call() throws Exception {
try {
Object result = invocation.proceed();
if (result instanceof Future) {
return ((Future) result).get();
}
}
catch (Throwable ex) {
ReflectionUtils.rethrowException(ex);
}
return null;
}
});
我有一点concern,向LZ咨询。
threadpool会不会成为瓶颈?例如设置poolSize为10,当并发为100时,肯定会有一些请求在队列中排队。如加大poolsize,也会带来线程切换的cost?基于以上疑问能不能找到一个平衡点?


原理其实是相通的,就是想通过异步线程进行处理。
相比spring,AsyncLoad中多了一些融合,减少使用异步带来对于业务代码的嵌入性。用了一些字节码处理,扩展了一些spring,jdk的内容
46 楼 agapple 2011-11-08  
xiaoZ5919 写道
和spring的asynch异步任务原理相似。通过proxy实施拦截
Future result = this.asyncExecutor.submit(new Callable<Object>() {
public Object call() throws Exception {
try {
Object result = invocation.proceed();
if (result instanceof Future) {
return ((Future) result).get();
}
}
catch (Throwable ex) {
ReflectionUtils.rethrowException(ex);
}
return null;
}
});
我有一点concern,向LZ咨询。
threadpool会不会成为瓶颈?例如设置poolSize为10,当并发为100时,肯定会有一些请求在队列中排队。如加大poolsize,也会带来线程切换的cost?基于以上疑问能不能找到一个平衡点?


这个我的建议是构建一个2n+1的线程模型。你可以看下我的AsyncLoadExecutor。
2n是指BlockingQueue的size为pool size的两倍
1是指当BlockingQueue队列满了之后切换回串行执行的模式(这样就不会有线程切换的cost)
45 楼 xiaoZ5919 2011-11-08  
和spring的asynch异步任务原理相似。通过proxy实施拦截
Future result = this.asyncExecutor.submit(new Callable<Object>() {
public Object call() throws Exception {
try {
Object result = invocation.proceed();
if (result instanceof Future) {
return ((Future) result).get();
}
}
catch (Throwable ex) {
ReflectionUtils.rethrowException(ex);
}
return null;
}
});
我有一点concern,向LZ咨询。
threadpool会不会成为瓶颈?例如设置poolSize为10,当并发为100时,肯定会有一些请求在队列中排队。如加大poolsize,也会带来线程切换的cost?基于以上疑问能不能找到一个平衡点?
44 楼 agapple 2011-07-18  
waterdh 写道
不错的思路,想得很深入了。
我是显式编写并行加载的代码,不够优美和灵活。


不能做到完全透明,还是有些使用场景的局限。
至于在配置上,基本上可以和spring融合下了挺多功夫,有兴趣可以一起交流。
43 楼 waterdh 2011-07-18  
不错的思路,想得很深入了。
我是显式编写并行加载的代码,不够优美和灵活。
42 楼 agapple 2011-06-14  
Reset 写道
没大看明白,是不是AOP某个方法,使用Future 异步计算结果
然后代理这个方法的返回值,然后使用返回值时如果结果还没出来就阻塞...

整个设计是有点AIO的感觉,几句话概括就是这样的一种情况。
就是将原先同步的调用转成异步调用,减少系统的响应时间

从实际应用测试来看,还是比较靠谱的,提升20%左右,这个也取决于你原先的I/O瓶颈所占的百分比。
在公司内部应用场景也会比较多,逐步的在实施,googlecode上稳定版马上就可以出来了
41 楼 Reset 2011-06-14  
没大看明白,是不是AOP某个方法,使用Future 异步计算结果
然后代理这个方法的返回值,然后使用返回值时如果结果还没出来就阻塞...
40 楼 agapple 2011-05-04  
xgj1988 写道
tps 是什么东西


tps是Transactions per second 的简写,http://en.wikipedia.org/wiki/Transactions_per_second

主要是用于评价一个系统的性能,除此之外,还有并发数,响应时间,吞突量(throughput)等一些列指标,通过一个二维图表绘制这些数据,可以找到一个系统的负载瓶颈点。
39 楼 xgj1988 2011-05-03  
tps 是什么东西
38 楼 agapple 2011-02-25  
winit 写道
呵呵,谢谢及时回复,那我就不明白了,既然modelB的加载(bbb方法的执行)依赖于ModelA执行的结果,A没有返回之前,B是加载不了的,实质上还是串行吧?
result = modelA.xxx();
modelB.bbb(result);


可能表述的依赖没讲清楚。

A服务依赖B,指A服务的请求发起或者请求参数,取决于B的返回的结果。

那假定这个B是一个pojo bean,那依赖结果的概念是指依赖bean具体的属性。

从技术实现上说,在调用serviceA获取结果的时,我会直接返回一个假的LazyLoad产生的mock对象。此时对应的属性值全为null,在你具体依赖到该modelA的属性数据时,就会有一个阻塞等待,转为串行的过程。

不知道说明白了没? 貌似很多人对这块有一定的理解偏差。

最后再看你的modeB.bbb(result)。 你这时的代码应该还不产生一种依赖关系,所以这里还是不会有阻塞,直接过去
37 楼 winit 2011-02-25  
呵呵,谢谢及时回复,那我就不明白了,既然modelB的加载(bbb方法的执行)依赖于ModelA执行的结果,A没有返回之前,B是加载不了的,实质上还是串行吧?
result = modelA.xxx();
modelB.bbb(result);
36 楼 agapple 2011-02-25  
winit 写道
对于楼主说的这种类型的:
if(modelA.isOk()){//先依赖modelA的请求 
    modelB.getXXX() 

确实可以异步加载,但更常见的是如下依赖类型的:
result = modelA.xxx();
modelB.xxxxx(result);
对于这种异步没用吧?


因目前技术实现的局限性,如果服务接口返回的是java的原始类型,暂不支持异步并行加载,final对象后续可考虑使用jdk proxy处理,目前代码没支持。

modelB.xxxx(result); 这里如果你的result是个对象类型时Integer,String等也可以支持。
35 楼 winit 2011-02-25  
对于楼主说的这种类型的:
if(modelA.isOk()){//先依赖modelA的请求 
    modelB.getXXX() 

确实可以异步加载,但更常见的是如下依赖类型的:
result = modelA.xxx();
modelB.xxxxx(result);
对于这种异步没用吧?
34 楼 agapple 2011-02-24  
donglee 写道
大任务包含分支任务分解,可以参考下fork/join并行分解模型


根fork/join是两码事,fork/join主要用于处理一些主任务拆分成对等子任务。 而异步加载需要解决的是未知服务的并行加载,两个服务之间可能还存在数据上的依赖关系。
33 楼 agapple 2011-02-24  
cljhyjs 写道
agapple 写道
cljhyjs 写道
LZ的想法很有创意,但是考虑到如果把这块功能抽象,采用类似拦截机制的方法在业务服务层做封装,可能需要考虑的问题会很多。 毕竟抽象层无法判断各个方法之间的依赖关系,即使采用显式的等待方式,可能也会增加程序的复杂度,或者实现封装代码块的复杂度。
   建议采用半自动化方式,由开发人员自己决定是否将方法投入到异步并行的容器中。就像spring中的ECcache、jbosscache配置使用一样。


这个你可以看看主文中的代码例子。

因底层技术是采用字节码增强方式,包括service和model都会织入一定的代码,具体开发者是感觉不到的。就和正常的写代码是一样的方式,至于依赖关系的串行处理,也是植入代码内部的。

原先设计的理念就是尽量做到无嵌入,对开发透明。

理想的情况就是普通开发人员完成业务编码开发后,由项目的技术经理或者架构师,配置一份xml,决定哪些service的哪几个方法要进行异步并行加载。


这也是一种方法,何必不让开发者自己在applicationContext.xml中配置,决定哪些service以及方法需要异步并行加载呢?



支持的阿,你看下首页中关于 扩展一:AsyncLoadFactoryBean 描述这一块。

基于spring FactoryBean接口,配置方式就是纯spring ioc那一套,对哪个service的哪些方法进行异步加载。

相关推荐

    异步并行加载工具Asyncload.zip

    长的外部环境单个请求处理基本都是在几十ms,最终的出路只能异步 并行,从而诞生了该开源产品项目介绍名称:asyncload译意: async cocurrent load语言: 纯java开发定位: 业务层异步并行加载工具包,减少页面...

    iframe 异步加载技术及性能分析

    使用iframe是因为他可以和主页面并行加载,不会阻塞主页面。当然使用iframe也是有利有弊的:Steve Souders在他的blog里面有阐述:Using Iframes Sparingly: iframe会阻塞主页面的onload事件 主页面和iframe共享同一...

    Visual C++串口通信技术详解.(机械工业.李景峰.杨丽娜.潘恒)

    1.1.4 并行接口技术 1.1.5 串行接口技术 1.2 RS-232C标准 1.2.1 RS-232C电气特性 1.2.2 RS-232C连接器机械特性 1.2.3 RS-232C的接口信号 1.2.4 RS-232C的通信方式 1.3 RS-422/RS-485标准 1.3.1 RS-422简介 1.3.2 RS...

    ORACLE9i_优化设计与系统调整

    §6.1.1 系统设计阶段和开发阶段的优化 85 §6.1.2 改善产品系统的优化 85 §6.2 优化的优先步骤 86 §6.2.1 步骤1:优化商业规则 86 §6.2.2 步骤2:优化数据设计 87 §6.2.3 步骤3:优化应用程序设计 87 §6.2.4 ...

    windows驱动开发技术详解-part2

    巧和应用实例,兼顾了在校研究生和工程技术人员的实际需求,对教学、生产和科研有现实的指导意义 ,是一本值得推荐的专著。  ——中国工程院院士  院士推荐  目前,电子系统设计广泛采用通用操作系统,达到降低...

    Windows驱动开发技术详解的光盘-part1

    本书是作者结合教学和科研实践经验编写而成的,不仅详细介绍了Windows内核原理,而且介绍了编程技巧和应用实例,兼顾了在校研究生和工程技术人员的实际需求,对教学、生产和科研有现实的指导意义,是一本值得推荐的...

    嵌入式系统/ARM技术中的基于DMA控制器的SoC系统设计

    DMA控制器能够有效替代微处理器的加载/存储指令,显着提高系统的并行能力。DMA是在存储器与输入/输出设备间直接传送数据,是一种完全由硬件完成输入/输出操作的方式。数据传递可以从外设到内存,从内存到外设。但DMA...

    Visual.Basic.2010.&.NET4.高级编程(第6版)-文字版.pdf

    第ii部分 业务对象和数据访问第8章 数组、集合和泛型 311 8.1 数组 312 8.1.1 多维数组 313 8.1.2 ubound函数 314 8.1.3 redim语句 314 8.1.4 preserve关键字 315 8.2 集合 315 8.2.1 循环语句 317 ...

Global site tag (gtag.js) - Google Analytics