如何用反应式编程提升系统性能与可用性?

前言


反应式编程这两年愈来愈热,很多人都知道著名的反应式宣言


即时响应:只要有可能,系统就会及时地做出响应。


弹性:系统在出现失败时依然保持即时响应性。


回弹性:系统在不断变化的工作负载之下依然保持即时响应性。


消息驱动:反应式系统依赖异步的消息传递,从而在确保系统松耦合、 隔离和位置透明。


那么反应式程序究竟在运行层面是怎样的?对软件系统有哪些改进?如何开发一个反应式程序呢?


在最近的一年时间,我们在同程艺龙开发了一个反应式编程框架并应用于一些典型的应用场景在这些场景中系统性能和可用性都得到较大提升


程序是如何运行又是如何崩溃的?


为什么要进行反应式编程的尝试?我们先从传统的编程方法引发的问题说起。


传统的后端程序开发事实上都是多线程开发但是很多开发工程师并没有感觉到自己是在进行多线程开发因为自己在程序中并没有创建线程。其实这是因为多线程通常是由Tomcat这样的Web容器创建的我们开发的程序由Web容器创建的多线程调用执行,后端工程师开发的程序同样要考虑多线程的问题。


这样的应用系统的线程模型通常是这样的。


如何用反应式编程提升系统性能与可用性?


对于一个高并发的应用系统,同时总是有很多个用户请求到达系统的Web容器。Web容器为每一个请求分配一个用户线程去进行处理,而容器能够启动的用户线程数目是有限的。如果当前所有的容器线程都已经被用完了,这时候还有新的用户请求到达,请求就会被阻塞在应用服务器,等待前面的线程释放,或者直接返回服务器错误。


而线程在运行期可能会遇到各种阻塞情况导致线程无法执行下去。比较典型的就是访问数据库一个用户程序,想要访问数据库,必须要获得数据库的连接,而数据库的连接数相对用户线程数是比较少的。当数据库连接用完以后,线程请求获得数据库连接的时候就会被阻塞。而对于得到了数据库连接的线程,去访问数据库的时候,当它将数据库操作请求发送以后,数据库在远程进行数据处理的时候,当前的线程依然会被阻塞。这些被阻塞的线程既无法去响应其他的用户操作,也无法完成自己的工作,只能白白地消耗系统的资源。如果遇到某种情况,比如说数据库因为某个慢查询而响应比较慢,那么大量的用户线程就会堆积阻塞在数据访问这里无法得到释放,响应时间变长。而新的请求又会不断的到达,不断的消耗系统资源,最后可能会导致系统崩溃。


反应式编程框架Flower的解决之道


针对上述传统的阻塞式编程的缺点,我们基于Akka(https://akka.io/)开发了一个全异步的反应式编程框架Flowerhttps://github.com/zhihuili/Flower)


如何用反应式编程提升系统性能与可用性?


使用Flower开发的Web应用,只需要有限的几个线程,就可以完成全部的用户请求操作。当并发用户到达应用服务器的时候,Flower只需要极少的容器线程就可以处理所有的并发用户请求。这个线程并不会执行真正的业务操作,它只是将用户的请求变为请求对象以后,将请求对象异步交给Flower的Service去处理,自身立刻就返回。因为容器线程不做太多的工作,所以极少的容器线程就可以满足高并发的用户请求,用户的请求不会被阻塞,不会因为容器线程不够而无法处理。


用户请求交给Flower的Service对象以后,Service之间依然是使用异步的消息通讯的方式进行调用,Service之间也不会直接进行阻塞式的调用。一个Service完成业务逻辑处理计算以后,会返回一个处理结果,这个结果以消息的方式异步地发送给他的下一个Service,Service之间使用了Akka Actor进行消息通信,也是只需要有限的几个线程就可以完成大量的Service处理和消息传输。


我们刚才提到Web应用主要的线程阻塞,是因为数据库的访问导致的线程阻塞。Flower支持异步数据库驱动,用户请求数据库的时候,将请求提交给异步数据库驱动,立刻就返回,不会阻塞当前线程,异步数据库访问连接远程的数据库,进行真正的数据库操作,得到结果以后,将结果以异步回调的方式发送给Flower的Service进行进一步的处理,这个时候依然不会有线程被阻塞。也就是说使用Flower开发的系统,在一个典型的Web应用中,几乎没有任何地方会被阻塞,所有的线程都可以被不断的复用,有限的线程就可以完成大量的并发用户请求,从而大大地提高了系统的吞吐能力和响应时间,同时,由于线程不会被阻塞,应用就不会因为并发量太大或者数据库处理缓慢而宕机,从而提高了系统的可用性。


基于Flower框架开发一个异步反应式系统的时候,只需要实现Flower的Service接口。


public class ServiceA implements Service<Message2> {  @Override  public Object process(Message2 message) {    return message.getAge() + 1;  }}

然后将这些Service按照处理流程,进行流程编排。就可以得到一个异步的反应式系统。


getServiceFlow().buildFlow("ServiceA", "ServiceB");

通过流程编排的Service之间没有任何耦合,Service之间的调用不需要任何依赖,只需要将这些Service编排在一个流程中就可以了。


Flower的Service可以异步通信,主要是基于AKKA Actor进行异步通信的,那么AKKA Actor又是如何实现异步的消息通信的呢?


如何用反应式编程提升系统性能与可用性?


一个Actor向另一个Actor进行通讯的时候,当前Actor就是一个消息的发送者sender,当他想要向另一个Actor进行通讯的时候,他需要获得另一个Actor的ActorRef,也就是一个引用,通过引用进行消息通信。而ActorRef收到消息以后,会将这个消息放入到Actor的Mailbox里面去,然后就立即返回了。也就是说一个Actor向另一个Actor发送消息的时候,不需要另一个Actor去真正的去处理这个消息,只需要将消息发送到目标Actor的Mailbox里面就可以了。自己不会被阻塞,可以继续执行自己的操作。而目标Actor检查自己的Mailbox中是否有消息,如果有消息,Actor则会在从Mailbox里面去获取消息,对消息进行异步的处理,而所有的Actor会共享线程,这些线程不会有任何的阻塞。


反应式编程性能和可用性改善效果


我们在同程艺龙的一些典型产品中进行了Flower应用落地,实践表明,Flower在提升系统性能和可用性方面都有非常大的改进。


如何用反应式编程提升系统性能与可用性?


这是在某产品核心查询接口利用Flower重构前后的性能对比,左边这张图是TPS的对比,右边这张图是响应时间的对比,我们可以看到,重构后,TPS吞吐能力提升一倍,而平均响应时间则降低了一倍,性能提升显著。


除了性能提升外,使用Flower开发的Web应用的可用性也会得到提升。


如何用反应式编程提升系统性能与可用性?


这是一个典型的互联网微服务应用架构,并发用户请求进入系统网关后,调用相关微服务完成具体业务处理。但是如果某个微服务出现故障,比如服务1,响应延迟比较厉害,可能会导致服务2也无法正常访问,事实上,可能会导致整个网关失效,系统整体宕机。


如我们前面分析,网关作为一个Web应用,使用传统的同步阻塞式方式调用服务1和服务2,那么当服务1响应延迟的时候,就会阻塞网关的线程,而网关的线程是有限的,严重情况,可能会阻塞所有调用服务1的网关线程,导致网关假死。这时,即使服务2是正常的,也无法正常访问了。通常网关是集群部署的,如果所有的网关都调用了服务1,那么整个系统都会不可用。


我们用Flower框架对网关进行了重构,并使用异步HTTP Client调用服务1和服务2,这样对服务的调用不会占用网关的线程,当服务1响应延迟的时候,服务2的访问是正常的,系统虽然部分功能失效,但是整个系统是可用的。


小结


没有人能够预言未来,也没有人能够断言未来的编程是什么样。但是我们纵观编程的历史就会发现,编程技术的进步驱动力主要来自两个方面


一是使程序具有更低的耦合性。编程语言的进步,从汇编语言到面向过程的编程语言,再到面向对象的编程语言,在编程语言层面就使得程序的耦合性越来越低。而我们在开发过程中使用的各种编程框架,MVC、ORM等等,也使代码之间的关系变得更加清晰,耦合变得更低。


二是使程序运行速度更快在编程层面,从多线程技术,到对象池、连接池各种池化复用技术,再到各种异步IO技术,目的都是使程序占用尽量少的资源、更多的并行执行,从而使执行速度更快。在架构层面,各种分布式集群技术、分布式缓存技术、分布式消息队列技术,主要目标也都是为了提高性能。


从这个角度看,未来的编程技术也一定是在这两个方面进行创新性的改进。


反应式编程框架Flower在低耦合方面使得服务之间的调用不再直接依赖,而是通过流程编排的方式将多个服务关联起来,完成一个业务逻辑处理。在性能方面,Flower将整个计算处理过程全部异步化,更少线程切换、避免线程阻塞,从而获得更好的执行性能。从某种程度讲,Flower遵循编程技术的进步趋势并进行了一定的创新。


事实上,Flower对反应式的支持并不止文中提到的这些特性。如果你对反应式编程以及Flower框架感兴趣,欢迎体验Flower并加入Flower的开发,Flower开源地址:https://github.com/zhihuili/Flower


云服务器ECS地址:阿里云·云小站

上一篇:甲骨文宣布推出Oracle开发人员仓库


下一篇:ORA-12154: TNS:could not resolve the connect identifier specified