关于“异步”,从Amazon的工作流框架中获得的思考
紧接着上篇文章,云平台的工作流框架AWS Flow Framework给我带来的另一个有所感触的话题是“异步”:
这个框架把异步的行为划分为Workflow端执行的部分和Activity端执行的部分,Workflow控制工作流程,Activity执行具体的工作流task,二者都以poll的模式不断从中心SWF去获取任务。对于开发者来说,用类似这样简单的代码,就完成了整个工作流任务的部署,框架为开发人员隐藏了大部分实现细节:
@Workflow public interface CalculateWorkflow { @Execute public void calculate(); } public class CalculateWorkflowImpl implements CalculateWorkflow { …… public void calculate() { Promise<String> val1 = activity1.calc(); Promise<String> val2 = activity2.calc(); printResult(val1, val2); } @Asynchronous public void printResult(Promise<String> val1, Promise<String> val2) { System.out.println("Result: " + val1 + " and " + val2); } }
对于这样一段代码(再配合Activity的代码),做了两点处理:
1、activity调用的逻辑,返回值通过Promise包装:
Promise<String> val1 = activity1.calc(); Promise<String> val2 = activity2.calc();
2、@Asynchronous所修饰的方法会被处理成本地执行的异步方法,其中的参数使用Promise包装:
public void printResult(Promise<String> val1, Promise<String> val2)
这样一来,这里面包含了三个异步任务:
a、Activity1端执行的 activity1.calc(),
b、Activity2端执行的 activity2.calc(),
c、等待上述两个任务执行完毕后再在Workflow端执行的printResult。
其中,使用Promise包装的参数,它的作用就在此,在上述两个异步的Activity方法执行返回后,才会触发printResult方法的执行(printResult方法依赖于两个Activity方法执行结果的返回),可是,这和一般等待执行的异步流程merge操作不同的是,在等待过程中它不会block住线程(反例可以参考Future类,它的get方法是会block住线程的),最大限度地减少了资源的占用。
这只是其中利用异步的设计,给我印象很深刻的一个场景。对于减少资源占用,我们还经常接触到许多其它类似的场景,比如NIO Server,这是多路复用技术的一种。
传统服务器接收到请求以后,从request到response,整个过程占住一个线程不放,但是这种服务器可以在收到请求之后,将需要完成的工作用一个或若干个Command包装好,交给线程池中的工作线程去完成,对于收到请求和返回响应这样的过程,使用1~2个独立的线程去完成,在事件发生时,系统线程才通知这1~2个线程去完成和客户端的一次交互(或者用监控线程去轮询当前挂在Server上所有的连接,寻找需要响应的连接来完成交互)。
当在线用户数量大大超出服务器的线程数时,使用NIO模式可以保证在收到请求和返回响应的用户接口层不成为瓶颈。
另外还有一个特别值得一提的场景,由于互联网的BS模式下,从Server实时或准实时地向Client推送一些东西是比较麻烦的(有同学说用pushlet来解决,但这个办法有很大的局限性),有一种解决这个问题的办法是客户端使用ajax去定期获取数据(比如现在的很多SNS网站都用了这种办法),在这种情况下服务端对请求处理的压力就陡然增大了。
Nginx是一款高性能的代理服务器,性能高的其中一个重要原因是,它基于epoll模型设计的。与之相对的是select模型,select采用的是轮询的办法,每次读写状态检测都检查FD_SET中所有的句柄,当句柄数量增大时,这个过程消耗的时间应该是线性增长的。另外,FD_SIZE也设定了整个可开启的句柄数,这造成了另一种局限。
epoll模型就没有这两个问题,它给每个活跃FD挂接一个异步回调函数,不需要第三方去遍历和调用这些句柄,而它的句柄限制是操作系统的限制。
关于Continuation Server。这种模式下Client和Server会调过来,Continuation Server发送页面给客户端,作为function call,然后等待客户端返回执行结果(SendPageAndWait),因此,在服务端收到响应的时候,要恢复之前方法调用的上下文环境,继续执行收到响应后的下一行语句。
这个过程就需要服务器端能够在发送页面前将上下文环境保存下来,在获得响应之后动态获取调用栈恢复上下文环境,这些工作都是异步事件驱动的。
不能不说一说Node.js,这是JavaScript在2011年最火爆的词语之一,其实服务端js不是什么新东西,只不过因为Node.js,人们高呼,JavaScript居然也可以这样快!大概V8引擎做的编译优化太好了,许多前端工程师第一次发现自己的价值其实远不止在页面上。Node.js把复杂的事件处理框架隐藏起来,开发人员只需要关注业务接口的调用。
Node.js所有的事件触发的调用都是异步和非阻塞的,它对高并发的访问有很好的承受能力(Node宣称他们的服务器每一台都可以承受几万连接并发)。如果服务端面对一场subscribe的灾难,虽然有足够大的队列来承受瞬间的高并发访问,但是瓶颈在请求接收和响应上,那么Node.js应该能成为一个有价值的解决方案。
最后,对于Jscex,还有Barrier模式,这篇文章已经介绍过了,故不赘述。
异步调用给传统软件编码的思维带来了新的挑战,无论是对现场的快照、异常的处理还是分支跳转的控制,但是带来了许多不可替代的优势,资源占用更少,效率更高,可扩展性更强。
文章系本人原创,转载请注明出处和作者