引言
在分布式系统中,由于集群规模庞大,系统环境复杂,出错是常态,容错是一个非常重要的内容。
传统的异常处理,通常是捕获某种类型的异常并调用相应的异常处理函数,但是在分布式系统中,由于环境的复杂性,通常无法预料错误的类型并进行有效的处理,传统的异常处理方式并不适用。
Akka沿袭了Erlang的let it crash哲学,当actor内部发生异常时,并不试图捕捉异常并处理,而是重建一个新的actor, 使得整个系统在错误发生的时候可以自动恢复。
监护策略
在Akka中,在子actor被创建后,父actor就成为了子actor的监护这,在子actor出错时负责处理, 有一对一和一对多两种策略:
- OneForOneStrategy
- 只诛首恶,余者不问。只有出错的
actor会被处理。Akka默认采用这种机制。
- 只诛首恶,余者不问。只有出错的
- OneForAllStrategy
- 城门失火,殃及池鱼。当出错时,不仅出错的
actor,其兄弟actor也采用同样的策略一并处理。
- 城门失火,殃及池鱼。当出错时,不仅出错的
恢复策略
actor出错时具体采用何种策略呢?共有四种:
- Stop - 停止出错的
actor,不再让它处理任何消息。 - Restart - 这是默认策略,杀死旧的
actor,重新创建一个新的actor。 - Resume - 忽略本次错误,恢复
actor对消息的处理。 - Escalate - 交给父
actor来决定处理策略。
代码示例
回到我们的红包程序,这里设计了一个采用Resume策略的监护者,对于比较重要、不能重启的actor, 通常可以采用resume策略进行错误恢复。
1 | /** |
Error Kernel Pattern
整个Akka系统,被设计成一个树形结构,底层的子Actor由其上一层的父Actor创建并管理。

Error kernel pattern的同样来源于Erlang,其核心思想在于,处于上层的Actor承担风险较小的任务,出错之后尽量采用resume的方式来恢复,处于底层的Actor承担更加危险的任务,出错之后通常采用restart的方法来恢复。
由于不同层级的actor在整个系统中的地位不同,在设计actor系统的时候,倾向于将重要的数据放在上层的actor,而将具体要执行的操作下放到下层的actor。
Death Watch
对于那些不存在父子关系的actor, 一个actor如果想要获得另一个actor生命周期相关的信息,可以使用death watch, 在被关注的actor挂掉的时候收到Terminated消息并采取相应动作。
1 | import akka.actor.{ Actor, Props, Terminated } |
death watch一个典型的应用是关闭ActorSystem。
ActorSystem应该在什么时候被关闭呢?一个自然的答案 —— 在actor处理完所有的消息之后。如何知道actor已经处理完所有的消息呢?由于actor消息处理是异步的,这是个很难回答的问题。
一个可能的方案是,估算一下系统处理消息所需的时间,设置一个超时,到达时限后关闭ActorSystem,但这只是一个粗略的实现。
如果需要更加精确的方案,可以考虑使用death watch, 参考akka shutdown patterns, 核心思想是设计一个单独的actor来关注所有其他的actor,当这些actor都发出Teminated消息之后,关闭整个系统。
总结
Akka的错误恢复机制,遵循了Let it crash哲学,体现了优雅的简洁性,使得构建稳定健壮的分布式系统成为可能。