通常的编程实践不能够正确的解决现代系统的需求.幸运的是,我们不需要丢弃掉我们知道的所有事.反之,Actor模型用有原则的方法解决这些缺点,使我们系统更匹配我们想要的模型. Actor模型抽象允许我们通过通信的角度去思考我们的代码,这个和人们在大的组织出现交换没有什么不同.
使用Actor 我们可以:
- 在不使用锁的情况下使用封装
- 使用实体协作的模型对信号做出反应,改变状态并且发送信号给其他的Actor 来推进整个应用向前
- 不再担心执行机制不匹配我们以有的经验
通过使用消息避免锁和阻塞
和方法调用不同,actor之间通过发送消息给彼此.发送消息不会把执行的线程从发送者传递到目的地.一个Actor可以发从一个消息并且无阻塞的继续工作.因此,可以在同样的时间内完成更多的工作.
对于对象,当一个方法返回,它将释放对于执行线程的控制.对于这点,actor也如同对象一样.当他们他们完成处理当前消息后,对消息作出反应并且返回执行.使用这个方法,actor实际上完成了我们想象中对象的执行:
没有返回值,是传递消息和方法调用的区别.通过传递消息,一个actor把工作委托给另一个actor.对于调用堆栈,如果期望一个返回值,发送的动作需要阻塞或者执行在同一个线程其他actor的工作.而actor模型则是通过返回消息来传递结果.
第二个我们需要的关键改变是在我们的模型中恢复封装(reinstate encapsulation).Actor对于消息的响应和对象调用方法类似,不同点是取代了多线程对于我们actor的侵入和破坏内部状态以及不可变性,Actor是从消息这发送独立执行的,他们按照顺序响应消息,一个时间处理一个消息.当每个actor按照发送给他们的顺序处理消息时,不同的actor之间可以并行得工作,这样一个Actor系统就可以在硬件提供的基础上尽可能多的处理消息.
因为一个消息总是被一个Actor处理,一个Actor的不可变性就能不在同步的情况下被保持,这些事在不使用锁的情况下自动发生的:
概述一个actor接受了消息时发生什么:
- actor把消息添加到队列尾部
- 如果一个actor没有为执行规划,它会被标记为准备去执行(ready to execute)
- 一个隐藏的调度者实体获取了Actor并且开始执行actor
- Actor从队列末端获取到消息
- Actor修改内部状态,发送消息给别的actor
- Actor变为未调度(unscheduled)
为了完成这些行为,actor有:
- 一个邮箱(一个消息从末端进入的队列)
- 一个行为(actor的状态,内部变量等)
- 消息(代表一个信号的数据块,如同方法调用和参数)
- 一个执行环境(这个机制使actor有消息可以响应并且调用他们消息处理的代码)
- 一个地址
消息发送给actor的邮箱.actor的行为描述了actor如果回应这些消息(比如发送更多的消息或者改变状态).一个执行环境编排了一个线程池去透明的驱动所有的动作完成.
这是个简单的模型并且解决了前面列举的问题:
封装被保留下来,通过信号的传递解耦了执行的程序(方法调用传递执行,消息传递没有).
不需要锁.修改一个actor的内部状态只能通过发送消息实现,同一时间处理一个当试图保持不变性的时候消息消除了竞争.
再也不用使用锁了,发送者也是非阻塞的.百万个Actor可以高效的被调度在十几个线程中,发挥了CPU的全部潜能.任务委托对于actor来说是一个常用的的操作模型.
actor的状态事本地的并且共享,改变数据是通过传递消息,这些消息映射到当代内存层次结构的实际工作方式.在许多方面,这意味着数据的传输仅仅只有包含数据的缓存行,在原始和心中保持状态和数据缓存.同样的模型可以映射到远程通信,状态保持在一个机器的RAM中,改变或者数据在网络上如同包一样被传播.
Actor优雅的处理异常情况
我们已经不通过在actors之间共享堆栈来调用而是彼此之间发送消息,我们需要使用不同的方式处理异常场景.又两种我们需要考虑的错误场景:
- 当委托的任务在目标actor犹如一个任务的错误失败了(比如校验失败).对于这个,目标的actor的服务是没问题的,只是任务自身出现问题.服务的actor应该给发送者发送一个消息,代表这个错误的.这里没有什么特别,错误是领域的一部分,因为它变成了一个普通的消息
- 当一个服务自身出现了內部错误.Akk强制所有的actor被组织在一个树状的层次中,比如,一个actor创建了另一个actor则它变成了新的Actor的父actor.这个和操作系统把进程组织成一个棵树很像.和进程一样,当一个actor失败了,他的父actor决定怎么响应这个错误.同样当父actor被停止了,他的所有子actor将递归的被停止.这个服务被叫做 监督(supervision),它是Akka的核心
Supervisor 一个监督策略当开启一个子actor时明确的被父actor定义.它可以决定在一些类型的错误上是重启还是停止子任务.子actor从来不会默默挂掉(除非进入了死循环),他们妖魔失败被监管者处理这些失败,或者他们被停止(这种情况下会通知相关部分).总会有个负责的实体来管理actor:它的父actor.重启对外是不可见的,合作的actor仍然可以在重启的过程中给Actor发送消息.