反应式编程框架设计:如何使程序调用不阻塞等待,立即响应?

在之前的文章中就讨论过为什么在高并发的情况下,程序会崩溃 。主要原因是,在高并发的情况下,有大量用户请求需要程序计算处理,而目前的处理方式是,为每个用户请求分配一个线程,当程序内部因为访问数据库等原因造成线程阻塞时,线程无法释放去处理其他请求,这样就会造成请求堆积,不断消耗资源,最终导致程序崩溃 。

反应式编程框架设计:如何使程序调用不阻塞等待,立即响应?

文章插图
 
这是传统的 Web 应用程序运行期的线程特性 。对于一个高并发的应用系统来说,总是同时有很多个用户请求到达系统的 Web 容器 。Web 容器为每个请求分配一个线程进行处理,线程在处理过程中,如果遇到访问数据库或者远程服务等操作,就会进入阻塞状态,这个时候,如果数据库或者远程服务响应延迟,就会出现程序内的线程无法释放的情况,而外部的请求不断进来,导致计算机资源被快速消耗,最终程序崩溃 。
那么有没有不阻塞线程的编程方法呢?
一、反应式编程答案就是反应式编程 。反应式编程本质上是一种异步编程方案,在多线程(协程)、异步方法调用、异步 I/O 访问等技术基础之上,提供了一整套与异步调用相匹配的编程模型,从而实现程序调用非阻塞、即时响应等特性,即开发出一个反应式的系统,以应对编程领域越来越高的并发处理需求 。
人们还提出了一个反应式宣言,认为反应式系统应该具备如下特质:
  • 即时响应,应用的调用者可以即时得到响应,无需等到整个应用程序执行完毕 。也就是说应用调用是非阻塞的 。
  • 回弹性,当应用程序部分功能失效的时候,应用系统本身能够进行自我修复,保证正常运行,保证响应,不会出现系统崩溃和宕机的情况 。
  • 弹性,系统能够对应用负载压力做出响应,能够自动伸缩以适应应用负载压力,根据压力自动调整自身的处理能力,或者根据自身的处理能力,调整进入系统中的访问请求数量 。
  • 消息驱动,功能模块之间,服务之间,通过消息进行驱动,完成服务的流程 。
目前主流的反应式编程框架有 RxJAVA、Reactor 等,它们的主要特点是基于观察者设计模式的异步编程方案,编程模型采用函数式编程 。
观察者模式和函数式编程有自己的优势,但是反应式编程并不是必须用观察者模式和函数式编程 。Flower 就是一个纯消息驱动,完全异步,支持命令式编程的反应式编程框架 。
下面我们就看看 Flower 如何实现异步无阻塞的调用,以及 Flower 这个框架设计使用了什么样的设计原则与模式 。
二、反应式编程框架Flower的基本原理一个使用 Flower 框架开发的典型 Web 应用的线程特性如下图所示:
反应式编程框架设计:如何使程序调用不阻塞等待,立即响应?

文章插图
 
当并发用户到达应用服务器的时候,Web 容器线程不需要执行应用程序代码,它只是将用户的 HTTP 请求变为请求对象,将请求对象异步交给 Flower 框架的 Service 去处理,自身立刻就返回 。因为容器线程不做太多的工作,所以只需极少的容器线程就可以满足高并发的用户请求,用户的请求不会被阻塞,不会因为容器线程不够而无法处理 。相比传统的阻塞式编程,Web 容器线程要完成全部的请求处理操作,直到返回响应结果才能释放线程;使用Flower 框架只需要极少的容器线程就可以处理较多的并发用户请求,而且容器线程不会阻塞 。
用户请求交给基于 Flower 框架开发的业务 Service 对象以后,Service 之间依然是使用异步消息通讯的方式进行调用,不会直接进行阻塞式的调用 。一个 Service 完成业务逻辑处理计算以后,会返回一个处理结果,这个结果以消息的方式异步发送给它的下一个Service 。
传统编程模型的 Service 之间如果进行调用,被调用的Service 在返回之前,调用的 Service 方法只能阻塞等待 。而 Flower 的 Service 之间使用了 AKKA Actor 进行消息通信,调用者的 Service 发送调用消息后,不需要等待被调用者返回结果,就可以处理自己的下一个消息了 。事实上,这些 Service 可以复用同一个线程去处理自己的消息,也就是说,只需要有限的几个线程就可以完成大量的 Service 处理和消息传输,这些线程不会阻塞等待 。
我们刚才提到,通常 Web 应用主要的线程阻塞,是因为数据库的访问导致的线程阻塞 。Flower 支持异步数据库驱动,用户请求数据库的时候,将请求提交给异步数据库驱动,立刻就返回,不会阻塞当前线程,异步数据库访问连接远程的数据库,进行真正的数据库操作,得到结果以后,将结果以异步回调的方式发送给 Flower 的 Service 进行进一步的处理,这个时候依然不会有线程被阻塞 。


推荐阅读