??xml version="1.0" encoding="utf-8" standalone="yes"?>
很多语言都支?/span>promise~程模型Q像?/span>scala?/span>promisecdjqueryQ?/span>javascriptQ中?/span>deferred对象{,?/span>java中好像缺相兛_现。笔者不得以Q只能自己动手弄了一个。最后选择?/span>jquery中的deferred对象UL?/span>java中来的方案。目前已l应用在企业U项目的高性能服务器和android客户端等目中?/span>
Promise~程模型的概念这里也不再赘述Q大家自׃|查扑֍可。这U编E模型主要解决的问题是“同步调用变异步的问题”Q通常解决异步调用的方式是使用“回调”。但普通回调的使用在代码书写,q回g递和“异步Ҏ~排?#8221;非常的不方便。所以才会有Promise模型的诞生?/span>
q次会介l?/span>java版的deferred对象的用方法,以及?/span>jquery版之间的变化和改q。目前开攄版本是基于线E池的版本,正在开发基?/span>akka的版本。在jquery的实CQ因?/span>javascript是单U程的,所以不用考虑U程同步的问题。在javaU程池的版的deferred里,Z多线E环境做了很多测试,保证了线E安全及可靠性?/span>
一Q?span style="font-variant-numeric: normal; font-stretch: normal; font-size: 7pt; line-height: normal; font-family: "Times New Roman";"> 基本调用形式
final Deferred def = new Deferred (App. executor);
执行某个异步调用Q比如某个基于网l的异步服务
callService(new Response(){
public void onMessage(Object message){
def.resolve(message);
}
Public void onFail(Exception e){
def.reject(e);
}
});
你可以在构?/span>Deferred 对象后的L时候,使用def?/span>thenҎ。比?/span>
def.then(new Reply(){
public Object done(Object d) {
System.out.println("response:"+d);
return d;
}
public void fail(Object f) {
System.out.println("error:"+f);
}
});
一个经帔R到的场景?/span>callService后将def作ؓ参数传递到其他ҎQ在其他Ҏ内部再决?/span>def要绑定什么样的后l动作,也就是绑定什么样?/span>then?/span>
注意thenҎ的定?/span>public Object done(Object d)Q在实际使用?/span>done通常是以“处理?#8221;的方式来使用的,即你会看?/span>def.then().then().then()…q样的方式,每一?/span>then?/span>doneҎ接收的参数都是其上一?/span>then?/span>doneҎ的返回倹{通常作ؓ参数传递给某个Ҏ?/span>Deferred上面已经l定了一些默认的then对象Q来处理一些必要的步骤。比如对接收报文的初步解码?/span>
注意同在Reply接口?/span>failҎ是没有返回值的Q一旦异步处理链上的某个Deferred?/span>rejectQ其本n及后面所有的Deferredl定?/span>then都会被触?/span>failҎ。这保证了整个业务编排上或是你精心设计的法~排上Q意一个环节,无论如何都会得到响应Q这也是Promise模型关于异常的最重要的处理方式?/span>
Promise~程模型本n是强健的Q但异步服务却不是总能得到响应。在实际应用中,每一个作或业务环节?/span>Deferred都应该被定时轮询Q以保证在异步服务彻底得不到响应的时候(比如你执行了一个数据库查询Q但q了很长很长旉仍没有得到回应)Q可以给Deferred对象reject一个超旉误?/span>
响应处理对象then中方?/span>done?/span>fail都是不允许抛ZQ何异常的Q特别是doneҎQ如果你的算法依赖异常,请在done中加?/span>try…catchQƈ异怼换成下一?/span>then可以理解的信息,以便q个Deferred处理链中可以正常执行下去?/span>
二. pipe到另外一个异步处理流E上?/span>
假如你有如下的业务场景,你需要顺序调用三个异步的webservice服务来得到最l的q回l果Q其中没?/span>webservice的入参都和上一个的异步q回l果相关。(注意Q异步的webservice是调用之后,服务端立刻返回,服务端处理完成后再主动访问刚才的h方返回结果的方式Q如果将q种webservice调用装成同步方法无疑在~程上是非常方便的,可以使用我们q_写程序时序的书写方式,比如
reval1 = callwebservice1(param0)
reval2 = callwebservice2(reval1)
reval3 = callwebservice3(reval2)
方便的同时却牺牲了性能。调用线E要?/span>callwebserviceҎ内阻塞,以等待异步返回。这L~程Ҏ无法满高性能及高q发的需要。那么有没有既能cM于^常写E序旉序的书写方式又能满异步无阻塞的需要呢Q这是Promise~程模型本n要解决的最大问题?/span>
通常解决q种问题的方式是使用pipeQ?/span>pipeq个Ҏ名称的由来应该是来自?/span>linux shell的管道符Q即“|”
使用Deferred对象的解x案类g如下Q?/span>
Deferred.resolvedDeferred(App.executor,param0).pipe(new AsyncRequest2(){
public void apply(Object param0,final Deferred newDefered) throws Exception{
asyncCallwebservice1(param0).onResponse(new Response(){
public void onMessage(String message){
newDefered.resolve(message);
}
});
}
}).pipe(new AsyncRequest2(){
public void apply(Object reval1,final Deferred newDefered) throws Exception{
asyncCallwebservice2(reval1).onResponse(new Response(){
public void onMessage(String message){
newDefered.resolve(message);
}
});
}
}).pipe(new AsyncRequest2(){
public void apply(Object reval2,final Deferred newDefered) throws Exception{
asyncCallwebservice3(reval3).onResponse(new Response(){
public void onMessage(String message){
newDefered.resolve(message);
}
});
}
}).then(new new Reply(){
public Object done(Object d) {
//在这里消Ҏl结?/span>
return d;
}
public void fail(Object f) {
}
});
使用Deferred对象提供的方案好处就是,所有的调用都是异步的,上面q一q串代码立刻׃q回。所有的业务~排会按照书写顺序在U程池中的线E里被调用,你也不必担心q回值结果和参数传递过E中的线E安全问题,框架在关键位|都做了同步Q也做了相当多的试用于验证?/span>
可以看出Q对于异步方法调用而言Q比较难以解决的问题是异步算法的~排问题?/span>Deferred对象为异步算法提供了很好的解x案?/span>
相较?/span>AsyncRequest2c还有一?/span>AsyncRequest1c,接口如下Q?/span>
public interface AsyncRequest1<R> {
public Deferred apply(R result) throws Exception;
}
q个c要求在?/span>applyҎ中要自己创徏Deferred对象?/span>
?/span>. 一些小改进
相较于传l?/span>promise~程模型Q在java多线E环境下做了一些小升。这里主要介l?/span>synchronizeҎ
SynchronizeҎ{如下:
Deferred synchronize(ExecutorService executor,Deferred... deferreds)
实际上,synchronizeҎ众多的Deferred对象的完成状态同归集C个唯一?/span>Deferred对象上去Q即如果所有的Deferred对象参数?/span>resolved了,作ؓ最l结果的Deferred?/span>resolveQ如果众多的Deferred对象参数有一?/span>reject了,最l的那个Deferred也会立即reject(其他参数的状态都舍弃)?/span>
q个Ҏ一般用于多个ƈ行流E最l状态的“归ƈ”中?/span>
除了synchronizeQ框架还提供一些传l?/span>promise~程模型没有的改q,比如pipe4fail?/span>source{?/span>
四.?/span>android目中的应用
Q略Q?/span>
https://github.com/jonenine/javaDeferred
虽然大数据的发展已经近10个年头了Q?/span>hadoop技术仍然没有过Ӟ特别是一些低成本Q入门的小目Q?/span>hadoopq是蛮不错的。而且Q也不是每一个公叔R有能力招聘和培养自己?/span>spark人才?/span>
我本人对?/span>hadoop mapreduce是有一些意见的?/span>hadoop mapreduce技术对于开发h员的友好度不高,E序隑ֆQ调试困难,对于复杂的业务逻辑q没?/span>spark得心应手?/span>
2016q的春节前接C个Q务,要在一个没?/span>spark的^台实现电力系l的一些统计分析算法,可选的技术只?/span>hadoop mapreduce。受了这个刺Ȁ之后产生了一些奇思妙惻I然后做了一些试验,q最lŞ?/span>HST---hadoop simplize toolkitQ还真是无心载柳x荫啊?/span>
HST基本优点如下Q?/span>
屏蔽?/span>hadoop数据cdQ取消了driverQ将mapper?/span>reducer转化?/span>transformer?/span>joinerQ业务逻辑更接q?/span>sql。相当程度的减少了代码量Q极大的降低了大数据~程的门槛,让基层程序员通过单的学习卛_掌握大数据的开发?/span>
克服?/span>hadoop mapreduce数据源单一的情况,比如在一?/span>job内,input可以同时L件和来自不同集群?/span>hbase?/span>
q程日志pȝQ让mapper?/span>reducer的日志集中到driver的控制台Q极大减Mq行多进E程序的调试隑ֺ?/span>
克服?/span>hadoop mapreduce~写业务逻辑Ӟ不容易区分数据来自哪个数据源的困难。接q了sparkQ或?/span>sqlQ的水^?/span>
天生的多U程执行Q即?/span>mapper?/span>reducer端都默认使用多线E来执行业务逻辑?/span>
对于多次q代的Q务,相连的两个Q务可以徏立关联,下一个Q务直接引用上一个Q务的l果Q多次q代d的代码结构变得清C?/span>
以下会逐条说明
基本概念的小变化Q?/span>
SourcecM替了hadoop Input体系(formatQ?/span>split?/span>reader)
Transformer代替?/span>mapper
Joiner代替?/span>Reducer
L了饱受诟病的DriverQ改为内|的实现Q现在完全不用操心了?/span>
1. 基本上,屏蔽?/span>hadoop的数据类型,使用U?/span>javacd
在原生的hadoop mapreduce开发中Q?/span>org.apache.hadoop.io包下的各Uhadoop数据cdQ比如hadoop的TextcdQ算法的~写中一些{换非怸方便。而在HST中一律用java基本cdQ完全屏蔽了hadoopcd体系?/span>
比如在hbase作ؓsourceQInputQ的时候,再也不用直接使用ImmutableBytesWritable和Result了,HSTZ做了自动的{换?/span>
现在的mapperQ改名叫Transformer了)风格是这L
public static class TransformerForHBase0 extends HBaseTransformer<Long>
…
现在mapҎ叫flatmapQ看到没Q已l帮你自动{成了string和map
public void flatMap(String key, Map<String, String> row,
Collector<Long> collector)
可阅读xs.hadoop.iterated.IteratedUtilcM关于cd自动转换的部?/span>
2. 克服?/span>hadoop mapreduce数据源单一的情c比如在一?/span>job内,数据源同时读文g?/span>hbaseQ这在原生的hadoop mapreduce是不可能做到?/span>
以前讉KhbaseQ需要?/span>org.apache.hadoop.hbase.client.Scan和TableMapReduceUtilQ现在完全改Zspark怼的方式?/span>
现在的风格是q样的:
Configuration conf0 = HBaseConfiguration.create();
conf0.set("hbase.zookeeper.property.clientPort", "2181");
conf0.set("hbase.zookeeper.quorum", "172.16.144.132,172.16.144.134,172.16.144.136");
conf0.set(TableInputFormat.INPUT_TABLE,"APPLICATION_JOBS");
conf0.set(TableInputFormat.SCAN_COLUMN_FAMILY,"cf");
conf0.set(TableInputFormat.SCAN_CACHEBLOCKS,"false");
conf0.set(TableInputFormat.SCAN_BATCHSIZE,"20000");
...其他hbase?/span>ConfigurationQ可以来自不同集?/span>
IteratedJob<Long> iJob = scheduler.createJob("testJob")
.from(Source.hBase(conf0), TransformerForHBase0.class)
.from(Source.hBase(conf1), TransformerForHBase1.class)
.from(Source.textFile("file:///home/cdh/0.txt"),Transformer0.class)
.join(JoinerHBase.class)
Hadoop中的input,现在完全?/span>sourcecL代替。通过内置的机制{化ؓinputformatQ?/span>inputsplit?/span>reader。在HST的框架下Q其实可以很Ҏ的写?/span>Source.dbms(),Source.kafka()以及Source.redis()Ҏ。想惛_Q在一?/span>hadoop job中,你终于可以将L数据源,例如来自不同集群?/span>HBASE和来自数据库?/span>sourceq行join了,q是多么happy的事情啊Q?/span>
3. q程日志pȝ。让mapper?/span>reducer的日志集中在driverq行昄Q极大减M了ƈ行多q程E序的调试难?/span>
各位都体验过Q?/span>job fail后到控制台页面,甚至ssh到计节点去查看日志的痛苦了吧。对Q?/span>hadoop原生的开发,调试很痛苦的呢!
现在好了Q有q程日志pȝQ可以在调试时将mapper?/span>reducer的日志集中在driver上,错误和各U?/span>counter也会自动发送到driver上,q实时显C在你的控制C。如果在eclipse中调试程序,可以实现点?/span>console中的错误Q直接蟩到错误代码行的功能喽Q?/span>
PsQ有人可能会问,如何在集外使用eclipse调试一?/span>jobQ却可以以集方式运行呢Q这里不再赘qCQ网上有很多{案的哦
4. 克服?/span>hadoop mapreduce?/span>join上,区分数据来自哪个数据源的困难Q接q?/span>sparkQ或?/span>sqlQ的水^
在上面给出示例中Q大安看到了,现在?/span>mapper可以l定input喽!Q也是每个input都有自己独立?/span>mapper。正因ؓ此,现在?/span>input?/span>mapper改名?/span>Source?/span>Transformer?/span>
那么Q大家又要问了,?/span>mapper中,我已l可以轻松根据不同的数据输入写出不同?/span>mapper了,?/span>reducer中怎么办,spark?/span>sql都是很容易实现的哦?比如看h?/span>sql
Select a.id,b.name from A a,B b where a.id = b.id
多么L愉悦?/span>!
在原?/span>hadoop mapreduce中,?/span>reducer中找出哪个数据对应来自哪?/span>input可是一个o人抓狂的问题呢!
现在q个问题已经被轻松解军_Q看下面q个joinerQ对应原生的reducer
public static class Joiner0 extends Joiner<Long, String, String>
…
ReduceҎ改名?/span>joinҎQ是不是更脓q?/span>sql的概念呢Q?/span>
public void join(Long key,RowHandler handler,Collector collector) throws Exception{
List<Object> row = handler.getSingleFieldRows(0);//对应索引?/span>0?/span>source
List<Object> row2 = handler.getSingleFieldRows(1);//对应W二个定义的source
注意上面两句Q可以按照数据源定义的烦引来取出来自不同数据?/span>join后的数据了,以后有时间可能会Ҏ按照别名来取出,大家看源码的时候,会发现别名这个部分的接口都写好了Q要不你来帮助实C吧?/span>
5. 天生的多U程执行Q即?/span>mapper?/span>reducer端都默认使用多线E来执行业务逻辑?/span>
看看源码吧,HST框架是ƈ发调?/span>flatMap?/span>joinҎ的,同时又不能改变系l调?/span>reduceҎ的顺?/span>(否则hadoop的辛苦排序可q瞎了)Q这可不是一件容易的事呢!
看到q里Q有的同学说了。你q个HST好是好,但你搞的自动转换cdq个机制可能会把性能拉下来的。这个吗Q不得不承认Q可能是会有一点媄响。但在生产环境做的比对可以证明,影响太小了,基本忽略不计?/span>
W者在生环境做了做了多次试验Q?/span>mapperҎ多线E后性能q未有提高,特别是对一些业务简单的jobQ增?/span>Transformer中的q发U别效率可能q会下降?/span>
很多同学喜欢在mapper中做所?#8220;mapper端的join”。这U方式,怿在HST中通过提高mapper的ƈ发别后会有更好的表现?/span>
Reducer中的性能相对原生提升的空间还是蛮大的。大部分的mapreduce目Q都是mapper单而reducer复杂QHST采用q发执行join的方式对提升reducer性能是超好的?/span>
6. 对于多次q代的Q务,相连的两个Q务可以徏立关联,在流E上的下一?/span>job直接引用上一?/span>job的结果,使多ơP代Q务的代码l构变得清晰优美
虽然在最后才提到q一点,但这却是我一开始想要写HST原因。多ơP代的d太麻烦了Q上一个Q务要写在hdfs做存储,下一个Q务再取出使用Q麻烦不ȝ。如果都q序自动完成,岂不哉Q?/span>
在上一个Q务里format一?/span>
IteratedJob<Long> iJob = scheduler.createJob("testJob")
...//各种source定义
.format("f1","f2")
在第二个d中,直接引用
IteratedJob<Long> stage2Job = scheduler.createJob("stage2Job")
.fromPrevious(iJob, Transformer2_0.class);
//Transformer2_0.class
public static class Transformer2_0 extends PreviousResultTransformer<Long>
...
public void flatMap(Long inputKey, String[] inputValues,Collector<Long> collector) {
String f1 = getFiledValue(inputValues, "f1");
String f2 = getFiledValue(inputValues, "f2");
看到没,是q么单?/span>
在最开始的计划中,我还设计了?/span>redis队列来缓冲前?/span>job的结果,供后面的job作ؓ输入。这h来必Mg行的job可以在一定程度上q发。另外还设计了子d的ƈ发调度,q都留给以后d现吧?/span>
7. 便捷的自定义参数传递?/span>
有时候,在业务中需要作一?#8220;开兛_?#8221;Q在q行时动态传入不同的g实现不同的业务逻辑。这个问?/span>HST框架其实也ؓ你考虑C?/span>
Driver中的自定义参敎ͼsource中的自定义参数都会以内置的方式传?/span>transformer?/span>joiner中去Q方便程序员书写业务?/span>
查看transformer?/span>joiner的源码就会发玎ͼ
getSourceParam(name)?/span>getDriverParam(pIndex)ҎQ在计算节点L的得到在driver?/span>source中设|的各层ơ别的自定义参?/span>,爽吧!
8. 其他工具
HST提供的方便还不止以上q些Q比如在工具cMq提供了两行数据Q?/span>mapcdQ直?/span>join的方法。这些都留给你自己去发现q实践吧!
https://github.com/jonenine/HST
eclipseq_提供runtime方式调试插g?/span>RCP目Q但随着插g目写U复杂,启动旉也越来越长,特别是集成了诸如 Hibernate?/span>Spring之类的容器框架的时候。仅仅ؓ了调试代码中一些琐的片段而频J的重启目实在是一件异常烦人的工作?/span>
即重启了项目也许还没完。ؓ了ə目处于某个特定的状态下以方便测试,每次都要重新操作一遍前面业务流E,q同h十分令h厌倦的?/span>
eclispe使用?/span>OGSI作ؓ微内核,引入了一些动态特性。但?/span>OSGI的动态特性是在保持^台运行的情况下动态更?/span>BundleQ也是说需要重启插件才能完成动态加载的q程。有没有一些更加细_度的动态蝲入方案呢Q?/span>
?/span>Tomcat下开发过web目的h都知道,使用调试模式来部|项目即”热部|?/span>”可以实现动态蝲?/span>class文g,让程序员得以动态调试项目。今天向大家提供的这?/span>jar包得这U效果可以在eclipse runtime上实现?/span>
q是我在自己的插件^台项目—?/span>SCOOP框架中用的几个包?/span>
它可以非常好的解军_态类载入的问?/span>,包括内部cȝ动态蝲入都可以很好解决?/span>
其他的几个包q进行了以下试
1. 使用元数据标注的办法解决SWT UI U程的种U问?/span>
2. q提供了eclipse程框架的简单实玎ͼ以规范插件开发。特别是提出了一个面向业务而不是面向技术的工作概念,使得~码_度变大Qƈ得以提高效率。另外这个简单的程框架q将前面的两U机制很好的l合hQƈ且可以和eclipseq_的一些复杂机制解耦,为复杂流E的开发测试提供了方便。ؓ来实现自定义脚本语a(比如某个cM?/span>BPEL的工作流语言)开?/span>eclise插g目甚至使用囑Ş化的开发奠定了一定的基础?/span>
我给Z一个完整的CZ—?/span>JAXB插g。很多框架同jaxb一h供了code generation工具Q可以在q个例子的基上经q简单修改ؓq些框架提供插gQ比?/span>CXF插g?/span>AXIS2插g{等?/span>
动态类载入~码原则
使用动态类载入机制来进行调试在~码上有一定限制?/span>
首先是要q行动态蝲入的实例不要在非动态域q行引用。只有这P当一个流E结束时此实例才会在jvm中得以释放。当Ӟ非要在其他地方进行引用从而长久的在运行时保持q个实例也是有解x案的(可以使用代理cL术来实现Q具体解军_法不在本文之?/span>)?/span>
其次是进行接口同实现cȝ分离或父cd子类的分,以隔M同的class load scope。接口由父类载入器蝲入,不同的实?/span>(比如修改后的实现)׃同的子类载入器蝲入。得最l同一个类型由同一个类载入器蝲入,q样才能W合jvm的类载入规范。在父类载入域的父类型的Ҏ的参数类型及q回值类型也不能在动态域中?/span>
最?/span>,那些注册在扩展点上的cdActionDelegate?/span>WorkbenchPart{是不能够动态蝲入的Q他们必ȝeclipse提供的类载入器蝲入(q_会自动蝲入ƈ理其生命周期)。如果需要让q些cM动态蝲入,需要在q_提供的动态注册机制基上用代理或?/span>EJB2.0一样用R入式~译来实C理机Ӟq个话题同样不在本文之内?/span>
下面׃D例说明用方?/span>
因ؓ旉有限我无法详的完成此文Q请感兴的朋友自己阅读CZ源码?/span>
1. 几种动态类载入的办?/span>
(1) 使用手动~码q行c蝲?/span>
因ؓ会二次开发h员生迷惑,故未举例
(2) 在调试时使用spring文g配置动态类载入?/span>
Spring配置文g同样也是动态的Q而且会调试开发工作变得更加清晎ͼ推荐使用
(3) 使用Flow框架来进行动态类载入
2. 使用元数据标注解?/span>UIU程讉K的问?/span>
使用三个元数据及动态代理类解决SWT?/span>Eclipse的线E问题?/span>
3. ?/span>eclipse内部机制解耦以方便开发和试
开发插仉目很多时候需要实?/span>Eclipse内部的一些回调接口来实现功能Q这对程序员的水qx一U考验Q也使得插g开发工作更加复杂化。比如在实现progressMonitor的时候,需要实现它的回调接口,业务逻辑攄在其指定的回调接口—?/span>runable接口来实玎ͼq是非常不方便的。我们需要是一U可以提供功能的工具c,像调用一个普?/span>javaBean一h调用它,而不是将我们的业务代码变形撕去融入?/span>Eclipse的种U机制中厅R?/span>
q样做的另外一个坏处就是很难进行单元测试,比如qeclipseq_Q用一?/span>mockcLq行单有效的单元试?/span>
我在q方面也q行了一些尝试—?/span>”反客Z”Q将必须遵守eclipse的回调要求变为必遵守业务开发简单快L要求。同h?/span>progressMonitor上面Q用工厂类来创?/span>delegateQ然后可以在回调机制的外部向调用javaBean一h使用q_l我们提供的q个功能?/span>
q种试是有一定难度的Q要使用C同的设计模式Q处理各U线E问题。更重要的一Ҏeclipseq_本n有这L潜力Q它也在{待着我们q样做?/span>
4. 使用Flow规范插g目开?/span>
我的scoop目最l搁,到最后我发现实现它已l超Z我当时的能力。我原本是想开发一个统一的插件开发及部vq_。这样很多中Y件企业,特别是像我原来工作的那家公司可以拥有自qeclipse插g集,以适应自己Ҏ的要求。我q想提供一套面向插件开发业务的接口Q而不再面向技术也屏蔽各种技术细节,使得可以非常方便的扩展、修改甚至移植插件。我只是心里有想念就d现而已Q当最l认识到它有多么困难的时候不得不攑ּ了?/span>最后虽然失败了Q但q不觉得气馁。因为知道了要努力的方向Q同时也U篏了丰富的l验。最后就这?/span>jar包命名ؓSCOOP已示U念?br />
/Files/jonenine/Eclipse_Dynamic_Classload.rar