作者:HelloGitHub-Salieri
HelloGitHub 推出的《讲解开源项目》系列。
Akka is a toolkit for building highly concurrent, distributed, and resilient message-driven applications for Java and Scala.
上面这段文字摘抄自 Akka 官网(akka.io),翻译成中文也就是:“Akka 是一个为 Java 和 Scala 构建高并发、分布式和弹性消息驱动应用程序的工具包”。而 Akka 具有的一切特性,其实都源自于一个用于处理并发计算问题的模型——Actor 模型。
PowerJob 项目地址:
https://github.com/KFCFans/PowerJob
一、Actor 模型Actor 模型在 1973 年于 Carl Hewitt、Peter Bishop 及 Richard Steiger 的论文中提出,现在已经被用作并发计算的理论理解框架和并发系统的实际实现基础。
在计算机科学中,Actor 模型是一种并发运算上的模型。Actor 是一种程序上的抽象概念,被视为并发运算的基本单元:当一个 Actor 接收到一则消息,它可以做出一些决策、创建更多的 Actor 、发送更多的消息、决定要如何处理接下来的消息。Actors 可以修改它们自己的私有状态,但是只能通过消息间接的相互影响(避免了基于锁的同步)。
每一个 Actor 都由状态(State)、行为(Behavior)和邮箱(MailBox,其实就是一个消息队列)三部分组成:
状态:Actor 中的状态指 Actor 对象的变量信息,状态由 Actor 自己管理,避免了并发环境下的锁和内存原子性等问题。
行为:Actor 中的计算逻辑,通过 Actor 接收到的消息来改变 Actor 的状态。
邮箱:邮箱是 Actor 和 Actor 之间的通信桥梁,邮箱内部通过 FIFO(先入先出)消息队列来存储发送方 Actor 消息,接受方 Actor 从邮箱队列中获取消息。
前面说了一大堆晦涩难懂的概念,相信大家看的也都云里雾里的。这里结合我自己的理解用白话文讲一下:其实 Actor 模型的设计思想就是事件驱动,可以简单理解为线程级的消息中间件。所有 Actor 之间不共享数据,只通过消息沟通,因此不用关心传统并发程序编写过程中的并发安全问题(因为根本没有共享的数据)。同时,得益于 Actor 底层轻巧的设计(这部分其实属于具体实现了,不过目前所有的实现 Actor 设计都很轻量),使得单机可以存在百万量级的 Actor,因此能够带来极好的并发性能。
此外,由于 Actor 模型中万物都是 Actor,所以它是天然支持分布式的,即不同机器之间的 Actor 通讯和本地 Actor 之间的通讯没有实质上的区别。
因此,只要你掌握了事件驱动的编程思想,利用 Actor 模型,结合具体的实现框架(比如 JVM 系的 Akka),能够轻松编写出高性能的分布式应用。
二、Akka ToolkitsAkka Toolkit 也就是 Akka 工具包,其实就是 JVM 平台上对 Actor 模型的一种实现。Akka 本身提供了完整的 Actor 模型支持,包括对并发/并行程序的简单的、高级别的抽象、异步、非阻塞、高性能的事件驱动编程模型和非常轻量的事件驱动处理。同时,作为一个“工具包”,Akka 还额外提供了许多功能,由于篇幅有限,这里就简单介绍几个包,有兴趣可以前往官网(见参考文档)详细了解~
akka-streams:流处理组件,提供直观、安全的方式来进行异步、非阻塞的背压流处理。
akka-http:HTTP 组件,现代、快速、异步、流媒体优先的 HTTP 服务器和客户端。
akka-cluster:集群组件,包括集群成员管理、弹性路由等。
akka-remote(artery-remoting):通讯组件,也是 PowerJob 所使用的核心组件,然而官网并不推荐直接使用(直接使用 remote 启动还会警告使用了过于底层的 API),普通分布式应用推荐直接使用 cluster。
akka-persistence:持久化组件,提供“至少投递一次”的能力来保证消息的可靠送达。
三、Akka 简单使用接下来是关于 Akka 的一个超简明教程,帮助大家初步理解并入门 Akka,其内容涵盖了所有 PowerJob 中用到的 API,也就是说,看懂这部分,源码中的 Akka 就不再可怕喽~
3.1 开发 Actor首先,不得不提的一点是,Akka 从 2.6 版本开始,维护了 2 套 API(算上 Scala 和 Java 版本就 4 套了...看着IDE的智能提示就头大...),分别叫 classic 和 typed。typed 与原先的 classic 相比,最大的特色就是其具有了类型(Java 范型)。每一个 Actor 处理的消息类型可以直接由范型规定,从而有效限制程序 bug(将错误从运行期提前到了编译期)。然而,对于复杂系统要处理的消息不胜枚举,强类型就限制了一个 Actor 只能处理一种类型的消息。虽然从逻辑上来讲确实清晰,但实际工程实现中,必然导致代码阅读困难,整体结构松散(个人感觉这一点也是计算机科学与工程之间存在分歧的表现,当然也可能是我学艺不精,不了解正确的用法)。解释了那么多,终于可以点明主旨了~作者比较喜欢 classic,因此 PowerJob 只使用 AKKA classic API,本文也只涉及 AKKA classic API,反正官网说了会长期维护~