一个actorSystem 是一个重量级的结构。它会分配N个线程。所以对于每一个应用来说只用创建一个ActorSystem。
Actor是种可怜的“生物”,它们不能独自存活。Akka中的每一个Actor都是由一个Actor系统(Actor System)来创建和维护的。一个Actor系统会提供一整套辅助功能,
树形结构
actors以树形结构组织起来,类似一个生态系统。例如,一个actor可能会把自己的任务划分成更多更小的、利于管理的子任务。为了这个目的,它会开启自己的子actor,并负责监督这些子actor。关于监督的具体细节就不在这里讨论了。我们只要知道一点,就是每一个actor都会有一个监督者,那就是创建这些actor的那个actor。
如下图:
actor系统的精华就是任务的分解。任务要被分割的要足够小,每个分片都会被委托给子actor。这里用到的思想很明显就是“分而治之”,万变不离其宗啊!要想做好这一切,不仅仅要把任务组织的非常清晰(利于分解),也要对处理任务的最终actor在以下方面进行考虑:哪些消息需要处理、如何正常的回应、如何处理失败等等。如果一个处理特定任务的actor失败了,它会向它的监督者发出响应错误的消息去寻求错误处理的方法。这种递归式(监督者也有自己的监督者)的结构可以保证错误在合适的级别上进行处理。
你的应用程序是由几十个还是几百万个Actor来组成取决于你的用例,但是Akka完全有能力处理百万级别数量的Actor,你可能觉得创建这么多Actor一定是疯了,但实际上,Actor和线程之间并不是一一对应的关系,这一点非常重要,否则系统的内存很快就会被耗光的。由于Actor的非阻塞特性,一个线程可以为不同的Actor服务,Akka(译者注:具体地说是Dispatcher)会让线程在不同的Actor之间切换,依据是当前谁有消息需要被处理就分配线程给它。为了了解实际发生了什么。
消息的发送和处理完成是一个异步的非阻塞的过程。发送方不会一直被阻塞到接收方处理完消息,它会直接继续它自己的工作,发送方可能会期望从处理方那里得到一个反馈消息,但也许它根本不关心消息的处理结果。当某些组件发送一条消息给一个Actor时,真正发生的事情是:这个消息被投递给了这个Actor的Mailbox
, 我们基本上可以把Mailbox当成一个对列。把一条消息放到一个Actor的Mailbox的过程也是非阻塞的,比如:发送方不会一直等待消息进入接收方的Mailbox对列。
Dispatcher
会通知Actor它的Mailbox有一条新消息,如果这个Actor正在处理着手头上的消息,Dispatcher
会从当前的执行上下文里选一个可用的线程,一旦Actor处理完前面的消息,它会让Actor在这个准备好的线程上从Mailbox里取出这条消息去处理。Actor会阻塞分配给它的线程直到它开始处理一条消息,但这并不会阻塞消息的发送方,这意味着一个耗时较长的操作会影响整体的性能,因为所有其他的Actor不得不被安排从剩余线程中选取一个去处理消息(译者注:增加了线程调度的开销)。
因此,你的Receive偏函数要遵从一个核心的准则:尽可能地缩短它的执行时间(译者注:可以把任务分解为更小的单元委派更粒度更细的Actor去执行)。更重要的是,在你的消息处理代码里尽量地避免调用会造成阻塞的代码。当然,有些事情是你无法完全避免的,比如,当今主流的数据库驱动基本都是阻塞的,如果你想让你的程序基于Actor去持久化或查询数据时,你就会面临这样的问题。现在已经有一些应对这类问题的方案了,但是作为一偏介绍性的文章,本文先不涉及。
如何创建一个actor实例?
用new方法?这样不行!Akka会用一个ActorInitializationException
异常回敬你。事情是这样的:为了能让Actor良好地工作,你的Actor必须交给ActorSystem
和它的组件来托管。因此,你需要通过ActorSystem来帮你创建这个实例:
定义在AcotorSysytem上actorOf
方法期望一个Props
实例,针对新建的Actor的配置信息都会封装到这个Props
实例里,
注意:actorOf
返回的对象类型不是Barista
而是ActorRef
(译者注:Akka对Actor的托管体现在很多地方,前面提到的你不能直接创建一个Actor实例是一方面,此处,创建之后你得到的也不是Actor实例本身而是一个ActorRef
)。Actor从不会与其他Actor直接通信,因此没有必要获取一个Actor实例的直接引用,而是让Actor或其他组件获取那些需要发送消息的Actor的ActorRef
。
因此,ActorRef
扮演了Actor实例代理的角色,这样做会带来很多好处,因为一个ActorRef
可以被序列化,我们可以让它代理一个远程机器上的Actor,至于ActorRef
后面的这个Actor是本地JVM里的还是一个远程机器上的,这对使用者来说都是透明的,我们把这种特性称作“位置透明”。
请记住,ActorRef
不是类型参数化的,任何ActorRef
都可以互相交换,以便可以让你发送任意的消息给任意的ActorRef
。这种设计我们前面也提到了,它让你可以简单地修改你的Actor系统的网络拓扑而不需要对发送方做任何修改。
问询(Ask)机制:
有时候,给一个Actor发送消息然后期待返回一个响应消息这种模式并不适用于某些场景,最常见的例子是当某些组件并不是Actor但又需要和Actor交互时,它们就无法接收来自Actor的消息。对于这种情况,Akka有一种Ask
机制,它在基于Actor的并发和基于Future的并发之间提供一座桥梁