上一篇文章中我们聊了一下线程池,基于线程池的多线程编程是我们在高并发场景下提升系统处理效率的有效手段,但却不是唯一的。今天我们来看一下另一种异步开发的常用手段-响应式编程模型
传统多线程模型的缺陷
多线程模型是目前应用最为广泛的并发编程手段,但凡遇到什么性能瓶颈,首先想到的就是弄个线程池把任务丢进去并发执行,但它其实存在一些明显的缺陷。我们先来看一个例子,在一个web服务中,某个接口需要完成如下的任务:
-
1. 从数据库中查询一个数据
-
2. 调用另一个微服务的接口拿到另一个数据
-
3. 将两个数据组合起来并返回给用户
一个简化后的线程处理时序图可能是这样的:
从上图可以看出,不同用户的请求可以在线程池的线程并发的执行,但是单个用户的处理过程却仍然是串行执行的,而且由于数据库查询,调用微服务接口,写HTTP返回值都是典型的IO操作,导致线程大多数时间都处于阻塞状态。
如果是高并发场景,比如此时有1000个用户同时需要调用该接口,如果没有对线程池的数量加以限制的话,最高可能会创建1000个线程来处理用户请求,而这些昂贵的系统资源偏偏大部分时间都处于阻塞之中,这无疑是一个巨大的浪费。
响应式开发模型
看了上面的例子,小伙伴们很容易想到,如果线程在等待IO的过程中不需要保持活动状态,而能够去处理其它一些无需等待的任务,那不就不会浪费了吗。响应式编程就是要解决这个问题的,我们可以根据任务类型创建两类线程池,一类是主线程池,专门处理CPU密集型任务,这类的任务几乎不会阻塞;而需要等待IO的任务专门交给另一类IO线程池去处理。于是上述例子的处理时序图由变成了这样:
这样做了之后能带个几个好处:
-
1. 数据库查询和外部微服务的接口调用可以并发执行,这可以缩短响应时间;
-
2. 主线程池里面只需要很少的线程即可满足高并发的需求,因为它们几乎不阻塞,而需要阻塞的操作都放到IO线程中处理,这在一定程度上降低了系统整体的线程数量。
但那个烦人的红色的阻塞块依然存在,有办法彻底搞定它吗?遗憾的是,这个就没有一个简单的答案了。要想彻底的消灭阻塞,我们无法在应用代码层面解决问题,还需要底层数据源的支持。为了解释清楚这一点,我们还是要先了解下响应式编程的实现原理。
响应式编程其实底层的主要原理就是观察者模式,在此种模式中,一个目标对象管理所有依赖于它的观察者对象,并且在它本身的状态改变时主动发出通知。这种模式其实在我们的日常生活中也很常见,比如微信公众号,只要我们关注了某个公众号,我们不需要时刻去关注有没有新的文章,在有新文章发表的时候微信会主动的推送相关的通知给我们。
了解了这些以后,我们再回到刚才的例子。要想实现数据库访问和接口调用的完全响应式处理,就必须要得到底层数据库和TCP通讯层的支持,这个道理很简单,如果数据库不支持非阻塞的主动通知模式,那么调用方只能选择通过阻塞式的调用获取数据。不过好在不管是操作系统还是语言层面,目前大部分我们常见的IO访问目标都已经能够支持响应式的访问模式了。比如基于Java NIO就对socket,文件提供了非阻塞的访问模式,而Netty这样的三方框架更是可以借助操作系统底层的一些机制(linux epoll),实现更高性能的网络数据读写。一旦底层的数据源支持响应式的访问,那么我们的处理时序图就可以最终进化到完全形态:
这种情况下,线程几乎只在需要进行CPU计算的时候才会被激活,这意味着线程池中只需要保持很少量的线程即可满足大并发场景下的处理需求,极大的提升了系统的处理效率,降低了资源占用。
响应式编程的应用
响应式编程最成功的使用案例,当属Node.js了。它号称单线程就能支撑高并发请求的背后,正是基于响应式编程的思想,通过事情驱动和非阻塞IO来实现的异步模型。
当然,Node.js并非真正意义上的单线程,只是它的执行线程只有一个(利用V8引擎对JS进行解释执行),在进行I/O操作时Node.js再将任务交由worker线程池中的线程执行,并在事件队列中注册回调,在I/O操作完成时触发回调继续后续的动作,在整个I/O操作的过程中并不会阻塞JS的解释执行。
Java领域,Spring生态也基于推出了响应式web开发的一栈式框架:Webflux,依托于Spring的强大生态能力,能够实现web应用全链路的响应式开发,而且可以保持大部分情况下和传统Spring mvc应用开发一致的开发体验。
一栈式开发对于响应式编程而言是非常重要的,因为整个处理链路中只要有一个“堵点“(阻塞调用),那么响应式的处理不但无法带来性能提升,还可能由于频繁的线程上下文切换而影响性能,这是spring webflux最大的优势。其它的诸如RXJava,JDK 9自带的Flow API也都是常见的响应式框架,不过它们并不像spring webflux那样是一个全栈式的框架,而是更侧重于通用性,以及解决某个具体切面的问题。
总结
完全的响应式开发确实能在性能和资源占用上给系统带来显著的提升,但是它需要实现整个功能处理链路的完全异步化。这依赖于开发者小心的将功能任务划分到多个线程(池)之间进行处理,并通过事件通知机制来进行相互协调,这对开发者的心智负担是非常大的。
虽然有框架的辅助,但响应式编程的难度仍然是要显著的高于传统的命令式开发模型,再加上大多数业务应用并没有那么高的性能要求,导致目前响应式编程依然只是 ”看起来很美“,只在部分中间件和Android开发中使用的相对多一些。不过这并不妨碍我们了解响应式编程背后的原理,一种技术只有在我们能够清醒的认识它的优势和劣势的情况下,才能够在合适的场景去使用它,这是一名合格架构师的基本素质。
本文采摘于网络,不代表本站立场,转载联系作者并注明出处:https://www.iotsj.com//kuaixun/3074.html