一、Akka简介
Akka是在JVM平台上构建的高并发、分布式和容错应用的工具,也可以理解成是编写并发程序的框架,它用Scala语言写成,主要解决的问题是:轻松写出高效稳定的并发程序,使用者不再过多的考虑线程、锁和资源竞争等细节。
https://akka.io 官网截图
二、Actor模型
处理并发问题关键是要保证共享数据的一致性和正确性,因为程序是多线程时,多个线程对同一个数据进行修改,若不加同步条件,势必会造成数据污染。但是当我们对关键代码加入同步条件synchronized 后,实际上大并发就会阻塞在这段代码,对程序效率有很大影响。若是用单线程处理,不会有数据一致性的问题,但是系统的性能又不能保证。Actor 模型的出现解决了这个问题,简化并发编程,提升程序性能。
三、Akka中的Actor模型
3.1 Actor模型及其说明
Akka 处理并发的方法基于 Actor 模型(如上图)。
在基于 Actor 的系统里,所有的事物都是 Actor,就好像在面向对象设计里面所有的事物都是对象一样。
Actor 模型是作为一个并发模型设计和架构的。Actor 与 Actor 之间只能通过消息通信,如图的信封(MailBox)。
Actor 与 Actor 之间只能用消息进行通信,当一个 Actor 给另外一个 Actor发消息,消息是有顺序的(消息队列),只需要将消息投寄的相应的邮箱即可。
怎么处理消息是由接收消息的Actor决定的,发送消息Actor可以等待回复,也可以异步处理【类似于ajax】
ActorSystem 的职责是负责创建并管理其创建的 Actor, ActorSystem 是单例的(可以ActorSystem是一个工厂,专门创建Actor),一个 JVM 进程中有一个即可,而 Actor 是可以有多个的。
Actor模型是对并发模型进行了更高的抽象。
Actor模型是异步、非阻塞、高性能的事件驱动编程模型。[案例: 说明 什么是异步、非阻塞, 最经典的案例就是ajax异步请求处理 ]
Actor模型是轻量级事件处理(1GB 内存可容纳百万级别个 Actor),因此处理大并发性能高.
3.2 Actor模型工作机制说明
Actor模型的工作机制:
ActorySystem创建Actor
ActorRef:可以理解成是Actor的代理或者引用。消息是通过ActorRef来发送,而不能通过 Actor 发送消息,通过哪个ActorRef 发消息,就表示把该消息发给哪个Actor
消息发送到Dispatcher Message (消息分发器),它得到消息后,会将消息进行分发到对应的MailBox。(注: Dispatcher Message 可以理解成是一个线程池, MailBox 可以理解成是消息队列,可以缓冲多个消息,遵守FIFO)
Actor 可以通过 receive方法来获取消息,然后进行处理。
Actor模型的消息机制:
每一个消息就是一个Message对象。Message 继承了Runable, 因为Message就是线程类。
从Actor模型工作机制看上去很麻烦,但是程序员编程时只需要编写Actor就可以了,其它的交给Actor模型完成即可。
A Actor要给B Actor 发送消息,那么A Actor 要先拿到(也称为持有) B Actor 的 代理对象ActorRef 才能发送消息
四、Akka 中的 Dispatchers
Akka 中的 Dispatcher ,是维持Akka Actor动作的核心组件,是整个Akka框架的引擎。它是基于Java的Executor框架来实现的,Dispatcher 控制和协调消息并将其分发给运行在底层线程上的Actor,由它来负责调度资源的优化,并保证任务以最快的速度执行,用一句话来说,Dispatcher 负责 Akka 中的资源调度功能。
跟线程池一样,不自己创建Dispatcher时使用的是默认Dispathcer,如果任务中有些耗时任务,容易将默认线程池打满,影响其他任务的调度。
Dispatcher一共有四种,分别是:
(1) Dispatcher
Dispatcher是Akka中默认的派发器,它是基于事件的分发器,该派发器绑定一组Actor到线程池中。该派发器有如下特点:
每一个Actor都有自己的邮箱
该派发器都可以被任意数量的Actor共享
该派发器可以由ThreadPoolExecutor或ForkJoinPool提供支持
该派发器是非阻塞的。
(2) Balancing Dispatcher
该派发器是基于事件的分发器,它会将任务比较多的Actor的任务重新分发到比较闲的Actor上运行。该派发器有如下特点:
所有Actor共用一个邮箱
该派发器只能被同一种类型的Actor共享
该派发器可以由ThreadPoolExecutor或ForkJoinPool提供支持
(3) Pinned Dispatcher
该派发器为每一个Actor提供一个单一的、专用的线程。这种做法在I/O操作或者长时间运行的计算中很有用。该派发器有如下特点:
每一个Actor都有自己的邮箱
每一个Actor都有专用的线程,该线程不能和其他Actor共享
该派发器有一个Executor线程池
该派发器在阻塞上进行了优化,如:如果程序正在进行I/O操作,那么这个Actor将会等到任务执行完成。这种阻塞型的操作在性能上要比默认的Dispatcher要好。
(4) Calling Thread Dispatcher
该派发器主要用于测试,并且在当前线程运行任务,不会创建新线程,该派发器有如下特点:
每一个Actor都有自己的邮箱
该派发器都可以被任意数量的Actor共享
该派发器由调用线程支持
关于dispatcher的更多介绍可参考官网介绍:https://doc.akka.io/docs/akka/current/typed/dispatchers.html
五、Akka 中的 Router
当处理到来的消息流时,我们需要一个actor来引导消息路由到目标actor,从而提高消息的分配效率。在Akka中这个 actor就是Router。它所管理的一些目标actor叫做routees,Akka定义好的一些Router:
akka.routing.RoundRobinRouter:轮转路由器将消息按照轮转顺序发送给routers
akka.routing.RandomRouter:随机路由器随机选择一个router,并将消息发送给这个router
akka.routing.SmallestMailboxRouter:最小邮箱路由器会在routers中选择邮箱里信息最少的router,然后把消息发送给它。
akka.routing.BroadcastRouter:广播路由器将相同的消息发送给所有的routers
akka.routing.ScatterGatherFirstCompletedRouter:敏捷路由器先将消息广播到所有routers,返回最先完成任务的router的结果给调用者。
创建router actor 有两种方式:
Pool(池)——routees都是router 的子actor,如果routees终止,router将把它们移除,pool方式创建Router:
def main(args: Array[String]): Unit = { // 创建router val actorSystem = ActorSystem("testRouter") // 通知代码来实现路由器 val poolRouter = actorSystem.actorOf(RoundRobinPool(5).props(Props[WorkerRoutee]),"router") hahaRouter ! RouteeMsg(333) val myRouter = actorSystem.actorOf(Props[WorkerRoutee].withRouter(RoundRobinPool(nrOfInstances = 5))) myRouter ! RouteeMsg(22) val masterRouter = actorSystem.actorOf(Props[MasterRouter],"masterRouter") masterRouter ! RouteeMsg(100)}
Group(群组)——routees都创建在router的外部,router通过使用actor来选择将消息发送到指定路径,但不监管routees是否终止。Router actor 向 routees 发送消息,与向普通actor发送消息一样通过其ActorRef。Router actor 不会改变消息的发送人,routees 回复消息时发送回原始发件人,而不是Router actor。
import akka.actor._import akka.routing.{ RoundRobinGroup}object HelloScala { def main(args: Array[String]): Unit = { val _system = ActorSystem("AkkaTestActor") val tActor = _system.actorOf(Props[TestActor],"testActor") tActor ! RouteeMsg(13333) }}class TestActor extends Actor{ val routee1 = context.actorOf(Props[WorkerRoutee],"w1") val routee2 = context.actorOf(Props[WorkerRoutee],"w2") val routee3 = context.actorOf(Props[WorkerRoutee],"w3") val paths: Array[String] = Array(routee1.path.toString,routee2.path.toString,routee3.path.toString) val testRouter = context.actorOf(RoundRobinGroup(paths).props(),"testRouter") override def receive = { case RouteeMsg(s) => testRouter ! RouteeMsg(s) case _ => }}// 定义routee对应的actor类型case class RouteeMsg(s: Int)class WorkerRoutee extends Actor{ override def receive: Receive = { case RouteeMsg(s) => println(s"${self.path} mesage#$s") val caleActor = context.actorOf(Props[Cale]) caleActor ! RouteeMsg(s) case _ => println(s"${self.path}") }}class Cale extends Actor{ override def receive: Receive = { case RouteeMsg(s) => println(s"${self.path} message#$s") case _ => println(s"${self.path}") }}
上面几部分,简单介绍了Akka 模型中的几个重要组成部分以及实例用法,当然akka的用法也不仅仅如此,akka内部的设计构造也是非常复杂的,更详细的资料可以查询官网文档https://doc.akka.io/docs。