`
agapple
  • 浏览: 1595959 次
  • 性别: 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
分享到:
评论
12 楼 opal 2011-02-23  
ModelA modelA = serviceA.getModel(); 
ModelB modelB = serviceB.getModel(modelA.getResult()); 
ModelC modelC = serviceC.getModel(modelB.getResult()); 


这种情况????
11 楼 agapple 2011-02-23  
yashironan 写道
如果没理解错的话,可切分的多个子task,分别定义成FutureTask由Executor去执行,要求子task之间依赖性较低。用在合适的场景,确实效果不错


task越离散,多快数据之间没有依赖关系,也越接近于ajax可以实施的范围。

目前是支持依赖关系的处理,如果子task返回的对象model,存在一定的依赖关系,也只影响整个依赖的这一条链表的请求,其他不相关的同样是能走异步并行加载。

可以说业务越复杂,外部接口调用越多,提升的空间就越大
10 楼 agapple 2011-02-23  
royaki 写道
比如有10个线程请求数据,原本情况是10个线程分别进行处理,分担压力。
使用LZ这种方式以后10个线程的压力会变小,但是executor就要处理大量的请求数据,这样来看总体的耗时反而有可能增加。

而且,并没有看到比ajax明显改善的地方。

不知道是不是我理解的不对.....


理解有点偏差,原本的一个work线程,起了n个线程并行加载后,因为由线程调度,所有线程的总的耗时是会有增加的。 但这总的耗时是n个线程的时间之和,一个请求总的处理时间确是这max(单个Thread的响应时间),一个页面请求的响应时间就会减小,客户体验就好。

就是以cpu的运算的代价,提升系统的单次请求的响应时间

9 楼 freej 2011-02-23  
这个思路不错,有空看看。
8 楼 yashironan 2011-02-23  
如果没理解错的话,可切分的多个子task,分别定义成FutureTask由Executor去执行,要求子task之间依赖性较低。用在合适的场景,确实效果不错
7 楼 kimmking 2011-02-23  
有点SEDA的意思
6 楼 royaki 2011-02-23  
比如有10个线程请求数据,原本情况是10个线程分别进行处理,分担压力。
使用LZ这种方式以后10个线程的压力会变小,但是executor就要处理大量的请求数据,这样来看总体的耗时反而有可能增加。

而且,并没有看到比ajax明显改善的地方。

不知道是不是我理解的不对.....
5 楼 haigui.chen 2011-02-23  
agapple 写道
服务越多,服务model业务之间的依赖程度越少,响应时间提升会非常明显。基本上就等价于单服务响应时间最长的那个值,而不是原先的所有服务请求时间的总和

没错,这就是并行的优点。

引用
可以说是通过提升cpu的使用率,减少请求中I/O阻塞的时间。和计算机总线设计DMA有点类似

说得简单点,就是自己模拟了一个非阻塞调用,把等IO的时间用到别的上面,或者说同时进行多个IO操作。

不过,能不能share下你这个framework到底提高了多少response time 和 TPS
4 楼 agapple 2011-02-23  
服务越多,服务model业务之间的依赖程度越少,响应时间提升会非常明显。基本上就等价于单服务响应时间最长的那个值,而不是原先的所有服务请求时间的总和

可以说是通过提升cpu的使用率,减少请求中I/O阻塞的时间。和计算机总线设计DMA有点类似
3 楼 haigui.chen 2011-02-23  
个人总结下:
用线程池模拟一个异步调用
对调用者屏蔽了线程池和方法拦截(其实还是要在applicationContext配置,利用cgLib)。
2 楼 agapple 2011-02-23  
guoshiguan 写道
楼主的意思是不是,原来要同步去调用外部资源的,现在改用成,先阻塞现有的线程后,让专门的线程(可以是线程池内的)去调用,然后再访回给用户是吗?


理解有点偏差。
1. 首先不是阻塞主线程。 会是一个异步的动作,主线程不会被阻塞,继续自己干活。
2. 至于数据什么时候返回,取决于model对象的一些动作,比如我要获取具体model.getXXValue()的具体内容时,就会等阻塞等到远程服务的返回 (因请求发起和使用该value存在一定的间隔,在这段间隔时间,后台会并行的加载数据)。

假设有3个服务,每个服务的平均响应时间都是1s。首先调用A,再调用B,再调用C,那最终的响应时间就是1s,因为B,C服务都在A调用阻塞的那1s时已经完成了请求。 总的响应时间提升会明显。

所以这是一个异步+并行的过程,可以细细体会一下。

还有一个,这个和ajax不同,是工作在biz服务层,并不是直接输出html给页面。
1 楼 guoshiguan 2011-02-23  
楼主的意思是不是,原来要同步去调用外部资源的,现在改用成,先阻塞现有的线程后,让专门的线程(可以是线程池内的)去调用,然后再访回给用户是吗?

相关推荐

    异步并行加载工具Asyncload.zip

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

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

    【iframe异步加载技术】 在Web开发中,`iframe`(Inline Frame)常用于加载第三方内容,如广告、插件等。它的主要优点是能够与主页面并行加载,不阻塞主页面的渲染。然而,`iframe`的使用并非没有代价。正如Steve ...

    ios异步加载图片

    在iOS开发中,异步加载图片是一项至关重要的技术,它能显著提升应用的性能和用户体验。用户界面的响应速度直接影响到应用的评价和用户留存率。异步加载避免了主线程因处理耗时操作(如下载大图片)而被阻塞,确保...

    .NET并行计算技术基础

    - **应用场景**:异步数据加载、后台任务执行、长时间运行的操作等。 2. **并行 LINQ (PLINQ)** - **核心概念**:PLINQ 是 LINQ 的并行版本,允许在查询数据时自动进行并行处理,从而提高数据处理的速度。 - **...

    Excel文件的导入(异步线程)

    在IT领域,尤其是在数据分析、报表制作以及业务处理中,Excel文件常常被用来存储和传递大量数据。本知识点主要探讨如何高效地实现Excel文件的导入,特别是利用异步线程技术来提升性能。异步线程在多任务环境中,能够...

    网络游戏-一种并行网络文件系统小文件异步预读装置及方法.zip

    《网络游戏中的并行网络文件系统小文件异步预读技术》 网络游戏的发展日新月异,随着技术的进步,玩家对游戏体验的要求也越来越高。在网络游戏的运行过程中,大量的小文件频繁地被读取和更新,这给服务器端的文件...

    动态加载外部JS文件

    `async`属性使脚本并行加载,不保证执行顺序;`defer`属性则保证脚本在DOM解析完成后、DOMContentLoaded事件触发之前执行,且按照脚本在文档中的顺序执行。 2. 使用`XMLHttpRequest`或`fetch` API:通过创建HTTP...

    LightningJS安全快速异步嵌入代码用于第三方Javascript交付

    此外,LightningJS还支持并行加载,提高加载效率。 3. **异步**:通过异步加载,LightningJS保证了脚本的加载不会阻塞页面渲染,提升用户体验。它使用HTML5的`&lt;script async&gt;`属性或自定义事件来实现异步加载。 4....

    内核模块的动态加载优化.pptx

    ### 延迟加载技术分析 #### 性能优势 - **减少开销**:延迟加载技术将模块加载延迟到真正需要时执行,避免了启动时加载所有模块带来的额外开销,提高系统启动速度和运行效率。 - **降低内存占用**:只有在真正需要...

    ChatGPT技术的并行计算与加速优化方法.docx

    ### ChatGPT技术的并行计算与加速优化方法 #### 一、并行计算方法 **1....随着硬件技术和算法设计的进步,这些方法的应用范围将进一步扩大,为人机交互和智能对话系统带来更多的可能性和发展空间。

    js加载页面

    使用Chrome开发者工具或Lighthouse等工具进行性能分析和优化,确保JS加载对页面性能的影响降到最低。 总之,“js加载页面”涉及了浏览器的工作原理、脚本的加载方式、性能优化策略以及各种工具的使用。理解并掌握...

    Delphi5开发多层应用系统处理大数据集的方法分析.zip

    在处理大数据集时,多层架构通常分为客户端、业务逻辑层(中间层)和数据访问层,每一层都有其特定的职责。 1. **客户端层**:这是用户与应用交互的部分,通常由用户界面组件构成。Delphi5提供了丰富的VCL(Visual ...

    解决任意的多线程并行、串行、阻塞、依赖、回调的并行框架

    在设计并行框架时,合理使用阻塞和非阻塞机制可以优化系统性能。 依赖管理在多线程编程中至关重要,因为某些任务可能需要在其他任务完成后才能开始。这种情况下,我们需要一种机制来跟踪和控制这些依赖关系,确保...

    哈工大 软件架构与中间件 单面A4开卷笔记

    表示层的软件架构技术主要关注用户体验,包括视觉习惯、异步内容加载、自适应设计和前端组件化,这些技术有助于构建响应式的用户界面。例如,Struts MVC框架在JavaEE开发中,通过配置文件、控制器、模型和视图的分离...

    AsyncControl异步控制渲染的特性

    AsyncControl是针对这一需求而设计的一个工具,它允许开发者实现非阻塞的异步操作,从而提高应用程序的并发性和效率。本文将深入探讨AsyncControl异步控制渲染的特性,并结合实际应用场景进行分析。 1. **异步编程...

    bigPipe Net

    - 异步处理:利用.NET的异步编程模型,使得页面组件可以并行加载和执行,减少整体等待时间。 2. "BigPipe" 文件可能是一个示例或者配置文件,展示了如何配置和使用这个.NET框架,包括: - 页面配置:定义页面的...

    基于FPGA的ASI_SDI码流播放器的设计与实现.pdf

    在本设计中,动态加载技术可能被用来实现码流播放器的功能扩展,或者用于在不中断系统运行的情况下更新和升级系统功能。 7. 系统架构: 本项目采用基于FPGA的系统架构方法,这涉及到从系统级别的角度出发,设计整个...

    电子设计实验报告.doc

    它有5个输入端口:CLK(时钟)、RST(复位)、EN(时钟使能)、LOAD(数据加载控制)和DATA(4位并行加载数据),以及两个输出端口:DOUT(4位计数值)和COUT(进位标志)。计数器在RST为0时被复位,EN为1且CLK上升...

Global site tag (gtag.js) - Google Analytics