关于“ 异步”,从 Amazon 的工作流框架中获得的思考

云平台的工作流框架 AWS Flow Framework 给我带来的另一个有所感触的话题是“ 异步”:

1

这个框架把异步的行为划分为 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 模式,在此不展开。

异步调用给传统软件编码的思维带来了新的挑战,无论是对现场的快照、异常的处理还是分支跳转的控制,但是带来了许多不可替代的优势,资源占用更少,效率更高,可扩展性更强。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》

7,397 次阅读

发表评论

电子邮件地址不会被公开。

back to top