【Akka】Actor模型探索

Akka是什么

Akka就是为了改变编写高容错性和强可扩展性的并发程序而生的。通过使用Actor模型我们提升了抽象级别,为构建正确的可扩展并发应用提供了一个更好的平台。在容错性方面我们採取了“let it crash”(让它崩溃)模型,人们已经将这样的模型用在了电信行业,构建出“自愈合”的应用和永不停机的系统,取得了巨大成功。Actor还为透明的分布式系统以及真正的可扩展高容错应用的基础进行了抽象。

Akka是JVM(JAVA虚拟机,下同)平台上构建高并发、分布式和容错应用的工具包和执行时。

Akka用 Scala语言写成,同一时候提供了Scala和JAVA的开发接口。

Akka处理并发的方法基于Actor模型。在基于Actor的系统里。全部的事物都是Actor,就好像在面向对象设计里面全部的事物都是对象一样。可是有一个重要区别——那就是Actor模型是作为一个并发模型设计和架构的。而面向对象模式则不是。更详细一点,在Scala的Actor系统里,Actor互相交互并共享信息但并不正确交互顺序作出预设。Actor之间共享信息和发起任务的机制是消息传递。

Akka在多个Actor和以下的系统之间建立了一个层次(Layer),这样一来,Actor仅仅须要处理消息就能够了。创建和调度线程、接收和分发消息以及处理竞态条件和同步的全部复杂性,都托付给框架,框架的处理对应用来说是透明的。

Akka混合模型特点

  • Actors
    Actors为你提供:
    对并发/并行程序的简单的、高级别的抽象。
    异步、非堵塞、高性能的事件驱动编程模型(代码能够异步处理请求并採用独占的方式执行非堵塞操作)。
    很轻量的事件驱动处理(1G内存可容纳约270万个Actors)。
  • 容错性
    使用“let-it-crash”语义和监管者树形结构来实现容错。

    很适合编写永不停机、自愈合的高容错系统。监管者树形结构能够跨多个JVM来提供真正的高容错系统。

  • 位置透明性
    Akka的全部元素都为分布式环境而设计:全部Actor都仅通过发送消息进行互操作,全部操作都是异步的。
  • 可伸缩性
    在Akka里。不改动代码就添加节点是可能的,感谢消息传递和位置透明性(location transparency)。
  • 高弹性
    不论什么应用都会碰到错误并在某个时间点失败。Akka的“监管”(容错)策略为实现自愈系统提供了便利。

  • 响应式应用
    今天的高性能和高速响应应用须要对用户高速反馈,因此对于事件的响应须要很及时。Akka的非堵塞、基于消息的策略能够帮助达成这个目标。

  • 事务性Actors
    事务性Actor是Actor与STM(Software Transactional Memory)的组合。

    它使你能够使用自己主动重试和回滚来组合出原子消息流。

Actor系统

Actor本质上就是接收消息并採取行动处理消息的对象。

它从消息源中解耦出来,仅仅负责正确识别接收到的消息类型,并採取对应的行动。


Actor是封装状态和行为的对象,他们的唯一通讯方式是交换消息,交换的消息存放在接收方的邮箱里。从某种意义上来说。Actor是面向对象的最严格的形式。可是最后把它们看成一些人:在使用Actor来对解决方式建模时,把Actor想象成一群人,把子任务分配给他们,将他们的功能整理成一个有组织的结构,考虑怎样将失败逐级上传。这样的结果就能够在脑中形成进行软件实现的框架。

树形结构

程序中负责某一个功能的Actor可能须要把它的任务分拆成更小的、更易管理的部分。

为此它启动子Actor并监管它们。

每一个Actor有且仅有一个监管者。就是创建它的那个Actor。
Actor系统的精髓在于任务被分拆开来并进行托付,直到任务小到能够被完整地进行处理。这样做不仅使任务本身被清晰地划分出结构。而且终于的Actor也能依照它们“应该处理的消息类型”,“怎样完毕正常流程的处理”以及“失败流程应怎样处理”来进行解析。假设一个Actor对某种状况无法进行处理,它会发送对应的失败消息给它的监管者请求帮助。这样的递归结构使得失败能够在正确的层次进行处理。

能够将这与分层的设计方法进行比較。分层的设计方法终于很easy形成防护性编程,以防止不论什么失败被泄露出来。

把问题交由正确的人处理会是比将全部的事情“藏在深处”更好的解决方式。

如今,设计这样的系统的难度在于怎样决定谁应该监管什么。

这当然没有一个唯一的最佳方案,可是有一些可能会有帮助的原则:

  • 假设一个Actor管理还有一个Actor所做的工作。如分配一个子任务,那么父Actor应该监督子Actor。原因是父Actor知道可能会出现哪些失败情况,知道怎样处理它们。
  • 假设一个Actor携带着关键数据(i.e. 它的状态要尽可能地不被丢失),这个Actor应该将不论什么可能的危急子任务分配给它所监管的子Actor,并酌情处理子任务的失败。

    视请求的性质,可能最好是为每一个请求创建一个子Actor。这样能简化收集回应时的状态管理。这在Erlang中被称为“Error Kernel Pattern”。

  • 假设Actor A须要依赖Actor B才干完毕它的任务,A应该观測B的存活状态并对收到B的终止提醒消息进行响应。这与监管机制不同,因为观測方对监管机制没有影响,须要指出的是。仅仅是功能上的依赖并不足以用来决定是否在树形监管体系中加入子Actor.

配置容器

多个Actor协作的Actor系统是管理如日程计划服务、配置文件、日志等共享设施的自然单元。使用不同的配置的多个Actor系统能够在同一个jvm中共存。Akka自身没有全局共享的状态。

将这与Actor系统之间的透明通讯(在同一节点上或者跨网络连接的多个节点)结合,能够看到Actor系统本身能够被作为功能层次中的积木构件。

Actor实践

  1. Actor们应该被视为很友好的同事:高效地完毕他们的工作而不会无必要地打搅其他人,也不会争抢资源。

    转换到编程里这意味着以事件驱动的方式来处理事件并生成响应(或很多其他的请求)。Actor不应该因为某一个外部实体而堵塞(i.e.占领一个线程又被动等待),这个外部实体可能是一个锁、一个网络socket等等。

    堵塞操作应该在某些特殊的线程里完毕,这个线程发送消息给可处理这些消息的Actor们。

  2. 不要在Actor之间传递可变对象。为了保证这一点。尽量使用不变量消息。

    假设Actor将他们的可变状态暴露给外界,打破了封装,你又回到了普通的Java并发领域并遭遇全部其缺点。

  3. Actor是行为和状态的容器。接受这一点意味着不要在消息中传递行为(比如在消息中使用scala闭包)。有一个风险是意外地在Actor之间共享了可变状态,而与Actor模型的这样的冲突将破坏使Actor编程成为良好体验的全部属性。

Akka中的Actor模型

使用Actor就像租车——我们假设须要,能够高速便捷地租到一辆。假设车辆发生问题,也不须要自己修理,直接打电话给租车公司更换另外一辆就可以。
Actor模型是一种适用性很好的通用并发编程模型。它能够应用于共享内存架构和分布式内存架构,适合解决地理分布型的问题。

同一时候它还能提供很好的容错性。

一个Actor是一个容器,它包括了 状态,行为,一个邮箱,子Actor和一个监管策略。全部这些包括在一个Actor Reference里。

Actor引用

Actor是以Actor引用的形式展现给外界的。Actor引用能够被自由的无限制地传来传去。内部对象和外部对象的这样的划分使得全部想要的操作能够透明:重新启动Actor而不须要更新别处的引用,将实际Actor对象放置到远程主机上,向另外一个应用程序发送消息。但最重要的方面是从外界不可能到Actor对象的内部获取它的状态。除非这个Actor很不明智地将信息发布出去。

状态

Actor对象通常包括一些变量来反映Actor所处的可能状态。这可能是一个明白的状态机(e.g. 使用 FSM 模块),或是一个计数器。一组监听器。待处理的请求,等等。

这些数据使得Actor有价值,而且必须将这些数据保护起来不被其他的Actor所破坏。

好消息是在概念上每一个Akka Actor都有它自己的轻量线程。这个线程是全然与系统其他部分隔离的。

这意味着你不须要使用锁来进行资源同步。能够全然不必操心并发性地来编写你的Actor代码。

在幕后,Akka会在一组线程上执行一组Actor。一般是很多Actor共享一个线程,对某一个Actor的调用可能会在不同的线程上进行处理。

Akka保证这个实现细节不影响处理Actor状态的单线程性。

因为内部状态对于Actor的操作是至关重要的,所以状态不一致是致命的。

当Actor失败并由其监管者又一次启动,状态会进行又一次创建。就象第一次创建这个Actor一样。这是为了实现系统的“自愈合”。

行为

每次当一个消息被处理时。消息会与Actor的当前的行为进行匹配。行为是一个函数,它定义了处理当前消息所要採取的动作,比如假设客户已经授权过了。那么就对请求进行处理,否则拒绝请求。

这个行为可能随着时间而改变,比如因为不同的客户在不同的时间获得授权。或是因为Actor进入了“非服务”模式,之后又变回来。这些变化要么通过将它们放进从行为逻辑中读取的状态变量中实现,要么函数本身在执行时被替换出来。见become 和 unbecome操作。

可是Actor对象在创建时所定义的初始行为是特殊的。因为当Actor重新启动时会恢复这个初始行为。

邮箱

Actor的用途是处理消息,这些消息是从其他的Actor(或者从Actor系统外部)发送过来的。

连接发送者与接收者的纽带是Actor的邮箱:每一个Actor有且仅有一个邮箱,全部的发来的消息都在邮箱里排队。排队依照发送操作的时间顺序来进行,这意味着从不同的Actor发来的消息在执行时没有一个固定的顺序,这是因为Actor分布在不同的线程中。从还有一个角度讲。从同一个Actor发送多个消息到同样的Actor,则消息会按发送的顺序排队。

能够有不同的邮箱实现供选择,缺省的是FIFO:Actor处理消息的顺序与消息入队列的顺序一致。这一般是一个好的选择,可是应用可能须要对某些消息进行优先处理。在这样的情况下,能够使用优先邮箱来依据消息优先级将消息放在某个指定的位置。甚至可能是队列头。而不是队列末尾。

假设使用这样的队列,消息的处理顺序是由队列的算法决定的,而不是FIFO。

Akka与其他Actor模型实现的一个重要区别在于当前的行为必须处理下一个从队列中取出的消息,Akka不会去扫描邮箱来找到下一个匹配的消息。无法处理某个消息一般是作为失败情况进行处理,除非Actor覆盖了这个行为。

子Actor

每一个Actor都是一个潜在的监管者:假设它创建了子Actor来托付处理子任务。它会自己主动地监管它们。子Actor列表维护在Actor的上下文中。Actor能够訪问它。

对列表的更改是通过创建(tt class=”docutils literal”>context.ActorOf(…))或者停止(context.stop(child))子Actor来实现,而且这些更改会立马生效。实际的创建和停止操作在幕后以异步的方式完毕。这样它们就不会“堵塞”其监管者。

监管策略

Actor的最后一部分是它用来处理其子Actor错误状况的机制。错误处理是由Akka透明地进行处理的,将监管与监控中所描写叙述的策略中的一个应用于每一个出现的失败。

因为策略是Actor系统组织结构的基础,所以一旦Actor被创建了它就不能被改动。

考虑对每一个Actor仅仅有唯一的策略,这意味着假设一个Actor的子Actor们应用了不同的策略。这些子Actor应该依照同样的策略来进行分组,生成中间的监管者。又一次倾向于依据任务到子任务的划分来组织Actor系统的结构。

Actor终止

一旦一个Actor终止了,i.e. 失败了而且不能用重新启动来解决。停止它自己或者被它的监管者停止,它会释放它的资源,将它邮箱中全部未处理的消息放进系统的“死信邮箱”。而Actor引用中的邮箱将会被一个系统邮箱所替代,系统邮箱会将全部新的消息重定向到“排水沟”。 可是这些操作仅仅是尽力而为,所以不能依赖它来实现“保证投递”。

不是简单地把(未处理的:译者注)消息扔掉的想法来源于我们(Akka:译者注)測试:我们在事件总线上注冊了TestEventListener来接收死信,然后将每一个收到的死信在日志中生成一条警告。

这对于更快地解析測试失败很有帮助。

我们认为可能这个功能也能够用于其他的目的。

參考资料

让并发和容错更easy:Akka演示样例教程
Akka 2.0官方文档中文版

转载请注明作者Jason Ding及其出处
GitCafe博客主页(http://jasonding1354.gitcafe.io/)
Github博客主页(http://jasonding1354.github.io/)
CSDN博客(http://blog.csdn.net/jasonding1354)
简书主页(http://www.jianshu.com/users/2bd9b48f6ea8/latest_articles)
Google搜索jasonding1354进入我的博客主页

posted @ 2017-07-08 20:15  yfceshi  阅读(1623)  评论(0编辑  收藏  举报