谈到Akka就必须介绍Actor并发模型,而谈到Actor就必须看一篇叫做《A Universal Modular Actor Formalism for Artificial Intelligence 》的论文,它最早发表于1973年,提出了一种并发计算的理论模型,Actor就源于该模型。
在Actor模型中,actor是一个并发原语,简单的说,一个actor就是一个工人,与进程或线程一样都能够工作或处理任务。其实这还有点不好理解,我们可以把它想象成面向对象编程语言中的一个对象实例。在OOP中一个对象可以访问或修改另一个对象的属性,也可以直接调用另一个对象的方法。例如下图,person1给person2发送了一个消息,直接调用方法就行了。深入底层执行逻辑的话,结果就是JVM转到sayHello的代码区,一步步执行。
public class HelloWorld { private String name = ""; public HelloWorld(String name){ this.name = name; } public String getName(){ return this.name; } public void sayHello(HelloWorld to, String msg){ System.out.println(to.getName()+" 收到 "+name+" 的消息:"+ msg); } } public class OOPInvoke { public static void main( String[] args ) { HelloWorld person1 = new HelloWorld("Person1"); HelloWorld person2 = new HelloWorld("Person2"); person1.sayHello(person2,"Hello world"); } }
sayHello在一个线程中执行基本没有问题,但是多个线程执行时,就可能出问题了,因为在执行sayHello的时候person2的name值可能被其他线程修改。这是一个name字段,意外修改没有关系,但如果是一个金额字段呢?
actor和对象的不同之处在于,actor的状态不能直接读取、修改,actor的方法不能直接调用。actor只能通过消息传递的方式与外界通信。每个对象都有一个this指针,代表对象的地址,可以通过该地址调用方法或存取状态;与此类似,actor也有一个代表本身的地址,但只能向该地址发送消息。
简单点说,actor通过消息传递的方式与外界通信。消息传递是异步的。每个actor都有一个邮箱,该邮箱接收并缓存其他actor发过来的消息,actor一次只能同步处理一个消息,处理消息过程中,除了可以接收消息,不能做任何其他操作。前面这段话,我加粗、倾斜、加下划线、还用红色字体标出,你就应该知道有多重要了,这就是actor模型的本质。
Actor模型的另一个好处就是可以消除共享状态,因为它每次只能处理一条消息,所以actor内部可以安全的处理状态,而不用考虑锁机制。标红的这两句话如果可以理解透彻,基本上Actor模型的精髓你就掌握了。
那么读者可能会问,每次只处理一个消息,这不是会严重的影响性能么?废话,一次处理一个消息当然影响性能了。不过,如果你恰当的运用Akka和Actor模型,完全可以不必关心性能的问题。下面是Actor模型的几个基本原则:
(1)所有的计算都是在actor中执行的
(2)actor之间只能通过消息进行通信交流
(3)为了响应消息,actor可以进行下列操作
a. 更改状态或行为
b. 发消息给其他actor
c. 创建有限数量的子actor
看了上面几个基本原则,你是不是更加鉴定的认为Actor模型没啥用?嗯,这就对了,因为我当初也是这么认为的。一次处理一个消息,没有并发,怎么提高性能;如果actor只能更改状态或行为,发消息给其他actor,创建有限数量的子actor,那我的业务逻辑在哪里;actor之间只能通过消息通信,我怎么知道另外一个actor的地址。其实吧,如果你能问到这几个问题,那么恭喜你,你非常需要我这个博客系列,我会一一进行分析,把我之前的坑展示给你看,以确保你不会再掉进去。
其实Actor模型出现的很早,而20世纪80年代,爱立信在Erlang中实现了Actor模型,用于嵌入式电信应用程序。该实现中引入了监督机制提供的容错性概念。爱立信使用Erlang和Actor模型实现了一款日后经常被提及的应用:AXD301。这玩意儿能提供99.9999999% 的可用性,看到没,7个9!!!绝对可以亮瞎人们的狗眼,这意味着在100年的时间中,AXD301只有3.1秒的时间会宕机。
Actor模型的另一个重要的特性就是容错,它通过监督机制提供容错。这跟java中的throw exception有点类似,都是把处理响应错误的责任交给出错对象以外的实体。但在java中如果一个程序或者线程抛出了一个异常,你敢放心的恢复对应的程序或线程吗?你确保恢复之后还能正常的运行吗,毕竟需要很多资源需要重新创建。但Actor模型可以!
如上图所示actor之间是有层级关系的,子actor如果出现了异常会抛给父actor,父actor会根据情况重新构建子actor,子actor从出现异常,到恢复之后正常运行,这段时间内的所有消息都不会丢失,等恢复之后又可以处理下一个消息。也就是说如果一个actor抛出了异常,除了导致发生异常的消息外,任何消息都不会丢失。这容错性当然好了。当然了,为了实现这种特性,akka或者Erlang需要做很多工作的。
Akka中的Actor模型还有另外一个比较重要的两个特性:分布式与位置透明性。其实可以认为这是一个特性。Actor模型中一个很重要的概念就是actor地址,因为其他actor需要通过这个地址与actor进行通信。akka考虑到分布式的网络环境,对actor地址进行了抽象,屏蔽了本地地址和远程地址的差异,对于开发者来说基本上是透明的。由于actor地址是透明的,那么akka有引入了集群。当然了基于Actor模型和位置透明性,Akka还有其他很多有用的组件,这里就不介绍了,后面会详细说明。
关于Actor模型就先介绍到这里,下一章节我们会介绍Actor模型的最基本的设计模式,以说明该模型的适用场景。