文章

Akka 入门-Actor

Akka 入门-Actor

模块

AKKA 是调度模块化的,它由许多拥有不同特性的 JAR 组成。

  • akka-actor – 经典角色、类型角色、IO 角色等。
  • akka-agent – 代理、整合了 Scala 的 STM 特性
  • akka-camel – 整合 Apache 的 Camel
  • akka-cluster – 集群成员管理、弹性路由
  • akka-kernel – AKKA 微内核,运行着一个极简应用服务器
  • akka-osgi – 在 OSG 容器里使用 AKKA 的基本 bundle,包括 akka-actor 的类
  • akka-osgi-aries – Aries——服务提供角色系统的蓝图
  • akka-remote – 远程角色
  • akka-slf4j – SLF4J Logger (事件总线监听器)
  • akka-testkit – 测试角色系统的工具包 Toolkit for testing Actor systems
  • akka-zeromq – 整合 ZeroM

Actor

  • 角色(Actor)

    • 并发与并行性的高等级抽象
    • 异步,无锁以及高性能的事件驱动编程模型
    • 非常轻量级的事件驱动流程
  • 容错
    • 拥有“让它崩溃”语义的管理层级
    • 管理层级可以跨越多个 JVM,实现真正的容错系统
    • 非常适合编写可自我修复且永不停机的高容错能力的系统
  • 位置透明
    • akka 旨在分布式环境中工作:角色之间都是用纯消息交互,并且一切都是异步的

Actor 优点

  • 事件驱动模型:Event-driven model,Actor 通过响应消息来执行工作。Actor 之间的通信是异步的,允许 Actor 发送消息并继续自己的工作,而不是阻塞等待响应
  • 强隔离原则:Strong isolation principles,与 Java 中的常规对象不同,Actor 在调用的方法方面,没有一个公共 API。相反,它的公共 API 是通过 Actor 处理的消息来定义的。这可以防止 Actor 之间共享状态;观察另一个 Actor 状态的唯一方法是向其发送请求状态的消息。
  • 位置透明:Location transparency,系统通过工厂方法构造 Actor 并返回对实例的引用。因为位置无关紧要,所以 Actor 实例可以启动、停止、移动和重新启动,以向上和向下扩展以及从意外故障中恢复
  • 轻量级:Lightweight,每个实例只消耗几百个字节,这实际上允许数百万并发 Actor 存在于一个应用程序中。

Actor 生命周期

actor 被创建后存在,并且用户请求关闭消失。当 actor 被关闭后,其所有的子 actor 都将被递归地关闭。这个特性极大简化了我们的资源清理工作,并且防止资源泄露。实际上,在进行底层多线程编程时,我们经常会小看各种对并发资源生命周期管理的难度。 Context.stop(self)自我关闭,停止其他 Context.stop(actorRef) 通过这种方式停止一个 actor 是坏习惯,但是你可以给这个 actor 发送 PosionPill 或者自定义关闭消息来关闭它。

Akka 的 actor 提供了很对生命周期 API,你可以在实现 actor 时重载这些方法。最常用的是 preStart()和 postStop()。 preStart()会在 actor 启动后,并在它处理第一个消息之前被调用 postStop()会在 actor 将要被关闭前被调用,在它之后,actor 不会再处理任何消息了

Actor 层次结构

通过调用 context.actorOf 创建一个 actor。这种方式向现有的 actor 树内加入了一个新的 actor,这个 actor 创建者 就成为了这个 actor 的父 actor。 所有的 actor 都有一个共同的家长 成为 user guardian 可以通过调用 system.actorOf()来创建属于它新的 actor 实例。创建 actor 将返回一个有效的 URL 引用,因此,如果我们通过调用 system.actorOf(…,”someActor”) 创建一个名为 someActor 时,其引用将包路径/user/someActor

图

Actor 失败处理

父 actor 和子 actor 在整个声明周期内都保持着联系。当一个 actor 失败了(抛出异常或者在 receive 里冒出一个未处理的异常),他会被暂时地挂起。就像之前提到的一样,失败信息会被传递到父 actor 中,然后由父 actor 来决定如何处理这个子 actor 产生的异常。在这种方式下,父 actor 就是子 actor 的监管者,默认的监管策略就是停止并且重启子 actor。如果你没有修改默认的监管策略,那么所有的失败都会导致重启 actor。 到在 actor 失败后,被监管的 actor 被立即停止并重启,我们也看到了一条这个异常被处理的日志。在测试中,我们使用 preStart()和 postStop()钩子,这些钩子可以在 actor 被重启前后被调用,所以我们不能用它来区分 actor 是我吃一次启动还是被重启。重启 actor 在大多数情况是正确的,重启可以让 actor 恢复到一个已知的正确状态上,也就是启动时的干净状态。在内部真正发生的是:preRestart()postRestart() 方法被调用,如果它们没有被重载,则它们分别会调用 postStop()和 preStart()你可以尝试重载这些方法,看看输出的改变

Actor 处理子角色故障

角色最后一个功能是处理子角色的故障。故障处理对于用户来说是透明的,对于发生的故障 Akka 都会自动的通过对应策略处理(监管和监测章节介绍)。 因为这些策略是构成角色系统的基础,所以一旦角色被创建那么这些故障处理策略将不可改变。 由于每个角色都只有一类故障处理策略,这也就意味着不同的子角色将会应用不同的策略。角色根据子角色所使用的故障处理策略将它们分组。根据任务被切分的子任务性质系统会不断的重复这个分组过程。一旦角色终止,如失败后无法重启,自己停止或者被监督者停止,那么它将释放它占有的所有资源,并将它信箱中没处理的消息发送到“死信件信箱”里,然后“死信件信箱”将会把他们以 DeadLetters 的形式传递给 EventStream. Actor 引用中的信箱将会被一个系统信箱所取代,然后将所有消息以 DeadLetters 的形式转发给 EventStream。虽然系统会尽最大努力来实现该消息传递,但是我们不能依赖它来实现“有保证的传递”。 为什么不选择悄悄的将所有消息倾倒出来,是基于我们下面的测试结果考虑:我们在事件总线(BUS)上注册了测试事件监听器(TestEventListener)。这里事件总线即死信件被发送到的地方。该监听器将会对收到的每条死信件记录一条警告日志——这个可以帮助我们更快的检测失败。所以我们有理由相信这个特征还将可以应用于其他目的。

本文由作者按照 CC BY 4.0 进行授权