1.4 纵向扩展和横向扩展

本节中我们简要看看消息传递的风格如何提供了纵向扩展和横向扩展的好处。 在我们的卖票例子中,纵向扩展表示在我们单台服务器上运行更多的 TicketingAgents, 横向扩展表示在大量机器上启动 TicketingAgents。

** 横向扩展

虽没有明确说明,目前位置我们看到的代理和打印室都是在一台机器上,一个 JVM 上。 那怎么横向扩展到多台机器呢?

消息传递风格使用地址来通信,所以我们要做的改动只是地址如何链接到 actors。 如果工具箱把地址是本地的还是远程的处理好了,我们可以通过配置地址如何解析来扩展方案。 图 1.9 展示了 Akka 工具箱如何解决这个问题。

Figure 1.9 透明的远程 Actors(通过网关)

Akka 提供了它称之为远程 Actors (我们在第 5 章中会讨论)的概念来使我们寻求的透明成为可能。 Akka 传递消息给远程 actor 时,把消息传到那个 actor 所在的机器,然后再把结果通过网络传回。 TicketingAgent 不知道自己刚才通信的是远程的打印室。 方案几乎可以与之前全在内存中的例子一样。 唯一需要改变的是,如何查询对远程 actors 的引用,这可以通过修改配置文件达到,后边会看到。 代码完全一样。 这意味着我们总可以从纵向扩展转换到横向扩展,不改变一行代码。

Akka 中大量利用了地址解析的灵活性,这贯穿于本书。 远程 actor, 集群,甚至测试工具箱都使用了地址解析的灵活性。

共享可变状态的例子利用了锁,锁只能用在一个虚拟机内。 那横向扩展,使得这个例子可以在多台机器上工作有多难?JVM 中没有特性可以直接用来达到这一目标。 最常见的办法是从 JVM 中取出所有状态,全部放到数据库中。

如果我们走了数据库路线,代码就得彻底改变。 最大的问题是,在实行更多控制的要求下,数据的存储变得重要,中间层会崩溃,类只是 DTO,或没有实际行为的无状态前端。 真正黑暗的是,如果还有更多复杂行为,也需要数据库能处理,真是个灾难。 而且,我们系统的透明性,以及可以很容易地跟踪流,变成行为与复杂的控制机制混在一起的各种补丁。 在某些架构中,消息队列和 web services 会防止这一点,结果是一个具有 Akka 各种好处的模型,除了将他们抽取到应用层中,应用层中的代码为多个目的服务:业务需求和通信机制。 建立在消息上,而不是共享可变状态,并可以透明地远程连接,Akka 将我们从痛苦的不确定性中解救,使得我们可以从一套代码中纵向扩展或横向扩展。

** 纵向扩展 如果我们只想增加单台机器上的性能,即纵向扩展,该怎么办?设想我们升级了机器上 CPU 内核的个数。 在共享可变状态例子中,我们可以增加代理所在线程的数目。 但是如我们看到的,锁导致了竞争,这意味着任一时刻工作的线程数比总数少,因为有一些会相互等待。 尽量少地共享意味着锁会尽可能地少,这正是消息传递方法的目标。

使用消息传递方法,代理和打印室可以运行在较少的线程上,只要工具箱优化了消息的处理和分派,性能就会超过使用锁的版本。 每个线程都有个栈来存储运行时数据。 栈的大小根据操作系统有所不同,例如在 Linux x64 上一般是 256kB。 栈的大小是限制一台机器上同时运行的线程数目的因素之一。 Linux x64 平台上,1GB 内存可以跑 4096 个线程。

Actors 运行在称为分派者(dispatcher)的抽象层上。 分派者操心使用哪个线程模型,并处理信箱。 类似线程池的工作方式,只是基于极端重要的消息传递方案之上,线程池处理任务调度,Akka 的分派者/信箱会处理线程模型(TODO)和消息。 最好的地方是,可以通过配置层来映射分派策略,意味着我们可以不改变代码就能改变分派策略。 在后续章节中,我们会看到配置 Akka 很简单,使得性能调整轻而易举。

Actors 是轻量级的,因为它们运行在分派者之上,actors 不一定与线程数成比例。 Akka actors 占用空间比线程少的多,大约270个 actor 占用 1GB 内存。 与 4096 个线程相比,这是个很大的差别,这意味着,相对直接使用线程,你可以更自由的创建各种类型的 actors。 根据特定的需求,可以选择不同类型的分派者。 可以在多个 actors 之间共用一个分派者,或者不同的 actors 使用不同的分派者。 性能调整时,允许配置和调整整个应用使用的分派者和邮箱,提供了极大的灵活性。

既然我们认识到了消息传递方法带给并发,容错和扩展的好处,该来看一些细节:Akka 使用的具体组件及这些组件如何一起工作来提供消息传递工具箱。