(转)http://www.csdn.net/article/2014-12-17/2823174
在Akka里面,和Actor通信的唯一方式就是通过ActorRef
。ActorRef
代表Actor的一个引用,可以阻止其他对象直接访问或操作这个Actor的内部信息和状态。消息可以通过一个ActorRef
以下面的语法协议中的一种发送到一个Actor:
-!
(“告知”) —— 发送消息并立即返回
-?
(“请求”) —— 发送消息并返回一个Future对象,代表一个可能的应答
每个Actor都有一个收件箱,用来接收发送过来的消息。收件箱有多种实现方式可以选择,缺省的实现是先进先出(FIFO)队列。
在处理多条消息时,一个Actor包含多个实例变量来保持状态。Akka确保Actor的每个实例都运行在自己的轻量级线程里,并保证每次只处理一条消息。这样一来,开发者不必担心同步或竞态条件,而每个Actor的状态都可以被可靠地保持。
Akka的Actor API中提供了每个Actor执行任务所需要的有用信息:
sender
:当前处理消息的发送者的一个ActorRef
引用context
:Actor运行上下文相关的信息和方法(例如,包括实例化一个新Actor的方法ActorOf
)supervisionStrategy
:定义用来从错误中恢复的策略self
:Actor本身的ActorRef
引用
再次需要注意的是,在Akka里,Actor之间通信的唯一机制就是消息传递。消息是Actor之间唯一共享的东西,而且因为多个Actor可能会并发访问同样的消息,所以为了避免竞态条件和不可预期的行为,消息的不可变性非常重要。
因为Case Class默认是不可变的并且可以和模式匹配无缝集成,所以用Case Class的形式来传递消息是很常见的。(Scala中的Case Class就是正常的类,唯一不同的是通过模式匹配提供了可以递归分解的机制)。
Akka的容错和监管者策略
在Actor系统里,每个Actor都是其子孙的监管者。如果Actor处理消息时失败,它就会暂停自己及其子孙并发送一个消息给它的监管者,通常是以异常的形式。
在Akka里面,监管者策略是定义你的系统容错行为的主要并且直接的机制。
在Akka里面,一个监管者对于从子孙传递上来的异常的响应和处理方式称作监管者策略。 监管者策略是定义你的系统容错行为的主要并且直接的机制。
当一条消息指示有一个错误到达了一个监管者,它会采取如下行动之一:
- 恢复孩子(及其子孙),保持内部状态。 当孩子的状态没有被错误破坏,还可以继续正常工作的时候,可以使用这种策略。
- 重启孩子(及其子孙),清除内部状态。 这种策略应用的场景和第一种正好相反。如果孩子的状态已经被错误破坏,在它可以被用到Future之前有必须要重置其内部状态。
- 永久地停掉孩子(及其子孙)。 这种策略可以用在下面的场景中:错误条件不能被修正,但是并不影响后面执行的操作,这些操作可以在失败的孩子不存在的情况下完成。
- 停掉自己并向上传播错误。 适用场景:当监管者不知道如何处理错误,就把错误传递给自己的监管者。
而且,一个Actor可以决定是否把行动应用在失败的子孙上抑或是应用到它的兄弟上。有两种预定义的策略:
OneForOneStrategy
:只把指定行动应用到失败的孩子上AllForOneStrategy
:把指定行动应用到所有子孙上
如果没有指定策略,那么就使用如下默认的策略:
- 如果在初始化Actor时出错,或者Actor被结束(Killed),那么Actor就会停止(Stopped)
- 如果有任何类型的异常出现,Actor就会重启
Akka也考虑到对 定制化监管者策略的实现,但正如Akka文档也提出了警告,这么做要小心,因为错误的实现会产生诸如Actor系统被阻塞的问题(也就是说,其中的多个Actor被永久挂起了)。
本地透明性
Akka架构支持 本地透明性,使得Actor完全不知道他们接受的消息是从哪里发出来的。消息的发送者可能驻留在同一个JVM,也有可能是存在于其他的JVM(或者运行在同一个节点,或者运行在不同的节点)。Akka处理这些情况对于Actor(也即对于开发者)来说是完全透明的。唯一需要说明的是跨越节点的消息必须要被序列化。
Actor系统设计的初衷,就是不需要任何专门的代码就可以运行在分布式环境中。Akka只需要一个配置文件(Application.Conf),用以说明发送消息到哪些节点。下面是配置文件的一个例子:
- akka {
- actor {
- provider = "akka.remote.RemoteActorRefProvider"
- }
- remote {
- transport = "akka.remote.netty.NettyRemoteTransport"
- netty {
- hostname = "127.0.0.1"
- port = 2552
- }
- }
- }
最后的一些提示
我们已经了解了Akka框架帮助完成并发和高性能的方法。然而,正如这篇教程指出的,为了充分发挥Akka的能力,在设计和实现系统时,有些要点值得考虑:
- 我们应尽最大可能为每个Actor都分配最小的任务(如上面讨论的,遵守单一职责原则)
- Actor应该异步处理事件(也就是处理消息),不应该阻塞,否则就会发生上下文切换,影响性能。具体来说,最好是在一个Future对象里执行阻塞操作(例如IO),这样就不会阻塞Actor,如:
- case evt => blockingCall() // BAD
- case evt => Future {
- blockingCall() // GOOD
- }
- 要确认你的消息都是不可变的,因为互相传递消息的Actor都在它们自己的线程里并发运行。可变的消息很有可能导致不可预期的行为。
- 由于在节点之间发送的消息必须是可序列化的,所以必须要记住消息体越大,序列化、发送和反序列化所花费的时间就越多,这也会降低性能。