Akka应用模式:分布式应用程序设计实践指南(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-11 12:58:33

点击下载

作者:虞航仲

出版社:电子工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

Akka应用模式:分布式应用程序设计实践指南

Akka应用模式:分布式应用程序设计实践指南试读:

前言

响应式应用开发是软件开发的新前沿。随着联网设备的普及,数据量也在增加。以前的单线程批处理数据的旧技术根本无法满足这个新领域所提出的需求。大数据的概念已经兴起,我们需要新的工具和新的技术来应对它。

通常,解决现有问题的灵感并不是来自现有的技术,而是来自过去的经验。许多现今用于处理大数据的新工具实际上是基于旧的actor的概念而产生的。actor是构建Akka的关键概念,但其根源追溯起来应该属于过去。actor不是一个新概念,相反,它是一个被重新关注的旧概念。

当开始探索Akka、actor、streams和其他与之相关的技术时,我们将从现实世界的角度来看待它们:如何在一系列项目中安排一组人,同时优化他们的可用时间及技能?这是一个复杂的问题,并不是只用一个下午就可以解决的。但这又是一个有趣的问题,为深度探索提供了很大的空间。这也是大多数软件开发人员在职业生涯中的某个时刻一定会遇到的问题。在对Akka进行探索的过程中,我们将回顾这个问题。

在解决问题之前,我们必须先了解可用的工具,还需要了解这些工具为什么存在,以及它们可以解决什么样的问题。我们需要知道Akka的起源及其在Actor模型中的根源。我们需要一套指导原则将应用程序拼接在一起,这套原则会在探索域驱动设计(D D D)的过程中被发现。有了这些基础,便可以开始使用Akka提供的所有工具来构建域了。我们可以探索简单的actor的使用方法以及它如何与流关联,可以让系统分布在多个节点上,使其具有更好的容错性、可用性及可扩展性。

首先,我们需要知道这一切的根源在哪里。本书使用的排版约定

本书使用如下排版约定:

斜体(Italic)

标志着新词汇、URL、邮箱地址、文件名和文件扩展名。

等宽字体(Constant width)

用于程序清单(包括段落中的),表示程序元素,如变量名或函数名、数据库、数据类型、环境变量、语句和关键字。

等宽字体加粗(Constant width bold)

显示字面上被用户输入的命令或其他文本。

等宽字体且斜体(Constant width italic)

显示应该被用户提供的值或者由上下文决定的值所替换的文本。

该图标表示技巧或建议。

该图标表示一般注释。

该图标表示警告或者注意事项。O'Reilly Safari

Safari(以前的Safari Books Online)是企业、政府、教育者和个人的会员制培训及参考平台。

订阅者可以从一个完全可搜索的数据库中获得来自250多家出版商提供的成千上万的书籍、培训视频、互动教程,这些出版商包括O'Reilly Media、Harvard Business Review、Prentice Hall Professional、Addison-Wesley Professional、Microsoft Press、Sams、Que、Peachpit Press、Adobe、Focal Press、Cisco Press、John Wiley&Sons、Syngress、Morgan Kaufmann、IBM Redbooks、Packt、Adobe Press、FT Press、Apress、Manning、New Riders、McGraw-Hill、Jones&Bartlett、Course Technology。

若想获得更多资讯,请访问http://oreilly.com/safari。

如何联系我们

请将对本书的评价和发现的问题通过如下地址通知出版社。

美国:

O'Reilly Media,Inc.

1005 Gravenstein Highway North

Sebastopol,CA 95472

中国:

北京市西城区西直门南大街2号成铭大厦C座807室(100035)

奥莱利技术咨询(北京)有限公司

我们提供了本书网页,上面列出了勘误表、示例和其他信息。请通过http://bit.ly/applied-akka-patterns访问该页。

要给出本书意见或者询问技术问题,请发送邮件到bookquestions@oreilly.com。

更多有关书籍、课程、会议和新闻的信息,请见网站http://www.oreilly.com。

在Facebook找到我们:http://facebook.com/oreilly

在Twitter上关注我们:http://twitter.com/oreillymedia

在YouTube上观看:http://www.youtube.com/oreillymedia

致谢

这本书能够出版,要感谢许多人的帮助,包括Lightbend和社区里的Akka团队,本书的编辑Nan Barber以及评论家Konrad Malawski、Sean Glover、Petro Verkhogliad。特别要感谢我们的家人给予的宽容和支持。

读者服务

轻松注册成为博文视点社区用户(www.broadview.com.cn),扫码直达本书页面。

· 提交勘误:您对书中内容的修改意见可在提交勘误处提交,若被采纳,将获赠博文视点社区积分(在您购买电子书时,积分可用来抵扣相应金额)。

· 交流互动:在页面下方读者评论处留下您的疑问或观点,与我们和其他读者一同学习交流。

页面入口:http://www.broadview.com.cn/32529第1章 Actor模型

理解如何正确使用actor是最基本的,这样才能使其发挥最大的作用,这也是我们将在本章中学到的知识。本章将会探讨actor——它是如何工作的,它们如何与彼此及外部世界进行交互。

设计软件时用到的很多技术都教导我们,在编写代码之前先去了解现实世界是很有必要的。我们必须了解将要开发的软件用例,知道谁将使用它,如何使用它,这些信息对设计优秀的软件系统而言至关重要。还有一个非常重要的问题在设计过程中经常被忽略:系统执行需要耗费多长时间?任何一个开发过高并发系统的工程师都知道,时间是整个软件开发过程中的重要组成部分。

下面先探讨一些看似与软件开发并不密切相关的话题,不用担心,稍后会向大家说明这些是如何与软件开发产生联系的!现实是最终一致的

思考一下平时去拿一杯咖啡的过程,表面上看,这是一个很简单的过程:伸手,然后拿到一杯咖啡。这个过程并没有包含太多动作,但是,更深入地去思考这个过程会发现许多奥秘。

为了能拿到这杯咖啡,首先需要知道它在哪里。环顾四周然后会看到它,但是此时看到的这杯咖啡是它现在的状态吗?可能已经是它曾经的状态了?因为我们是基于这个杯子反射回来的光来判断它的位置的,但是光是需要时间传播的。而且,当眼睛接收到这些数据之后,也需要时间先来处理数据然后再传送到大脑。另外,神经系统的其他部分还需要进行额外的一系列处理,才能让我们移动手臂,最后拿起这杯咖啡。

这个过程中的每个阶段都会产生一些时间延迟,而这些延迟最终都会影响到拿杯子这件事情本身。如果处于一个静止的环境中,这点延迟对于简单的拿杯子的动作而言不会有太大的影响,然而世界本质上是动态的,时刻都在发生变化,随着事件频繁发生而剧烈变化,这些小的延迟便会积累。不过对于前面那个简单的拿起一杯咖啡的例子来说,这点延迟还是相当微不足道的。那么,如果想要在杯子从桌子上掉下来的时候尝试去抓住它,同时又保证咖啡不洒出来,是不是就变得相当有挑战性了呢?如果想要同时抓住很多正在掉落的杯子呢?我们现在仿佛处在一个不可能完成任务的境界之中。

事实上,现实世界是受制于光速的。物理定律和光速为因果关系规定了上限。只有当两件事情占据相同的空间(当然这是不可能的),并且它们发生的时间有一定间隔时,先发生的事件才能对后发生的事件产生影响。当两个人在不同的距离点观察同一件事件时,其实他们会在不同的时间点经历这件事,距离近的人会稍早于距离远的人。然而,尽管经历的时间点不同,但都是真实地经历了。Actor模型就是基于这种现实物理世界的规律而设计的。

我们生活在处理一些过时信息的状态中。比如,细胞之间通过激素进行消息交流。再比如,我们日常聊天、看新闻、阅读博客,所有这些不同信息间的交流都是以异步的形式进行的。事实上,最后会发现生活中并没有事情是同步进行的。

即便是计算机,其行为也是异步的,计算机的每一步操作都是通过在某种介质上传播信号来完成的,信号可以是电信号、光信号或者其他信号。

既然世界上所有的事情都是以某种异步的形式在进行着,那么为什么还要花大力气去尝试编写同步的软件系统呢?我们也经常告诫自己,软件系统应该基于现实世界来建模和构造,但却忽略了时间这一基本概念。如果基于现实世界来建模,同时使用异步事件或消息机制来构造软件系统,岂不是更好吗?

反过来思考一下,如果通过传统的同步软件系统来模拟现实世界的话,这个世界会变成什么样子?最后的结果又会是什么?

回到拿一杯咖啡的例子。当大脑决定要去拿一杯咖啡的时候,首先需要暂停时间,至少是暂停我们所处环境的时间。停止周围的世界,这样才能保证在决定要拿一杯咖啡的那一刻到喝到咖啡的那一刻之间没有发生任何变化,没有任何事情影响到这个过程。如果快要拿到咖啡的时候,突然出现其他人抢先把这杯咖啡拿走了,那便会造成很混乱的局面。所以,只有冻结周边的一切,把状态彻底锁定,才能保证不会发生类似的混乱。任何想要拿这杯咖啡的人都会被“冻结”,直到我们成功拿到这杯咖啡为止。当然这并不意味只是冻结另外一个想拿这杯咖啡的人的状态,还包括空气、阳光以及这个杯子周边的一切。我们和杯子之间的所有影响因素都需要被冻结,否则就可能拿不到这杯咖啡,或者最后拿到的咖啡可能已经不是一开始看到的咖啡了。这听起来很复杂,比基于时间建模的系统要复杂得多。

这是Actor模型的基础之一——考虑时间因素,我们才能编写可以反映现实世界实际状态的软件系统,而不是去假设一个并不存在的时间被冻结的世界。解构Actor模型

1973年,Carl Hewitt、Peter Bishop和Richard Steiger一起开始帮忙解决论文Universal Modular Actor Formalism for Artificial Intelligence中提到的一些问题。虽然许多编程范例都是基于数学模型的,不过正如Hewitt所说,Actor模型的灵感是来自物理学的,它经历了多年的演变,但是基本概念却一直保持不变。

重要的是,在使用Akka时,可以在不使用Actor模型的情况下编写代码。而且事实上使用actor并不等同于使用Actor模型。有很多开发者在使用Akka多年后仍没有意识到Actor模型的存在。

使用Actor模型和简单使用actor之间的区别在于对待actor的方式。如果把actor当作顶级构建模块使用,所有系统程序代码都在actor系统内编写,这种情况下就是在使用Actor模型。另一方面,如果构建的系统里面有在非actor模块中的actor,即部分程序代码不是在actor系统内编写的,那么便不是在使用Actor模型。

还有一点也很重要,Actor 模型中的编程并不是一种工具或技术。整个编程语言都是围绕Actor模型的思想实现的,所以它更像是一种编程范式而不是工具集合。学习Actor模型编程就和学习面向对象编程或函数式编程一样,由于Actor模型是早于Akka的,所以除了Akka,还有很多其他版本的Actor模型的实现。

例如,Pony语言就是基于actor并且可以很容易地实现Actor模型的一种编程语言。另外,Erlang进程也相当于Akka的actor,它们在Erlang里面是非常基础的功能特征。再比如Ada编程语言,因为有Task的概念和名为“entries”的消息存在——消息由异步机制的Tasks控制来实现排队——这意味着Ada也是可以用来实现Actor模型的。

实现Actor模型需要遵循以下几个基本规则。

· 所有的计算都是在actor中执行的。

· actor之间只能通过消息进行通信交流。

· 为了响应消息,actor可以进行下列操作。

—更改状态或行为。

—发消息给其他actor。

—创建有限数量的子actor。

如果熟悉Akka,可以马上知道这些是如何在Akka里面实现的。但我们要先抛开Akka,直接探讨actor,这样才能更好地理解最基本的Actor模型和基于Akka实现的Actor模型之间的差异。所有的计算都在一个actor中执行

Carl Hewitt称actor为基本的计算单位[1]。基本的计算单位是什么意思呢?这意味着使用Actor模型构建系统时,一切都是actor。无论是计算斐波纳契序列还是维护系统中用户的状态,都可以在一个或多个actor中进行。

实现“一切都是actor”的想法并不是一件简单的事情。如果每次计算都需要在一个actor中进行,这意味着每个函数和每个状态变量都可以是actor。即使在技术上可行,也并不实用。通常,如果有一组相关的函数,我们会将这些函数都封装到一个actor中。这样做并不违反Actor模型,但是怎么划定那条用来判断是否违反Actor模型的边界线呢?后面学习领域驱动设计(DDD)时,将更详细地讨论这一点。在介绍DDD时引入的构建模块是非常适合转化成actor的,可以将其作为创建actor的指导原则。

Actor模型中的actor不仅有状态,还有行为,这听起来很像OOP的定义。其实两者是密切相关的,Alan Kay创造了“面向对象编程”这个术语,同时他也是Smalltalk语言的原始创造者之一,这很大程度上受到了Actor模型的影响。虽然Smalltalk和OOP的最终演化远离了Actor模型,但Actor模型的许多基本原则仍然在影响着现在的Smalltalk和OOP。事实上,OOP最初的核心并不是对象本身而是对象之间的消息流。

OOP将对象(类的实例)作为基本的计算单位,Java或类似语言的开发者会比较熟悉。另一方面,函数式编程则是围绕函数及其应用程序来进行计算机运算的,这一点在Lisp和Haskell语言中可以体现出来。

Actor模型中另一个对高并发应用有帮助的是隔离actor状态的思想。actor的状态永远不会直接暴露在外面,也无法被其他actor查看或修改,除非通过消息机制间接地进行。这种隔离机制同样适用于actor的行为。actor内部的方法和机制同样也不会直接暴露给其他actor。事实上,在actor内部,状态和行为可以被视为相同的因素(后面会更详细地介绍)。

模型中的actor可以有许多不同的形式。它们可以作为高技术性的结构存在,如数据库访问层;也可以作为与特定领域相关联的结构存在,如一个人或一个日程表,甚至可以进行简单的数学运算。系统中需要执行任意计算的任意结构都可以是一个actor。

大家将会在后面的章节中发现,actor是Actor模型以及基于Akka的应用程序中最基本的构建块。actor之间只能通过消息进行通信

我们以互相隔离的方式创造了actor,使它们永远不会暴露自己的状态或行为。这是Actor模型很重要的一个特性。但是因为用这种方式隔离了它们,因此需要去寻找其他可以与它们进行通信并了解系统状态的方法。

在Actor模型里面,所有的通信都是基于消息机制进行的,这也是actor之间进行通信的方法。

每个actor在创建时都会获得一个地址,该地址是与该actor进行通信的入口。不能通过这个地址直接访问该actor,但可以通过这个地址发消息给它。

Akka 区分了地址和引用的概念。大多数actor通信都是通过引用完成的。Akka actor也有地址可以在某些情况下使用,然而通常会避免这样使用。无论使用引用还是地址,基本原理都是一样的:通过一种方法找到actor的邮箱,以便向其发送消息。

发送给actor的消息是不可变的数据。这些消息会被发送到目标actor提供的地址并被存在邮箱里面。消息到达邮箱后的状态在发送方的控制范围之外。可以不按发送顺序成功投递消息,但投递也有可能失败。Actor模型提供了最多投递一次的消息投递机制,这意味着有可能发生投递失败的情况。如果想要确保每次投递都能成功,则需要使用其他工具来辅助。

最多投递一次是Actor模型和Akka提供的默认交付保证。然而,我们可以基于最多投递一次的机制来构造最少投递一次的机制,Akka中的某些机制可以实现这一点。

Akka进一步提供了更强大的顺序保证机制,可以确保正在通信的actor之间的消息都是按顺序被送达的。也就是说,一个actor发给另外一个actor多个消息的时候,可以确保消息被送达的顺序和发送的顺序是一样的。

消息被投递到邮箱后,actor可以接收并处理这些消息,但是每次只能接收处理一条消息。没有一个actor可以同时接收处理两条或两条以上的消息。这一点很关键,确保了我们可以以单线程的模式在actor中进行操作。actor可以很自由地修改自己的内部状态,而不用担心是否会有其他线程也在操作该状态。只要维持这样的单线程模式,状态就可以避免遇到并发问题。

消息的确切类型取决于actor的功能。对于不同领域的actor,经常会看到消息使用与领域相关的专业术语当作命令或事件,比如项目调度的例子里面会出现如“AddUser”或者“CreateProject”这样的命令,如果是一个更具技术感的actor,相关的消息也会更有技术范儿,如“Save”和“Compute”。

因为actor之间只通过消息机制进行通信,因此产生了一些有趣的可能。只要邮箱另一端的actor能处理这条消息,那么便可以用任何方法完成这个工作(见图1-1)。这意味着接收端的actor可以直接处理这条消息,或者把自己当作这条消息的中间代理,只简单地把该消息转发给另外一个actor去进行必要的处理即可。接收端actor也可以把该消息切分成很多更小的消息块,然后发送给其他actor进行进一步的处理。以这种方式,消息的处理细节可以根据需要而简单化或复杂化,并且这些细节对于消息的发送者而言是透明的。图1-1 actor之间互相通信

值得一提的是,Akka的邮箱略有不同。Akka默认提供可以保证顺序的邮箱机制,这样可以保证消息按被投递的顺序接受处理。actor可以创建子actor

在Actor模型中,一切都是actor,而且actor之间只能通过消息机制进行通信,但是actor还得知道其他actor的存在。

当actor接收到消息后,可以进行的操作之一是创建有限数量的子actor。之后父节点就会知道它的所有子节点的存在,并可以访问子节点的地址。这意味着父节点可以向子节点发送消息。

除了通过创建子节点来获知其他actor的方法,一个actor还可以把地址信息通过消息机制发送给其他actor。这样父节点便可以把它知道的所有actor(包括自身)的地址信息都通知给子actor,所以子actor可以很容易获取到父节点或兄弟节点的地址。只需要进行一点简单工作,子节点就可以知道它所处的层级体系里有哪些其他actor。此外,如果用于actor的地址都遵循一个固定规则,那么其他actor的地址也可以按照该规则去计算合成,但是如果不小心使用,可能会造成不必要的复杂性问题以及安全隐患。

actor的这种层级结构意味着,除根节点以外的所有节点都将拥有一个父节点,同时任何节点都可以拥有一个或多个子节点。这样从根节点开始遍历整棵树的actor集合被称为一个actor系统。

actor系统中的每个actor总是可以通过其地址被唯一标识。其实地址的命名并不需要遵守任何特定的模式,只要地址是唯一的,可以用来唯一标识一个actor即可。Akka使用层级结构的模式来命名,就像目录结构一样(如图1-2所示),或者也可以使用随机生成的唯一键。图1-2 actor的层级系统

如图1-2所示,根actor是顶级的actor,其他的节点都在它的下面被创建。根节点下有两个子节点A和B,A的地址是Root/A,B的地址是Root/B。A下面也有两个子节点,同样分别被命名为A和B。即使它们共享了其他actor的名字,但地址仍是唯一的(Root/A/A和Root/A/B)。子节点B也有一个地址为Root/B/A的子节点,同样,地址是唯一的,即使名称不唯一。以上是一个介绍如何生成actor地址的例子。

在Actor模型里,子actor是最有用的集成技术之一,在后续的章节中也会看到,这也是Akka中actor监督机制的基础。actor可以改变自己的状态或行为

除了发送消息和创建子actor,actor还可以改变它们对下一个消息的反应。当谈论一个actor如何改变时,我们经常会使用“行为”这个术语,其实这很容易误导人,因为行为的变化或actor反应的变化也可以表现为状态的变化。

来看看前文中讨论的调度示例。假设有一个代表个人可用性的actor,初始状态可能表明这个人可完成一个项目。当一条指派这个人到某个项目的消息送达后,actor会改变自己的状态,这样当下一条消息到达后,它会显示该人的状态是不可用的。即使这是状态的变化,我们仍然认为这是actor行为的改变,因为当下一条消息到达时,该actor已经表现为不可用了。

我们还可以让actor在接收到下一条消息时改变自己将要执行的计算,表示该系统中的人的actor可以以不同的状态存在着。当一个人受雇并可以被分配到某个项目中时,他们会以激活的状态存在。但是,某些条件可能导致这个人转移到非激活的状态(终止雇佣合同、延长休假等)下。处于激活状态下,这个人可以正常处理项目请求。但是,处于非激活状态下,系统可能会拒绝项目请求。在这种情况下,该actor在接收到下一条消息时便会改变自己要执行的计算。

因为actor可以使用这种方式改变自己的行为和状态,因此成为了很好的构建有限状态机的工具。系统中的每个行为都可以表示一个状态,当接收到消息时,actor可以从一个状态移动到另一个状态。

图1-3显示了一个简单的有限状态机模型,它表明了系统中一个人可能涉及的所有状态以及可发生的状态转移路径。在这个例子中,一个人可以以有限的状态存在,他的状态可以是被创建、激活、非激活或者终止。当一个人在系统中被创建时,这个人处于被创建的状态,但他还没有被激活,这种状态可能发生在该人已被雇用但尚未开始工作时。当一个人变成激活状态时,他不能回到被创建的状态。一个激活状态的人可以被安排到某个项目中执行工作。在某个时间点,一个人可能会变成非激活的状态,这也许是因为他将要休假了。在非激活状态下,该人不能接收处理任何请求,但可以在激活和非激活状态之间自由转移。最终,该人可能离开这个公司,这时系统会把他的状态变成终止状态。在一个人进入终止状态后,他不能回到激活或非激活状态,但可以从终止状态转移到被创建的状态,因为他可能又被这家公司雇用了。图1-3 基于actor的有限状态机

actor是一种用来构建有限状态机的很好的方式。Akka提供了特定的工具,通过Akka FSM的形式更容易创建有限状态机。

我们显然可以创建更复杂的场景,但这些已经足够让大家知道通过改变actor的行为可以实现的事情的类型。无论是改变接收到的消息,改变处理消息的方式,还是改变actor的状态,这些都属于改变行为的范畴。后面将详细讨论Akka提供的几种成熟的用于改变actor行为的方法。一切都是actor

理解了actor和actor系统的构建块之后,我们回到“一切都是actor”的想法上,看看这意味着什么。

再来考虑一下调度域。调度域中的一个人将具有各种与之相关的信息,每个人都可能有一个表示可用性的日程表,当然,这个日程表会进一步分解成更离散的时间段。

在传统的面向对象架构中,可能会用一个类来表示这个人。这个类将具有一个与之相关联的日程表类,日程表可能会进一步分解成单独的日期。当系统收到一个请求后,会调用表示这个人的类中的某个函数,然后该函数会调用日程表类中的某个函数以及单独的日期数据。

除用一个actor来表示人之外,Actor模型与上述情况并没有太大的不同。其实在Actor模型里面,日程表也可以是一个actor,因为它有可能会进行一些计算。每个单独的日期也可以是一个actor,因为它们也可能需要参与计算。实际上,请求本身也可能有一个与之相关联的actor,因为在某些情况下,请求本身可能需要聚合由模型的其他部分计算出的信息。在这种情况下,我们需要创建一个处理聚合操作的RequestWorker。

如图1-4所示,在这个模型中,我们通过在对象之间传递消息来改变对象的状态,而不是直接调用它们的函数。像CheckAvailability这样的消息流将从Person actor流入ScheduleActor actor。在这个例子中,Person代表顶层的actor,Schedule是Person的子节点,Date则是Schedule的子节点。并且因为我们使用Actor模型,所有消息都是被并发处理的,因此可以在Schedule中同时计算多个Date,不需要等待一个完成后再进行下一个。实际上,在下一个计算开始之前,我们甚至不需要完成先前的请求。Schedule可以同时处理两个尝试查看重叠日程安排的请求,但因为模式是单线程的,因此当两个请求尝试修改同一天的日程安排时,只有第一个请求会成功。这是因为用同一个actor处理该日期的两个请求时,该actor每次只能处理一个请求的消息。另一方面,如果请求修改的日期没有重叠,这两个修改请求可以同时被处理和完成,这使得我们能够更好地利用资源。图1-4 基于actor的人员调度系统示意图Actor模型的使用

Actor模型是一个强大的工具,使用恰当时可以帮助我们构建高度可扩展、高度并发的应用程序。但是与任何其他工具一样,它并不是完美的。如果使用不当,Actor模型可能会创建难以追踪的复杂代码,甚至会变得更难调试。学习完这些工具后,你可能会决定在应用程序的所有地方都使用Actor模型,从而创建一个真正的Actor系统。但是早期还是先简单尝试然后让自己习惯它比较好。后面的章节将进一步探讨这些想法,并提供关于actor构建的其他指导。

有一件关于Actor模型的事需要大家知道:和其他技术一样,有时我们可能想要应用它,但有时可能不想。在某些情况下,选择其他模型可能更适合手头的任务。在这些情况下,不要害怕去选择使用其他工具或技术。Actor模型并不是或全有或全无的命题,关键是弄清楚需要它的地方以及不需要它的地方,然后创建一条清晰明确的线分隔两者。定义清晰的边界

许多成功的系统都只对顶层的实体使用actor建模,然后在这些actor内部使用函数式编程的方法,这样做没有问题。此外,比较常见的方法是将一组actor封装在同一个接口里面,使得该接口的客户端不知道它们正在与actor进行通信,这也是一种不错的方法。这些方法都实现了创建清晰的界限。第一个例子里面只在上层拥有actor,actor里面所有的实现都是使用函数式编程完成的;第二个例子里面,接口本身就是actor和非actor之间的清晰界限。

如果决定在应用程序的部分功能内使用Actor模型,那么应该在该部分功能的所有范围内都坚持使用,而只在明确的边界上脱离它。这些边界可以是前端和后端之间的边界,也可以是应用程序中两个不同的微服务之间的边界。有很多方法可以分割应用程序,也有很多方法可以应用Actor模型。但是应该避免破坏Actor模型本身所在之处的上下文,因为在不清楚的模式或理由下切换代码风格很容易导致代码杂乱和复杂。我们一定不想在同一个上下文中频繁跳进跳出Actor模型,相反地,会希望寻找从系统边界或业务边界跳进或跳出Actor模型的方式。

来看一个具体的例子。在调度域中,我们可能需要构建一个库来处理调度任务。库里面更倾向于使用Actor模型,但在外面打算使用其他技术。这是系统边界的一个很好的例子,可以从一个计算模型转换到另一个模型。

处理这种情况的一种方法是将所有任务都当成actor暴露出来,这样外部的客户端代码就会充分意识到它们对接的是actor。在这种情况下,会得到一个如下所示的消息协议。

然后客户端会使用如下代码来发送消息。

这样是可以的,但还不够完善。问题在于,actor代码里混杂着其他代码,这些代码可能具有不同的并发机制,从而引发一些意想不到的问题。这个过程实际上包括以下几个步骤:首先创建一个项目,然后安排项目,最后执行其他操作。这几个步骤都会涉及调度系统,即都和actor有联系。然而,代码中可能还有其他部分与不使用actor的上下文进行通信,所以在这里也可以存在函数调用或者future等机制。这种不断跳进和跳出Actor模型的行为若不能很好地被隔离,系统会变得很难跟踪。在这种情况下,更好的方法是创建一个API来封装actor,代码如下。

这层包裹在actor外的封装相当于一层与外界隔离的绝缘层,是用来隔离actor和非actor的明确边界。此时若要使用此API,代码如下。

代码变得更短了,我们成功地把复杂的程序移到了一个单独的函数中,那么这样真的更好吗?这种方法的好处是,客户端代码无须知道它内部是否正在使用actor,在这种情况下,actor已经成为一个实现细节,而不是API内的一部分了。再次使用这个API时,我们刚好可以利用这种函数调用,这意味着当我们将此函数调用与其他函数调用一起嵌套使用时,代码看起来会更加一致、更加干净。随着代码库的扩大,actor变得越来越复杂,因此这样的隔离对于保持系统的可维护性至关重要。

那么是否应该在任何情况下都这样做呢?所有的actor都应该用API来封装吗?这样应用程序就无须在意自己是否在和actor打交道了。答案明显是“不”。比如我们用Actor模型构建的调度系统的调度库就没必要进行这样的封装,相反地,这样做会造成不必要的复杂性。实际上这样做会使actor之间的交互变得更难,当使用Actor模型时,我们应该面对actor,而不应该试图隐藏这个事实。只有在系统的边界上——在那里我们会想要转换到不同的计算模型上——才应该创造这种隔离。何时适合使用Actor模型

上文中已经确定,有的时候使用actor更适合,有的时候则应该选择使用完整的Actor模型。而且使用它们时需要注意上下文,小心谨慎。但是什么时候是使用它们的正确时机呢?什么时候应该使用独立的actor,什么时候应该建立一个完整的Actor系统呢?当然,不可能有明确的规则告诉我们应该在何种情况下使用何种方案。每种情况都是独一无二的,不过,有一些指导建议可以作为实际操作时的参考。

第一个很明显需要选择actor的情况是,对高并发有严格要求的同时又需要维护某种状态。毕竟维持并发状态是Actor模型里面最基本的属性。

另一个明显应该选择actor的情况是,构建有限状态机的时候。因为actor的自然结构就很适合用于这种场景。另外,如果只是处理一个有限状态机,一个actor就可以完成了,但是如果要处理多个有限状态机,而且还可能彼此交互,那么就应该考虑使用Actor模型。

还有一个更微妙的适合用actor的情况是,需要高并发,同时也需要很小心地管理并发。例如,我们需要确保特定的一组操作可以与系统中的某些操作并发运行,但不能与系统中其他操作并发运行。在调度示例中,这可能意味着我们希望多个人能够同时修改系统中的项目,但不希望这些人同时修改同一个项目。这种情况下刚好可以利用actor提供的单线程工作模式,这些人可以各自独立操作,但是系统中对于特定项目进行的修改只能在一个actor中操作。结论

Actor模型是一个强大的工具,像其他所有强大的工具一样,我们需要了解它并细心控制它才能从中获得最大的收获。不应盲目地假设Actor模型在任何情况下都是最合适的。但是,在对它有一个扎实的理解并开始考虑如何应用及何时应用它时,会发现它实际上可以应用于很多不同的场景下。因为它很自然地反映了现实世界的异步性质,因此在很多情况下都是一个很好的用于建模的工具。掌握Actor模型之后,我们可以摆脱全局一致性的束缚,并且确信没有任何事情是绝对瞬间发生的,都是相对于观察者而言的。

Actor模型是一种用于组织应用程序功能的不同范式,而且具有许多优点。本章中提到的基础概念将在本书的其他部分进一步介绍,以让大家学会如何构建优秀的actor,并发挥该模型的最大优势。

Actor模型中的很多细节会让初学者觉得不太寻常甚至感到束缚。但是这种限制是有一定道理的,而且Actor模型带来的好处会远远弥补当初为了掌握它所付出的努力。

[1] 更多有关信息请参阅视频“Hewitt,Meijer and Szyperski:The Actor Model (everything you wanted to know...)”(http://www.youtube.com/watel?v=7erJIDV_Tlo)。第2章 Akka简介

在本章中,我们将介绍开源库Akka。大家可能已经知道Akka是什么了,但也许并不知道关于它的来龙去脉。

Akka是一个工具包,它可以帮助我们实现本书后面章节中涉及的所有模式,并且允许我们直接在自己的实际项目中使用这些技术。Akka是什么

官方网站上是这样介绍Akka的:“Akka是在Java虚拟机(JVM)上构建高并发、分布式、弹性消息驱动应用的开源工具包。”

Akka支持多种编程模式,并且强调Erlang风格的基于actor的并发。

以上便是关于Akka的简单介绍,但它并不是仅有这些特性。下面让我们更深入地来了解它吧。Akka是开源的

Akka是根据Apache 2许可证(一种公认的开源许可证)发布的开源项目,可以同时在其他开源或商业函数库及应用程序中被自由使用和扩展。

虽然也可以在Java中使用Akka,但是它本身是用Scala 编写的,所以自然从Scala语言中获得了许多特性,包括强类型安全、高性能、低内存占用,以及与所有JVM库和语言兼容。Akka中大量使用的Scala语言的一个关键特性是,不可变数据结构。Akka在其消息传递协议中经常使用Scala,事实上,Java开发者为了节省时间而特意使用Scala编写消息的情况并不罕见。

甚至还有专门为了方便Clojure使用Akka而设计的适配器工具包,如果不了解Clojure,可以查阅一下相关资料,其实它是JVM上的一个Lisp语言变种。Akka正在蓬勃发展

Akka还在不断更新迭代中,官方的更新频率基本保持在每几个月至少会发布一个小版本的水平上,函数库和辅助插件的生态系统要更加活跃。

还有一些人正在努力把Akka框架转移到.NET平台以及JavaScript上。

Akka的更新频率非常快,但官方会继续长期支持稳定版本。Akka大版本之间的升级过程都是非常顺利的,从而可以让使用Akka的项目以最小的风险进行定期升级。Akka是为分布式设计的

Akka与许多其他版本的Actor模型一样,不仅适用于单台多核的计算机系统,还适用于集群环境。因此,Akka的设计初衷就是为本地开发和分布式开发提供一样的编程模型——当需要把代码部署在集群环境中时,其开发方式和本地开发方式几乎一样,因此可以轻松地在本地进行代码开发和测试,然后部署到分布式的集群环境中。

在Akka系统里面,人们更习惯将交互行为建模成消息通信机制,与之相对的是远程过程调用(R P C)系统,它们把交互行为建模成过程调用。这是一个重要的区别,我们将在后面的章节中详细讨论。

虽然可以用Akka编写专门用于集群环境下的分布式代码(例如通过监听节点加入或离开集群的事件),但是actor内部的实际逻辑不会改变。无论是和本地还是远程的actor交互,它们的通信机制、交付保证、故障处理和其他概念都保持不变。

Akka提供了构建响应式系统需要的关键特性。这类系统是指严格遵守响应式声明中的基本特性的系统(可以通过http://www.reactivemanifesto.org/来查看)。该声明描述了响应式程序都应遵循的特征:高响应性、高容错性、高可伸缩性、消息驱动。其中消息驱动正是Akka所拥有的关键特性,Akka通过它支持其他特性。

在Akka系统中,一个actor可以是本地的,也可以是远程的。如果发送和接收actor处在同一个JVM中,那么它相对于这些actor就算

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载