架构设计系列文章,请参见连接。
背景
很早之前就想写Actor相关的介绍文章。怎奈没有好时机去介绍这部分内容。前几天准备研究一下Spring Cloud监控管理的内容,发现一个叫做Spring Boot Admin的项目。研究了一段时间后发现没有办法完美的解决我的问题,所以就准备研究一下改改代码。发现这个项目用了Spring的响应式编程的方法编写整个项目,所以这里就着这个机会把设计进行Actor介绍。
在接触Spring WebFlux之前,作者本人认为Actor其实是MapReduce的顶层的设计模式。可以为大量数据处理提供快速处理方案,并且解决线程管理工作。这两个问题都是程序员很难解决或者不愿解决的问题。后来想想其实在很多技术领域中都用到了这些概念。例如:JavaScript就是单线程事件驱动的模式完成了高吞吐量,libevent这种C语言的事件驱动库,基于libevent的memcached也是使用这种机制完成的,Nginx的实现方式也是基于这种模式去定义Work进程的。
思考
从计算机被发明到现在,计算机主要处理两种问题:大量的吞吐和大量的计算。例如:在业界著名的C10K问题是关于大吞吐的问题,用户建模过程其实就是一个要求算力的问题。对于IO密集型和CPU密集型的处理方式和方法,需要进行思考与设计。在计算机专业的《算法导论》课上就是专门的解决这些问题的。对于通用型处理过程中的IO密集型和CPU密集型也需要有专业的解决方案以及统一的思维模式。故对于IO密集型和CPU密集型通用问题处理的思考:
Netty基于单线程设计的EventLoop能够同时处理成千上万的客户端连接的IO事件,缺点是单线程不能够处理时间过长的任务,这样会阻塞使得IO事件的处理被阻塞,严重的时候回造成IO事件堆积,服务不能够高效响应客户端请求。所谓时间过长的任务通常是占用CPU资源比较长的任务,也即CPU密集型,对于业务应用也可能是业务代码的耗时。这点和Node是极其相似的,我可以认为这是基于单线程的EventLoop模型的通病,我们不能够将过长的任务交给这个单线程来处理,也就是不适合CPU密集型应用。
那么问题怎么解决呢,参照Node的解决方案,当我们遇到需要处理时间很长的任务的时候,我们可以将它交给子线程来处理,主线程继续去EventLoop,当子线程计算完毕再讲结果交给主线程。这也是通常基于Netty的应用的解决方案,通常业务代码执行时间比较长,我们不能够把业务逻辑交给这个单线程来处理,因此我们需要额外的线程池来分配线程资源来专门处理耗时较长的业务逻辑,这是比较通用的设计方案。
换句话说,利用有限的线程,来充分利用多核的优势。
方案
需要解决现实中的业务问题就需要进行。
事件驱动
设计模式
EventDelegate(事件委托)
在JavaScript和C#都有一种叫事件委托的编程方式。虽然这两种事件委托方式的机制不同,但是它为我们提供了关于发布/订阅的一种机制。提供了将事件发生和事件处理者之间进行隔离与解耦的机制,并为之后事件触发机制提供实现模式。
EventLoop(事件循环)
事件轮询和事件等待是事件处理的两种机制,事件循环的机制其实就是一种事件等待的机制。一般情况下使用*nix(linux,unix,win...)上提供的时间轮询机制降低CPU使用率又可以获取事件。对于EventLoop中由专门的事件接收处理,事件分发机制,事件业务处理。在单线程中进行相关的这三部分都在一个线程中完成。
Actor
Actor模型是1973年提出的一个分布式并发编程模式,在Erlang语言中得到广泛支持和应用。在Actor模型中,Actor参与者是一个并发原语,简单来说,一个参与者就是一个工人,与进程或线程一样能够工作或处理任务。可以将Actor想象成面向对象编程语言中的对象实例,不同的是Actor的状态不能直接读取和修改,方法也不能直接调用。Actor只能通过消息传递的方式与外界通信。每个参与者存在一个代表本身的地址,但只能向该地址发送消息。
Reactor
Reactor模型:
1.向事件分发器注册事件回调
2.事件发生
3.事件分发器调用之前注册的函数
4.在回调函数中读取数据,对数据进行后续处理
Proactor
Proactor模型:
1.向事件分发器注册事件回调
2.事件发生
3.操作系统读取数据,并放入应用缓冲区,然后通知事件分发器
4.事件分发器调用之前注册的函数
5.在回调函数中对数据进行后续处理
响应式编程的主流实现技术
目前,响应式编程的主流实现技术包括 Rxjava、 Akka Streams、Vert.x和Project Reactor等。
- Rxjava
Reactive Extensions(Rx)是一个类库,它集成了异步基于可观察( Observable)序列的事件驱动编程,最早应用于微软的NET平台。而 Rxjava是 Reactive Extensions 的Java实现用于通过使用 Observable/ Flowable序列来构建异步和基于事件的程序库,目前有1x版本和2.x版本两套实现。
- Akka Streams
Akka也是响应式流规范的初始成员,而 Akka Streams是以Akka为基础的响应式流的实现,在Akka现有的角色模型之上提供了一种更高层级的抽象,支持背压等响应式机制。
- Vert.x
Vert.x是为了构建响应式系统而设计的,基于事件驱动架构, Vert x实现了非阻塞的任务处理机制。Vert.x中包含Vert.x Reactive Streams工具库,该工具库提供了Vert.x上响应式流规范的实现。我们可以通过Vert.x提供的可读流和可写流处理响应式流规范中的发布者和订阅者。
- Project Reactor
Spring 5中引入了响应式编程机制,而Spring5中默认集成了 Project Reactor作为该机制的实现框架。 Reactor诞生较晚,可以认为是第二代响应式开发框架。所以,它是一款完全基于响应式流规范设计和实现的工具库。WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。参考官网(https://projectreactor.io/)
总结
响应式编程和事件驱动模式有着密切的关系。它有事件驱动的很多特点:高性能,异步处理等。并为我们提供了并发能力与分发能力,这部分是大部分程序员都无法管理的内容。有了响应式编程后可以解决很多关于性能,关于异步拆分的功能。本文只介绍了异步编程的一点概念,接下来会深入的进行响应式编程的讨论。
参考:
反应式宣言
IO设计模式:Actor、Reactor、Proactor
aio_write - asynchronous write
eventloop & actor模式 & Java线程模型演进 & Netty线程模型 总结
响应式架构:消息模式Actor实现与Scala、Akka应用集成
实战SpringCloud响应式微服务系列教程(第三章)
Reactor 指南中文版V2.0
使用 Akka Actor 和 Java 8 构建反应式应用
使用 Akka 的 Actor 模型和领域驱动设计构建反应式系统