传统三层无状态架构的缺陷
三层架构包括表示层、业务逻辑层(中间层)、数据访问层(存储层)
传统的三层体系结构具有无状态前端、无状态的中间层和存储层,由于存储层的延迟和吞吐量限制,其可扩展性有限(存储层常常会成为系统的瓶颈),因此必须针对每个请求进行咨询。这就意味着整套系统也会因为存储层的限制而变得低效。通常的做法是在中间层与存储层中间加一层缓存逻辑出来,以提升系统性能,但是很快就会遇到存储层与缓存层的数据一致性问题,为了防止缓存项的并发更新导致不一致,应用程序或缓存管理器必须实现并发控制协议。这无疑为开发人员和运维人员增加了额外的工作量。
Actor模型和CSP模型的出现,解决了传统三层架构的缺陷。
Actor模型
Actor模型简介
actor模型最早被1973年的“A Universal Modular ACTOR Formalism for Artificial Intelligence”引入,作为人工智能研究中的一种计算方法。它的最初目标是用一种能安全地跨工作站并发分布的通信方式来建模并行计算。这篇文章几乎没有假设实现细节,而是定义了一种高级消息传递通信模型。actor模型的四个主要变体:经典actor模型、基于进程的actor模型、通信事件循环模型、以及活动对象模型。
有些健壮的工业级actor系统正被用于赋能大规模可伸缩分布式系统,主要案例:例如Akka被用于服务PayPal的十亿级的事务,Erlang被用于为WhatsApp的上亿用户发送消息,而Orleans被用于服务Halo4的数百万玩家。
Actor模型作为一种用于处理并发计算的数学概念模型,它定义了系统组件该如何表现,以及如何与其他组件交互的一些通用规则。它将Actor对象用作并发计算的基本单元,它也是一种重要的软件设计思想,它在架构、设计、实现以及组件之间的消息传递方面有着非常好的应用,也更好的发挥了多核计算机的潜力。该思想与我们经常用到的面向对象语言的思想很类似:一个对象接受消息(方法调用)然后依据消息数据进行计算。主要的不同点在于,actor之间完全独立,没有共享内存。值得注意的是,一个actor维持的私有状态从来不会被其他actor改变,而只会通过接受消息,自己改变自己。通过创建新的Actor对象,可以在计算操作的生命周期中以抽象方式提高系统的分布性。
Actor模型允许建立一个有状态的中间层,其内存级的读写性能和特定于相关领域的业务实体行为,确保了系统的高性能以及数据的一致性。Actor模型天然的拥有着面向对象的程序设计功能,在实践中我们应该把主要精力放到组件之间的消息传递,而不是对象的属性和内部行为。
一个actor并不能成为actor模型。actor是系统的组件,actor模型认为系统所有的组件都是actor,都有地址,actor通过这些地址进行异步通信。
经典actor模型
经典actor模型,保持了在隔离的计算单元和状态单元之间通过消息来做异步通信的思想,主要特点如下:
1 | 异步通信:使消息就像从一个Actor对象传输到了另一个Actor对象 |
当一个actor接收消息时,通常做以下三件事情:
1 | create:用一种行为的描述和一组参数(包括其它actor)来创建一个actor。 |
在经典actor模型中,状态变化均由become操作来聚合完成。每当actor处理一条消息时,它会计算出一个行为,来响应它期望处理的下一种消息类型。become操作的参数是一个有名字的continuation b,表示actor应该被更新为的行为,以及它应该传递给 b 的状态。(continuation:延续)
可以简单理解成,在一个actor维持私有状态之前,第三条基本上意味着定义了接收下条信息之前actor的状态。或者说,actor是如何改变自身状态的。
假设一个actor是一个计算器,初始状态为0。当这个actor接收到“+1”的消息后,它将指定接收下一个消息时,actor的状态为1,而不是改变原始的状态。
Orleans对Actor的应用
Actor平台(例如Erlang和Akka)在简化分布式系统编程方面向前迈了一步。但是,由于提供的抽象和系统服务的水平相对较低,它们仍然使开发人员承担着许多分布式系统的复杂性。主要包括开发用于管理Actor的生命周期,处理分布式簇,处理Actor的失败和恢复,放置Actor以及由此产生的管理分布式资源的应用程序代码。要为应用程序中的这些问题构建正确的解决方案,这就开发人员的要求就非常高了,必须是分布式系统专家级别的。
为了减少这些问题的发生,Orleans框架引入了虚拟Actor的新型抽象,它解决了许多复杂的分布式系统问题,例如可靠性和分布式资源管理,从而使开发人员摆脱了那些麻烦。同时,Orleans运行时使应用程序能够获得高性能,可靠性和可伸缩性。
Orleans对Actor的实现特点:
- 1,Orleans Actor无处不在:无法明确创建或销毁它。它的生命周期超越了其任何内存对象的生命周期,因此也超越了任何特定服务器的生命周期。
- 2,Orleans Actor会自动实例化:如果没有Actor的内存实例,则发送给Actor的消息会促使在可用服务器上创建一个新实例。作为运行时资源管理的一部分,将自动回收未使用的Actor实例。
- 3,Actor永远不会失败:如果服务器崩溃了,下一条发送给运行在故障服务器上的Actor的消息将会促使Orleans自动在另一台服务器上重新实例化该Actor ,从而无需应用程序来监督和显式重新创建已经挂掉的Actor。
- 4,Actor实例的位置对于应用程序代码是透明的,从而大大简化了编程。
- 5,Orleans可以自动创建同一个无状态Actor的多个实例,从而无缝扩展热门Actor。
虚拟Actor的引入,相当于为开发者提供了一个虚拟的内存空间,使开发人员可以调用系统中的任何角色,无论它是否存在于内存中。虚拟化依赖于从虚拟角色映射到当前运行的物理实例的间接寻址。运行时通过一个分布式目录支持间接寻址,该目录将Actor标识映射到其当前物理位置。Orleans通过使用该映射的本地缓存来最小化间接寻址的运行时开销。这个策略被证明是非常有效的。在微软的生产服务中,缓存命中率通常远远超过90%。
CSP模型
CSP是 Communicating Sequential Processes(通信顺序进程) 的简称。在CSP中,多了一个角色Channel,过程(比如goroutine,Worker1)与过程(Worker2)之间不直接通信,而是通过Channle进行通信。
1 | Worker1 --> Channel --> Worker2 |
CSP模型特点:
- Channel是过程的中间媒介,Worker1想要跟Worker2发信息时,直接把信息放到Channel里(在程序中其实就是一块内存),然后Worker2在方便的时候到Channel里获取。
- Worker1和Worker2之间可以存在很多个Channel;在Golang中每个Channel定义不同的数据类型,即发送不同类型的消息的时候会用到多个不同的Channel。
Go语言的CSP模型是由协程Goroutine与通道Channel实现:
- Go协程goroutine: 是一种轻量线程,它不是操作系统的线程,而是将一个操作系统线程分段使用,通过调度器实现协作式调度。是一种绿色线程,微线程,它与Coroutine协程也有区别,能够在发现堵塞后启动新的微线程。
- 通道channel: 类似Unix的Pipe,用于协程之间通讯和同步。协程之间虽然解耦,但是它们和Channel有着耦合。
Actor模型和CSP模型比较
Actor之间直接通讯,而CSP是通过Channel通讯,在耦合度上两者是有区别的,后者更加松耦合。
同时,它们都是描述独立的流程通过消息传递进行通信。主要的区别在于:在CSP消息交换是同步的(即两个流程的执行”接触点”的,在此他们交换消息),而Actor模型是完全解耦的,可以在任意的时间将消息发送给任何未经证实的接受者。由于Actor享有更大的相互独立,因为他可以根据自己的状态选择处理哪个传入消息。自主性更大些。
在Go语言中为了不堵塞流程,程序员必须检查不同的传入消息,以便预见确保正确的顺序。CSP好处是Channel不需要缓冲消息,而Actor理论上需要一个无限大小的邮箱作为消息缓冲。
参考: