Akka入门与实践(txt+pdf+epub+mobi电子书下载)


发布时间:2020-09-02 09:10:17

点击下载

作者:[加]Jason Goodwin(贾森·古德温)

出版社:人民邮电出版社

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

Akka入门与实践

Akka入门与实践试读:

前言

本书将尝试帮助入门级、中级以及高级读者理解基本的分布式计算概念,并且展示如何使用Akka来构建具备高容错性、可以横向扩展的分布式网络应用程序。Akka是一个强大的工具集,提供了很多选项,可以对在本地机器上处理或网络远程机器上处理的某项工作进行抽象封装,使之对开发者不可见。本书将介绍各种概念,帮助读者理解网络上各系统进行交互的困难之处,并介绍如何使用Akka提供的解决方案来解决这些问题。

编写本书的过程也是作者自我学习、自我发现的过程。希望读者也能一起分享这些知识。作者在工作中有很多使用Java 8和Scala来编写Akka应用程序的经验,但是在编写本书的过程中,学到了很多更深入的Akka细节。这本书很好地介绍了为什么要使用Akka,以及如何使用Akka,并且展示了如何使用Akka工具集来开始构建可扩展的分布式应用程序。本书并不仅仅是官方文档的重复,更涉及了许多作为当代程序员要成功构建能够处理扩展性相关问题的系统时应该要理解的重要话题和方法。本书涉及的内容

第1章 初识Actor:Akka工具集以及Actor模型的介绍。

第2章 Actor与并发:响应式编程。Actor与Future的使用。

第3章 传递消息:消息传递模式。

第4章 Actor的生命周期—处理状态与错误:Actor生命周期、监督机制、Stash/ Unstash、Become/Unbecome以及有限自动机。

第5章 纵向扩展:并发编程、Router Group/Pool、Dispatcher、阻塞I/O的处理以及API。

第6章 横向扩展—集群化:集群、CAP理论以及Akka Cluster。

第7章 处理邮箱问题:加大邮箱负载、不同邮箱的选择、熔断机制。

第8章 测试与设计:行为说明、领域驱动设计以及Akka Testkit。

第9章 尾声:其他Akka特性。下一步需要学习的知识。阅读本书的前提条件

读者需要一台能够安装各种工具的计算机,比如Java JDK8(用于Java开发)或是Java JDK6(用于Scala开发)。除此之外,还需要SBT简单构建工具(Simple Build Tool)或是Typesafe Activator(已经包含SBT)。本书会介绍这些工具的安装。本书的目标读者

本书面向想要构建满足大规模用户需求的应用程序的初级至中级Java或Scala开发者。如果应用程序在处理日益增加的用户量以及数据量时需要满足高性能要求,那么建议阅读本书。本书可以让我们只编写更少、更简单的代码就轻松构建并扩展网络应用程序,给用户提供更强大的功能。读者反馈

我们欢迎读者的反馈。对本书的任何看法敬请告知我们。读者反馈对我们至关重要,可以帮助我们出版读者真正能够从中获益的书籍。

如果有普通的反馈,请直接向feedback@packtpub.com发送电子邮件,并且在邮件标题中提及书名。

如果有您精通的话题并且有兴趣撰写书籍或是向某本书做贡献,请从www.packtpub. com/authors查阅作者手册。客户支持

您是Packt书籍的拥有者,所以我们提供了很多服务,帮助您从所购买的书中获得最大的收获。下载示例代码

读者可以使用自己的账户从http://www.packtpub.com下载购买过的所有Packt Publishing书籍的示例代码文件。如果从别处购买了本书,那么可以访问http://www. packtpub.com/support并进行注册,我们会通过电子邮件将文件发送给您。下载本书的彩色图片

我们还提供了一个PDF文件,其中包含本书中使用的截屏/图表的彩色图片。这些彩色图片可以帮助读者更好地理解输出结果中的不同之处。读者可以从https://www.packtpub. com/sites/default/files/downloads/LearningAkka_ColoredImages.pdf处下载该文件。电子书、折扣以及其他

Packt出版社为出版的每一本书都提供了PDF和ePub的电子书版本。读者可以从www.packtpub.com获取电子书版本。纸质书的客户在购买电子书时可以享受折扣优惠。请通过customercare@packtpub.com联系我们,获取更多细节。

您还可以在www.packtpub.com阅读更多免费的技术文章,订阅免费新闻,并接收到大量Packt书籍以及电子书的折扣优惠。问题

如果对本书有任何问题,请通过questions@packtpub.com联系我们,我们将尽最大的努力解决您的问题。第1章初识Actor1.1 本章概述

Actor模型是一种并发计算的理论模型,而Akka的核心其实是Actor模型的一种实现。在本章中,我们将通过了解Akka和Actor模型的历史来介绍Akka的核心概念。这会帮助读者更好地理解Akka到底是什么,以及Akka试图要解决什么样的问题。其次,本章中将重复使用同一个例子来阐述本书的目的。

在介绍了上面这些概念后,本章将会把篇幅放在开发环境及工具的配置方法上。我们将配置好机器的环境,集成开发环境(Integrated Development Environment,IDE)并介绍第一个Akka项目(包括该项目的单元测试)。1.2 什么是Akka

本节将介绍Akka和Actor模型。Akka一词据说来源于瑞典的一座山,我们说到Akka时,通常是指一个分布式工具集,用于协调远程计算资源来进行一些工作。Akka是Actor并发模型的一种现代化实现。现在的Akka可以认为是从许多其他技术发展演化而来的,它借鉴了Erlang的Actor模型实现,同时又引入了许多新特性,帮助构建能够处理如今大规模问题的应用程序。1.2.1 Actor模型的起源

为了更好地理解Akka的含义及其使用方法,我们将快速地了解Actor模型的历史,理解Actor模型的含义,以及它是如何一步一步发展到如今的Akka这样一个用于构建高容错性分布式系统的框架。

Actor并发模型最早出现于一篇叫作《A Universal Modular Actor Formalism for Artificial Intelligence》的论文,该论文发表于1973年,提出了一种并发计算的理论模型,Actor就源于该模型。我们将在本节中学习Actor模型的特性,理解它的优点,能够在并发计算中帮助我们解决共享状态带来的常见问题。1.2.2 什么是Actor

首先,让我们来定义什么是Actor。在Actor模型中,Actor是一个并发原语;更简单地说,可以把一个Actor看作是一个工人,就像能够工作或是处理任务的进程和线程一样。把Actor看成是某个机构中拥有特定职位及职责的员工可能会对理解有所帮助。比如说一个寿司餐馆。餐馆的职员需要做各种各样不同的工作,给客人准备餐盘就是其中之一。1.2.3 Actor和消息传递

在面向对象编程语言中,对象的特点之一就是能够被直接调用:一个对象可以访问或修改另一个对象的属性,也可以直接调用另一个对象的方法。这在只有一个线程进行这些操作时是没有问题的,但是如果多个线程同时读取并修改同一个值,那么可能就需要进行同步并加锁。

Actor和对象的不同之处在于其不能被直接读取、修改或是调用。反之,Actor只能通过消息传递的方式与外界进行通信。简单来说,消息传递指的是一个Actor可以接收消息(在我们的例子中该消息是一个对象),本身可以发送消息,也可以对接收到的消息作出回复。尽管我们可以将这种方式与向某个方法传递参数并接收返回值进行类比,但是消息传递与方法调用在本质上是不同的:消息传递是异步的。无论是处理消息还是回复消息,Actor对外界都没有依赖。

Actor每次只同步处理一个消息。邮箱本质上是等待Actor处理的一个工作队列,如图1-1所示。处理一个消息时,为了能够做出响应,Actor可以修改内部状态,创建更多Actor或是将消息发送给其他Actor。图1-1 

在具体实现中,我们通常使用Actor系统这个术语来表示多个Actor的集合以及所有与该Actor集合相关的东西,包括地址、邮箱以及配置。

下面再重申一下这几个重要的概念:● Actor:一个表示工作节点的并发原语,同步处理接收到的消

息。Actor可以保存并修改内部状态。● 消息:用于跨进程(比如多个Actor之间)通信的数据。● 消息传递:一种软件开发范式,通过传递消息来触发各种行为,

而不是直接触发行为。● 邮箱地址:消息传递的目标地址,当Actor空闲时会从该地址获

取消息进行处理。● 邮箱:在Actor处理消息前具体存储消息的地方。可以将其看作

是一个消息队列。● Actor系统:多个Actor的集合以及这些Actor的邮箱地址、邮箱和

配置等。

虽然现在看来可能还不是太明显,但是Actor模型要比命令式的面向对象并发应用程序容易理解多了。我们可以举一个现实世界中的例子来比喻使用Actor模型来建模的过程,这会帮助我们理解它带来的好处。比如有一个寿司餐馆,其中有3个Actor:客人、服务员以及厨师。

首先,客人向服务员点单。服务员将客人点的菜品写在一张纸条上,然后将这张纸条放在厨师的邮箱中(将纸条贴在厨房的窗户上)。当厨师有空闲的时候,就会获取这条消息(客人点的菜品),然后就开始制作寿司(处理消息),直至寿司制作完成。寿司准备好以后,厨师会发送一条消息(盛放寿司的盘子)到服务员的邮箱(厨房的窗户),等待服务员来获取这条消息。此时厨师可以去处理其他客人的订单。

当服务员有空闲时,就可以从厨房的窗户获取食物的消息(盛放寿司的盘子),然后将其送到客人的邮箱(比如餐桌)。当客人准备好的时候,他们就会处理消息(吃寿司),如图1-2所示。

运用餐厅的运作来理解Actor模型是很容易的。随着越来越多的客人来到餐厅,我们可以想象服务员每次接收一位客人的下单,并将订单交给厨房,接着厨师处理订单制作寿司,最后服务员将寿司交给客人。每个任务都可以并发进行。这就是Actor模型提供的最大好处之一:当每个人各司其职时,使用Actor模型分析并发事件非常容易。而使用Actor模型对真实应用程序的建模过程和本例中对寿司餐厅的建模过程并没有太大差异。

Actor模型的另一个好处就是可以消除共享状态。因为一个Actor每次只处理一条消息,所以可以在Actor内部安全地保存状态。如果读者此前没有接触过并发系统,那么可能不是很容易马上理解这一点。不过我们可以用一种简单的方式来进行说明。假设我们尝试执行两个操作,同时读取、修改并保存一个变量,那么如果我们不进行同步操作并加锁的话,其中的一个操作结果将丢失。这是一个非常容易犯的错误。图1-2 

在下面的例子中,有两个线程同时执行一个非原子的自增操作。让我们来看看在线程间共享状态会带来什么结果。会有多个线程从内存中读取一个变量值,将该变量自增,然后将结果写回内存。这就是竞态条件(Race Condition),可以通过保证某一时刻只有一个线程访问内存中的值来解决其带来的一部分问题。下面用一个Scala的例子进行说明。

如果我们尝试使用多个线程并发地对一个整型变量执行100 000次自增操作,那么极有可能会丢失掉其中一些自增操作的结果。 import concurrent.Future import concurrent.ExecutionContext.Implicits.global var i, j = 0 (1 to 100000).foreach(_ => Future{i = i + 1}) (1 to 100000).foreach(_ => j = j + 1) Thread.sleep(1000) println(s"${i} ${j}")

我们使用x = x + 1这个简单的函数将i和j都自增了100 000次,其中i的自增操作通过多个线程并发执行,而j的自增操作则只通过一个线程来执行。等待1秒钟后,我们再打印运行结果,确保所有更新都已经完成。读者可能会认为运行结果是100000 100000,然而结果却并非如此,如图1-3所示。

共享状态是不安全的。如果两个线程同时读取一个值,将该值自增,然后写回内存,那么由于该值同时被多个线程读取,其中的某些操作结果将会丢失。这就是竞态条件,也是使用共享状态的并发模型存在的最基本的问题之一。图1-3 

通过推导每一步读取和写入操作的结果,我们能够更清晰地展示出竞态条件发生时的具体情况: [...] Thread 2 reads value in memory - value read as 9 Thread 2 writes value in memory - value set to 10 (9 + 1) Thread 1 reads value in memory - value read as 10 Thread 2 reads value in memory - value read as 10 Thread 1 writes value in memory - value set to 11 (10 + 1) !! LOST INCREMENT !! Thread 2 writes value in memory - value set to 11 (10 + 1) Thread 1 reads value in memory - value read as 11 [...]

为了保证内存中的共享状态值不出现错误,我们可以使用锁和同步机制,防止多个线程同时读取并写入同一个值。这就会导致问题变得更为复杂,更难理解,也更难确保结果的正确。

使用共享状态带来的最大威胁就是代码在测试中看上去经常是正确的,但是一旦有多个线程并发运行时,就会时不时地出现一些错误。由于测试时通常都不会出现多线程并发的情况,因此这些Bug很容易被忽略。Dion Almaer曾经在博客中写到过,大多数Java应用程序都存在大量的并发Bug,因此有时能正确运行,有时却运行失败。Actor通过减少共享状态来解决这一问题。如果我们把状态移到Actor内部,那么只有该Actor才能访问其内部的状态(实际上只有一个线程能够访问这些内部状态)。如果把所有消息都看做是不可变的,那么我们实际上可以去除Actor系统中的所有共享状态,构建更安全的应用程序。

本小节中的概念是Actor模型中的核心。第2章Actor与并发和第3章发送消息将会更详细地介绍并发、Actor以及消息传递。

Erlang语言中监督和容错机制的发展演化

自从在前面提到的论文中第一次出现以来,Actor模型随着时间的推移不断地发展,它对程序语言设计也产生了显著影响(比如Scheme)。

20世纪80年代,爱立信在Erlang语言中实现了Actor模型,用于嵌入式电信应用程序。这一实现绝对值得一提。该实现中引入了通过监督机制(Supervision)提供的容错性概念。爱立信使用Erlang和Actor模型实现了一款日后经常被提及的应用,叫作AXD301。AXD301能够提供99.9999999%的可用性,这一点令人惊叹。相当于在100年中,AXD301只有3.1秒的时间会宕机。AXD的开发团队表示,他们通过消除共享状态(正如我们之前介绍的一样)并引入Erlang中的监督容错机制来达到如此高的可用性。

Actor模型也是通过监督机制来提供容错性的。监督机制基本上是指把处理响应错误的责任交给出错对象以外的实体。这意味着一个Actor可以负责监督它的子Actor,它会监控子Actor的运行错误,并且根据子Actor生命周期中的运行表现执行相应的操作。当一个正在运行的Actor发生错误时,监督机制提供的默认处理方式是重新启动发生错误的Actor(实际上是重新创建)。这种重新创建出错Actor的处理方式基于一种假设:意外发生的错误是由错误状态导致的,因此移除并重新创建应用程序中出错的部分可以将其修复,并恢复正常工作。我们也可以编写自定义的错误处理方式作为监督策略,这样一来基本上就可以采取任何操作将应用程序恢复至工作状态,如图1-4所示。图1-4 

在本书中,我们会将关于分布式系统的容错性作为一个通用的问题,在多个章节中交叉介绍,并且把重点放在Akka和分布式系统的容错性上。第4章“Actor的生命周期——处理状态与错误”将重点介绍这一内容。

分布式与位置透明性的发展演化

如今的业务需求要求工程师能够设计同时响应成千上万个并发用户请求的系统,而用一台机子来运行这样的系统是绝对不够的。除此之外,多核处理器变得越来越流行,因此将任务分布到多个核上以确保有效地利用硬件资源也变得越来越重要。

Akka采用了Actor模型,并且继续对其发展演化,引入了对如今的工程师来说最重要的一个特性:网络环境下的分布式。Akka将其自己视作是一个支持容错性的分布式工具集。也就是说,Akka是一个提供了在多个服务器的物理边界之间工作的工具集。在支持高可用性的同时,几乎可以无限扩展。在最近的几个Akka发布版本中,大多数新增的特性都和解决网络系统的问题有关。最近引入的Akka Cluster(集群)允许将一个Actor系统部署到多台机器上,并且这一点对用户不可见。Akka IO和Akka HTTP也已经进入了核心库,使得系统之间的交互变得更简单。Akka对于Actor模型的重要贡献之一就是位置透明性的概念:就是说一个Actor的邮箱地址实际上可以是一个远程地址,但是这个地址对开发者来说基本上是透明的,所以无论是否是远程地址,编写的代码也基本上是相同的。

Akka沿用了Erlang的一些Actor实现,并且打破了Actor系统的物理边界。Akka添加了远程处理以及位置透明性,使得一个Actor的邮箱可以在远程机器上,而Akka会对网络上的消息传输进行抽象封装。

最近,Akka又引入了集群。读者可能知道一些基于Amazon那篇Dynamo论文的分布式系统,比如Dynamo、Cassandra和Riak。Akka Cluster也采用了类似的现代化方法。有了Cluster以后,一个Actor系统就可以运行在多台机器之上,而不同的节点之间也可以就各自的状态互相通信交互,这就实现了一个可伸缩的Akka集群,并且没有单点故障。这一机制和Dynamo风格的数据库类似,比如Riak和Cassandra。这个特性非常好,使得创建可伸缩并且具备优良容错性的系统变得相当容易。[1]

Typesafe(提供Scala和Akka等技术的公司)不断在通过提供许多网络工具(比如Akka IO和Akka HTTP)来推广分布式计算。除此之外,Typesafe已经参与了Reactive Streams提案,而Akka也实现了第一个版本,支持用于异步处理的非阻塞背压(Back-Pressure)。

在本书中,我们会详细介绍上面诸多内容。第4章Actor的生命周期——处理状态与错误和第5章纵向扩展将更详细地介绍远程处理。第6章横向扩展——集群化将介绍Cluster。第7章处理邮箱问题将介绍Reactive Streams。1.3 本书示例系统

在本书中,我们将主要构建两个服务,推荐读者着手操作,一起一步一步地实践。在每章的结尾都有一些课后作业,通过这些练习,读者可以将这一章学到的内容付诸实践。在开始下一章的学习之前,请完成这些课后作业。如果想和别人分享学习的进度,或是希望将这些作业的答案开源,那么可以把它们发布到GitHub上。

在本书中,我们将主要开发两个软件。第一个例子用于展示状态和分布式的处理;第二个例子用于展示如何完成工作。1.3.1 示例1:处理分布式状态

我们将研究如何构建一个可扩展的分布式内存数据库。在另一个例子中,我们将把数据存储到这个数据库中。说得明确一点,我们将构建一个类似于Redis或Memcached的高可用键值存储。我们构建的数据库将处理其在真实应用场景下所需的所有并发、集群以及分布式问题。为了了解如何提供上面的诸多特性,我们应学习如何在集群中对数据库的数据和负载进行切分及分配,有效地利用硬件资源以及如何进行横向扩展利用多台机器。我们将了解面对现实问题时的设计挑战及常见解决方案。我们还将研究如何构建客户端库,与我们编写的基于Akka的数据库进行交互,并且允许JVM上的任意用户使用。我极力推荐读者自己编写一个这样的数据库,放到GitHub上面去,并且在简历里展示一下。

这看上去相当复杂,但是庆幸的是,有了Akka工具集,要完成上面所有的任务其实相当简单。我们将从零开始,很快地构建出这个完整的系统。1.3.2 示例2:完成更多工作

为了在本书中举例展示如何使用Akka完成更多的工作,我们将构建一个用于阅读文章的API,读取博客或是新闻文章,抽取出主要的文本内容,然后将其存储到我们编写的数据库中,作为其他应用的数据源。

下面是一个用例:假设有一个移动设备上的阅读器,通过我们编写的服务从流行的RSS Feed请求读取文章,然后显示主要的文本内容,能够缩放文字适应屏幕显示,提供更好的阅读体验。我们的服务负责从主要的RSS Feed解析出文本内容,使其在移动设备上显示速度更快,用户无需等待。如果想要体验这样的真实移动应用程序,可以看看iOS上的Flipboard,这是我们所编写服务的应用的一个很好的例子,如图1-5所示。图1-5 

我们已经介绍了本书涉及的内容,接下来就开始配置环境并创建一个Actor吧!1.4 配置环境

在真正深入学习Akka之前,我们将介绍开发环境以及项目基本结构的设置。在本书后面的章节中新建项目时,读者可以回过头来参考本节的内容。1.4.1 选择一门语言

Scala和Java的API基本上是一一对应的,所以读者选择自己熟悉的语言即可。如果既会Scala,又会Java,那么Scala API当然更符合推荐的风格。不过两者都是绝对可用的选择。因为我们可以通过Scala的Actor API使用Scala来访问使用Java构建的Actor,反之亦然,所以也没有必要立刻就做出决定。选择能够让自己更快上手开发的就行。现在我们的重点是学习Akka,而不是学习语言。一旦了解了Akka,要学习另一个语言的API并不需要费很大的功夫。1.4.2 安装Java——Oracle JDK8

本书并不支持所有旧版本的Java,只支持Java 8。因此如果读者是一个Java开发者,但是对Java 8的特性并不熟悉,那么应该花点时间了解一下lambda和stream API。下面的教程对此进行了介绍:

http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/ index.html。

读者将会发现,本书中大量使用了lambda。因此花时间了解一下绝对是有帮助的。

在Windows上安装

从Oracle下载并安装Windows JDK8的安装包(exe):http://www.oracle.com/ technetwork/java/javase/downloads/index.html。

按照说明安装。

在OS X上安装

从Oracle下载并安装OS X JDK8的安装包(dmg):http://www.oracle.com/ technetwork/java/javase/downloads/index.html。

按照说明安装。

在Linux或Unix上安装(通用步骤)

有很多方法可以用于在*Nix系统上安装Java。我们可以使用通用安装包,也可以使用包管理器,比如基于Red Hat Enterprise Linux(RHEL)的发行版上的Yum和基于Debian的发行版上的Apt-Get。不同发行版上包管理器的安装方法各不相同,不过如果需要的话,可以在Google搜索引擎上找到相应的安装步骤。

通用安装包适用于所有系统,因此我们在这里介绍这种方法。这也是能够使用的最基本的安装方法。它会安装JDK,并且为当前用户启动JDK,但是不会对系统做出更改。如果想要修改系统的JDK/JRE,可以参照所运行的特定发行版的安装步骤。这一点对于服务器和桌面环境都是适用的。如果使用的是桌面环境,可以参照一下为其他用户设置默认JDK/JRE的步骤。

从Oracle下载Linux的JDK安装包(tar.gz):http://www.oracle.com/ technetwork/java/javase/downloads/index.html

该文件的名字可能类似jdk-8u31-linux-x64.tar.gz。将tar.gz的文件解压到合适的位置,比如/opt:sudo cp jdk-8u31-linux-x64.tar.gz /opt cd /opt sudo tar -xvf jdk- 8u31-linux-x64.tar.gz

我们需要将JAVA_HOME设置为Java8的文件夹:echo 'export JAVA_HOME=/opt/jdk1.8.031' >> ~/.profile

并且确认PATH包含Java的bin目录:echo 'export PATH=$PATH:/opt/jdk1.8.031' >> ~/.profile

现在,IDE和Sbt/Activator就可以使用安装的JDK来生成并运行我们所构建的应用程序了。1.4.3 确认Java环境配置

无论运行的是哪个操作系统,我们都需要确认设置了JAVA_HOME,并且PATH包含了Java的bin目录。只有使用通用安装包的时候才需要手动设置JAVA_HOME和PATH,但是无论使用哪种安装方式,都应该在一个新建的终端窗口中确认配置了JAVA_HOME环境变量并向PATH中添加了Java的bin文件夹。1.4.4 安装Scala

如果使用的是Scala,那么需要在系统上安装Scala及其REPL。在本书撰写时,最新的Scala版本(2.11)会被编译成Java 1.6的字节码,因此无需安装JDK8。不过有人说未来的Scala版本可能会要求JDK8,所以这一点可能会有变化。

Scala并不一定要单独安装。Typesafe Activator包含了Scala以及我们需要的所有工具,接下来我们会进行安装。1.4.5 安装Typesafe Activator

Typesafe Activator的安装包内打包了Scala、Akka、Play,简单构建工具(Simple Build Tool,SBT)以及其他一些特性,比如项目结构与模板。

Windows

从Typesafe下载Typesafe Activator:http://www.typesafe.com/get-started

运行安装程序,按照屏幕上的步骤安装。

Linux/Unix/OS X

从Typesafe下载Typesafe Activator:● http://www.typesafe.com/activator/download。● http://www.typesafe.com/get-started。

将文件解压到合适的位置,比如/opt:cd /opt sudo unzip typesafe- activator-1.2.12.zip。

赋予Activator可执行权限:sudo chmod 755 /opt/activator-1.2.12/activator。

将Activator添加至PATH:echo 'export PATH=$PATH:/opt/activator- 1.2.12'。

退出并重新登录。确认可以在命令行运行下述命令:activator --version

这句命令会输出类似如下的结果:sbt launcher version 0.13.5

OS X

除了可以使用Linux上的方法安装Activator外,还可以使用brew。本小节介绍使用brew的安装方法。

打开一个终端窗口。

输入下述命令(拷贝自http://brew.sh)。该命令会安装Homebrew OS X包管理器。ruby -e "$(curl –fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

最后,在终端窗口中输入下述命令并按回车键:brew install typesafe-activator

确认可以从命令行访问Activator:activator --version 1.4.6 新建项目

在本书中,我们将使用Activator快速建立项目的结构。我们可以使用任意模板来生成一个项目。我们只会使用基本的Java和Scala模板。读者可以随意尝试其他选项。Typesafe有许多由用户提交的模板,这些模板展示了如何结合使用各种不同的技术和方法。

在终端窗口的命令行提示符中输入如下命令,创建一个新的Activator模板:activator new

该命令的输出如下。

从中选择一个模板或者键入模板的名称:● minimal-akka-java-seed● minimal-akka-scala-seed● minimal-java● minimal-scala● play-java● play-scala 提示 可以敲击Tab键查看所有模板

根据想要使用的语言,选择minimal-scala或者minimal-java。接着会要求输入应用程序的名称,可以叫作akkademy-db。

进入akkademy-db文件夹,运行activator test,确认项目及环境已正确配置。cd akkademy-db activator test

在输出中可以看到,项目进行了编译,并且运行了测试。如果有任何问题,读者可能需要在继续往下阅读前查询stack-overflow,解决环境配置问题。

如果没有任何问题的话,可以看到下述输出:[info] Passed: Total 1, Failed 0, Errors 0, Passed 1 [success] Total time: 3 s, completed 22-Jan-2015 9:44:21 PM 1.4.7 安装IDE

我们已经配置好了环境,并且可以运行。现在可以开始编写代码了。如果读者想使用简单的文本编辑器,那么请跳过本小节。Emacs和Sublime都是非常优秀的文本编辑器,提供语法高亮和支持自动补全的插件。不过如果读者想要使用IDE的话,本小节会介绍Eclipse和Intellij的配置。

安装Intellij CE

如果选择使用IDE的话,推荐Intellij。在撰写本书的过程中,和我共事的许多Java开发者接手开发SBT项目后,他们几乎全部都转而使用Intellij,并且再也不想改回去了。

Intellij现在有内置的SBT支持,因此用于开发Akka项目的时候速度会很快。由于Intellij原生地支持本书中用到的所有技术,因此用户几乎不需要进行任何IDE配置。

创建并运行项目的步骤:

1.下载并安装Intellij CE(免费)。

2.安装完成后,选择打开项目。选择akkademy-db文件夹。

3.如果使用的是Java的话(或者如果Scala 2.12要求Java 1.8),选择Java 1.8;如果使用的是Scala 2.11,那么选择Java 1.6或Java 1.7。启用自动导入功能。单击确定按钮。

Eclipse

如果使用Eclipse的话,那么推荐读者下载Scala-Ide。Scala-Ide中包含了使用Java或Scala创建本书中Sbt/Akka项目所需的所有插件。即使只使用Java,读者也可能会在学习本书的过程中使用一些Scala相关的内容。

安装Eclipse(Scala-Ide)

Scala-Ide是一个在Eclipse中集成了Sbt和Scala插件的安装包。可以从http://scala- ide.org下载。

解压缩下载的文件。可以把解压缩后的文件夹移动到其他位置,比如Linux下的/opt和OSX下的~/Applications。

运行Eclipse二进制可执行文件。选择一个工作目录,或是设置默认工作目录。

进入Preferences: Java | Compiler,确认选择了正确的Java JDK。

准备Eclipse项目

要在Eclipse中打开项目,就必须要先生成一个Eclipse项目。

首先,我们必须要将eclipse sbt插件添加到环境中。打开全局sbt插件文件(如果没有的话新建一个),该文件位于~/.sbt/{version}/plugins/plugins.sbt。其中version就是sbt的版本。在撰写本书时,sbt的版本是0.13,所以该文件为~/.sbt/0.13/ plugins/plugins.sbt。

向该文件中添加如下行,如果文件中包含多行的话,确保行与行之间以空行相隔。addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "3.0.0")

可能需要查看sbteclipse的Git项目,确保该插件仍然可用:https://github. com/typesafehub/sbteclipse/。

一旦安装完该插件后,就可以生成eclipse项目:https://github.com/ typesafehub/ sbteclipse/。

在终端窗口中,进入之前建立的项目(akkademy-db)。在项目根目录下运行activator eclipsify,生成eclipse项目结构。

运行成功的话,将会看到如下信息:[info] Successfully created Eclipse project files for project(s): [info] akkademy-db

向Eclipse导入项目

在Eclipse中,选择File | Import。

选择General | Existing Projects into Workspace,如图1-6所示。图1-6 

选择文件夹,点击下一步,如图1-7所示。 提示 要注意的是,如果修改了build.sbt,那么需要重新生成

项目,并且可能需要重新导入。图1-7 1.5 创建第一个Akka应用程序——设置SBT项目

既然我们已经介绍了如何配置环境和创建项目,现在就可以开始使用Akka来编写一些Actor的代码并进行测试了。我们将使用简单构建工具(Simple Build Tool,SBT)。SBT是Scala项目的首选构建工具,也是Play框架和Activator实际使用的构建工具。SBT并不复杂,而且我们只使用它来管理依赖,构建、测试并运行应用程序,因此它不会成为我们学习Akka的障碍。1.5.1 将Akka添加至build.sbt

现在我们将使用IDE打开Java或Scala应用程序。Activator创建的项目结构并不是用于Akka的,因此我们首先需要添加Akka的依赖。我们将添加Akka的核心Actor模块akka-actor和akka-testkit,akka-testkit提供的工具使得我们对Actor的测试变得更加简单。

在一个Scala项目的build.sbt文件中,我们可以看到类似下面的代码。要注意的是,build.sbt中的依赖实际上是Maven的依赖。我们可以轻松地将任何Maven依赖添加到build.sbt的依赖中。接下来会对此做简要介绍。Java项目和Scala项目的build.sbt大同小异,只不过Java项目中包含了Junit依赖,而Scala的则是Scalatest。 name := """akkademy-db-java""" version := "1.0" scalaVersion := "2.11.1" libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-actor" % "2.3.6", "com.typesafe.akka" %% "akka-testkit" % "2.3.6" % "test", "junit" % "junit" % "4.11" % "test", "com.novocode" % "junit-interface" % "0.10" % "test" )

要使用Akka,需要添加一条新的依赖。[2]

Java项目的依赖如下: libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-actor" % "2.3.6", "com.typesafe.akka" %% "akka-testkit" % "2.3.6" % "test", "junit" % "junit" % "4.11" % "test", "com.novocode" % "junit-interface" % "0.10" % "test" )

Scala项目的build.sbt如下: name := """akkademy-db-scala""" version := "1.0" scalaVersion := "2.11.1" libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-actor" % "2.3.3", "com.typesafe.akka" %% "akka-testkit" % "2.3.6" % "test", "org.scalatest" %% "scalatest" % "2.1.6" % "test" )

使用%%获取正确的Scala版本

由于Scala的各个主要版本之间并不互相兼容,因此经常会使用多个Scala的版本来构建并发布库。为了能够在项目中通过SBT试图解析出使用正确Scala版本的依赖项,我们可以对build.sbt中声明的依赖稍作修改,在组ID后面使用两个%符号,而不是在库ID中给出Scala版本。

例如,在一个Scala 2.11的项目中,下面代码中的两个依赖是相同的: "com.typesafe.akka" % "akka-actor_2.11" % "2.3.3" "com.typesafe.akka" %% "akka-actor" % "2.3.3"

从Maven Central添加其他依赖

任何Maven依赖都可以添加到build.sbt中。比如http://www. mvnrepository.com中的所有依赖。在这个链接指向的页面上,每个库都有一个sbt的tab,其中包含了添加SBT依赖需要的代码。1.5.2 创建第一个Actor

在本小节中,我们将创建一个Actor,该Actor接收一条消息,将消息中的值存入Map以修改Actor的内部状态。这是我们构建分布式数据库的简单的开始。

首先构造消息

我们将从构造一个SetRequest消息开始编写我们的内存数据库。该消息用于将一个键(String)值(Object)对存储到内存中。我们可以把它看成是在同一个操作中进行插入和更新,类似于Map的Set操作。

要记住的是,Actor需要从其邮箱中获取消息,并查看消息中的操作指示。我们通过消息的类/类型来决定具体的操作。消息类型的内容具体描述了如何实现API协议。在本例中,我们在消息中使用String作为键,Object作为值。

消息必须永远是不可变的,这样可以确保我们和我们的团队不通过多个执行上下文/线程来做一些不安全的操作,从而避免一些奇怪而又出人意料的行为。同样要记住这些消息除了会发送给本地的Actor以外,也可能会发送给另一台机器上的Actor。如果可能的话,把所有值都定义为val(Scala)或final(Java),并且使用不可变集合及类型,比如Google Guava(Java)和Scala标准库所提供的集合及类型。

Java

下面使用Java将SetRequest消息实现为一个不可变对象。这是在Java中实现不可变对象的一种相当标准的方法。任何熟练的Java开发者应该都对此很熟悉。一般来说,我们应该始终在所有代码中优先使用不可变对象。package com.akkademy.messages;public class SetRequest { private final String key; private final Object value; public SetRequest(String key, Object value) { this.key = key; this.value = value; } public String getKey() { return key; } public Object getValue() { return value; }}

Scala

在Scala中,我们有一种更为简洁的方式来定义不可变消息:case class。我们可以通过case class来创建不可变消息,一旦在构造函数中设置属性初值后,之后就只能够读取属性值,不能再进行修改:package com.akkademy.messagescase class SetRequest(key: String, value: Object)

这样消息就定义完成了。

定义Actor收到消息后的响应

既然已经定义好了消息,现在我们就可以创建Actor,并描述Actor接收到消息后如何做出响应。作为例子的开始,在本小节中我们将在响应中做两件事:

1.将消息输出到日志。

2.将任何Set消息中的内容保存起来,以供之后使用。

在后面的章节中,我们将在这个例子的基础上不断完善,支持获取存储的消息,并将这个Actor作为一个线程安全的缓存抽象层来使用(最终实现成一个全功能的分布式键值存储)。

首先让我们看一下使用Java 8实现的Actor。

Java-AkkademyDb.java[3]

下面的代码展示了使用Java实现的Actor收到消息后的响应package com.akkademy;import akka.actor.AbstractActor;import akka.event.Logging;import akka.event.LoggingAdapter;import akka.japi.pf.ReceiveBuilder;import com.akkademy.messages.SetRequest;import java.util.HashMap;import java.util.Map;public class AkkademyDb extends AbstractActor { protected final LoggingAdapter log = Logging.getLogger(context().system(), this); protected final Map map = new HashMap<>(); private AkkademyDb() { receive(ReceiveBuilder .match(SetRequest.class, message -> { log.info("Received Set request: {}", message); map.put(message.getKey(), message.getValue()); }) .matchAny(o -> log.info("received unknown message: {}", o)) .build() ); }}

Actor是一个继承了AbstractActor(Java8的Java Akka API)的Java类。我们在这个类里创建了log和map两个变量,并且声明为protected,这样就可以在本章后面的测试用例中访问这两个变量。

我们在构造函数中调用receive。receive方法接受ReceiveBuilder作为参数,我们连续调用ReceiveBuilder的几个方法,生成最终的ReceiveBuilder。这样就描述了Actor接收到不同类型消息时该如何做出响应。在这里我们定义两种响应,并逐一介绍。

首先,我们定义Actor接收到任何SetRequest消息时做出的响应:match(SetRequest.class, message -> { log.info("Received Set request: {}", message); map.put(message.getKey(), message.getValue());}).

在Java8的API里,ReceiveBuilder的match方法有点儿像case语句,只不过match方法可以匹配类的类型。更正式地来说,这其实就是模式匹配。

在调用的match方法中,我们定义:如果消息的类型是SetRequest.class,那么接受该消息,打印日志,并且将该Set消息的键和值作为一条新纪录插入到map中。

其次,我们捕捉其他所有未知类型的消息,直接输出到日志。 matchAny(o -> log.info("received unknown message"))

Scala-AkkademyDb.scala

由于Scala在语言层面原生支持模式匹配,因此用来实现Actor最合适不过了。现在我们来看一下相应的Scala代码。package com.akkademyimport akka.actor.Actorimport akka.event.Loggingimport scala.collection.mutable.HashMap

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

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载