什么是 Actor?
在「Actor 系统」这一节中,我们介绍了 Actor 是如何形成层次结构的,以及其是构建应用程序的最小单元。本节将单独地研究 Actor,解释在实现它时遇到的概念。有关所有细节的更深入探讨,请参阅「Actors」。
Actor 是状态、行为、邮箱、子 Actor 和监督者策略(Supervisor Strategy
)的容器。所有这些都封装在一个 Actor 的引用之后。一个值得注意的方面是,Actor 有一个明确的生命周期,当不再被引用时它们不会被自动销毁;在创建了一个生命周期之后,你有责任确保它最终会被终止,这也让你能够控制当 Actor 终止时如何释放资源。
Actor 引用
如下面详细介绍的,为了从 Actor 模型中获益,需要将 Actor 对象从外部屏蔽。因此,使用 Actor 引用将 Actor 表示为外部对象,这些引用是可以自由地传递且不受限制的对象。这种分为内部对象和外部对象的方法可以实现所有所需操作的透明性:在不需要更新其他地方引用的情况下重新启动 Actor,将实际的 Actor 对象放在远程主机上,在完全不同的应用程序中向 Actor 发送消息。但最重要的一点是,除非 Actor 不明智地发布了这些信息,否则不可能从外部观察 Actor 的内部并掌握其状态。
状态
Actor 对象通常包含一些反映 Actor 可能处于的状态的变量。这可以是一个显式状态机(例如,使用「FSM」模块),也可以是一个计数器、一组监听器、挂起的请求等。这些数据使 Actor 有价值,并且必须防止其他 Actor 损坏它们。好消息是,从概念上讲,Akka 的每个 Actor 都有自己的轻量级线程,这完全与系统的其他部分隔离开来。这意味着,不必使用锁来同步访问,你可以编写 Actor 代码,而不必担心并发性。
在幕后,Akka 将在一组真正的线程上运行一组 Actor,在这些线程中,通常许多 Actor 共享一个线程,随后对一个 Actor 的调用可能最终在不同的线程上进行处理。Akka 确保这个实现细节不会影响处理 Actor 的状态。
因为内部状态对 Actor 的操作至关重要,所以状态不一致是致命的。因此,当 Actor 失败并由其监督者重新启动时,将从头开始创建状态,就像第一次创建 Actor 时一样。这是为了使系统能够自我修复。
或者,可以通过持久化接收到的消息并在重新启动后重播(请参见「持久化」),将 Actor 的状态自动恢复到重新启动前的状态。
行为
每次处理消息时,它都与 Actor 的当前行为相匹配。行为(Behavior
)指的是一个函数,它定义了在该时间点对消息做出反应时要采取的操作,例如,如果客户端被授权,就转发一个请求,否则就拒绝它。这种行为可能会随着时间的推移而改变,例如,由于不同的客户端随着时间的推移而获得授权,或者因为 Actor 可能会进入“停止服务”模式,然后返回。这些更改是通过从行为逻辑中读取的状态变量中对它们进行编码来实现的,或者函数本身可以在运行时交换出来,请参阅become
和unbecome
操作。但是,在构造 Actor 对象期间定义的初始行为是特殊的,因为重新启动 Actor 会将其行为重置为初始行为。
邮箱
Actor 的目的是处理消息,这些消息是从其他 Actor(或从 Actor 系统外部)发送给 Actor 的。连接发送方和接收方的部分是 Actor 的邮箱:每个 Actor 只有一个邮箱,所有发送方都将其消息排队。排队是按发送操作的时间顺序发生的,这意味着由于在线程间分发 Actor 的明显随机性,不同 Actor 发送的消息在运行时可能没有定义顺序。另一方面,从同一个 Actor 向同一个目标发送多条消息将以相同的顺序将它们排队。
有不同的邮箱实现可供选择,默认为FIFO
:Actor 处理的消息的顺序与它们排队的顺序匹配。这通常是一个很好的默认值,但是应用程序可能需要将某些消息优先于其他消息。在这种情况下,优先级邮箱将不总是在末尾排队,而是在消息优先级指定的位置排队,甚至可能在前面。当使用这样的队列时,处理的消息的顺序将自然地由队列的算法定义,通常不是FIFO
。
Akka 与其他一些 Actor 模型实现不同的一个重要特性是,当前行为必须始终处理下一条出列的消息,没有扫描邮箱以查找下一条匹配的消息。除非重写此行为,否则处理消息失败通常被视为失败。
子 Actor
每个 Actor 都可能是一个监督者:如果它为分配子任务创建子 Actor,它将自动对它们进行监督。子列表在 Actor 的上下文中维护,并且 Actor 可以访问它。对列表的修改是通过创建(context.actorOf(...))
或停止(context.stop(child))
子项来完成的,这些操作会立即反映出来。实际的创建和终止操作以异步方式在后台发生,因此它们不会“阻塞”其监督者。
监督者策略
Actor 的最后一个部分是其处理子 Actor 错误的策略。对于「监督和监控」中描述的每一个传入策略,Akka 将透明地进行故障处理。由于该策略是如何构建 Actor 系统的基础,因此一旦创建了 Actor,就不能更改它。
考虑到每个 Actor 只有一个这样的策略,这意味着如果不同的策略应用于一个 Actor 的不同子代,那么这些子代应该按照匹配的策略分组到中级监督者之下,然后根据任务拆分为子任务,再一次组织 Actor 系统。
当 Actor 终止
一旦一个 Actor 终止,即以一种不被重启处理的方式失败、自行停止或被其监督者停止,它将释放其资源,将其邮箱中的所有剩余邮件排入系统的“死信邮箱”,该邮箱将它们作为死信(DeadLetters
)转发到事件流(EventStream
)。然后在 Actor 引用中用系统邮箱替换原 Actor 的邮箱,将所有新消息作为死信重定向到事件流。但是,这是在尽最大努力的基础上完成的,因此不要依赖它来构建“有保证的消息传递”。
我们的测试启发了我们不只是静默地转储消息的原因:我们在发送死信的事件总线上注册TestEventListener
,它将记录收到的每个死信的警告,这对于更快地破译测试失败非常有帮助。可以想象,此功能也可以用于其他目的。
英文原文链接:What is an Actor?.