AKKA是JAVA虚拟机JVM平台上构建高并发,分布式和容错应用的工具包。也可以理解成scala语言编写的并发框架。这个应用在spark和flink集群中master和worker通信就是应用这个框架,因此应用还是比较广泛和重要的。因为AKKA不同版本通讯不了,用户使用AKKA和spark中AKKA冲突,spark放弃维护AKKA等原因,在spark2.x后全更改成netty写的了。但这个原理也是按这个原理来写的。
3.1在基于 Actor 的系统里,所有的事物都是 Actor,就好像在面向对象设计里面所有的事物都是对象一样。Actor 模型是作为一个并发模型设计和架构的。
3.2Actor 与 Actor 之间只能通过消息通信,如图的信封。每个Actor他们都有自己的Mailbox与其绑定。绑定是由系统来处理的。
3.3Actor 与 Actor 之间只能用消息进行通信,当一个 Actor 给另外一个 Actor发消息,消息是有顺序的(消息队列),只需要将消息投寄的相应的邮箱即可。
3.4怎么处理消息是由接收消息的Actor决定的,发送消息Actor可以等待回复,也可以异步处理。
3.5ActorSystem 的职责是负责创建并管理其创建的 Actor, ActorSystem 是单例的(可以ActorSystem是一个工厂,专门创建Actor),一个 JVM 进程中有一个即可,而 Acotr 是可以有多个的。
3.6Actor模型是对并发模型进行了更高的抽象。(例如mailbox,dispatcherMessage是看不到的被封装起来了)
3.7Actor模型是异步、非阻塞、高性能的事件驱动编程模型。
3.8Actor模型是轻量级事件处理(1GB 内存可容纳百万级别个 Actor),因此处理大并发性能高。
4.1Actor模型工作机制
4.1.1ActorySystem创建Actor。
4.1.2ActorRef:可以理解成是Actor的代理或者引用。消息是通过ActorRef来发送,而不能通过Actor 发送消息,通过哪个ActorRef 发消息,就表示把该消息发给哪个Actor。
4.1.3消息发送到Dispatcher Message (消息分发器),它得到消息后,会将消息进行分发到对应的MailBox。(注: Dispatcher Message 可以理解成是一个线程池, MailBox 可以理解成是消息队列,可以缓冲多个消息,遵守FIFO)。
4.1.4Actor 可以通过 receive方法来获取消息,然后进行处理。
4.2Actor间传递消息机制
4.2.1每一个消息就是一个Message对象。Message 继承了Runable, 因为Message就是线程类。
4.2.2从Actor模型工作机制看上去很麻烦,但是程序员编程时只需要编写Actor就可以了,其它的交给Actor模型完成即可。
4.2.3A Actor要给B Actor 发送消息,那么A Actor 要先拿到(也称为持有) B Actor 的 代理对象ActorRef 才能发送消息 。
5.1环境准备
pom.xml中
4.0.0
hz.csq.akak
AKKA
1.0-SNAPSHOT
UTF-8
2.11.8
2.11
2.4.17
org.scala-lang
scala-library
${scala.version}
com.typesafe.akka
akka-actor_${scala.compat.version}
${akka.version}
com.typesafe.akka
akka-remote_${scala.compat.version}
${akka.version}
src/main/scala
src/test/scala
net.alchim31.maven
scala-maven-plugin
3.2.2
compile
testCompile
-dependencyfile
${project.build.directory}/.scala_dependencies
org.apache.maven.plugins
maven-shade-plugin
2.4.3
package
shade
*:*
META-INF/*.SF
META-INF/*.DSA
META-INF/*.RSA
reference.conf
com.itstaredu.spark.SparkWorker
5.2.1 需求1:单个actor通讯
package com.csq.akka.actor
import akka.actor.{Actor, ActorSystem, Props}
//1.当继承Actor后就是一个Actor,因此就要重写核心方法Receive
class HelloActor extends Actor{
//1.receive方法会被Mailbox(实现了Runnable接口)调用
//2.当该Actor的Mailbox方法接受到消息后就会调用receive
//3.type Receive = scala.PartialFunction[scala.Any, scala.Unit]
override def receive:Receive = {
// 重写接受消息的偏函数,其功能是接受消息并处理
case "靓仔吃啥!"=>println("阿姨我不饿,就想听你叫一声靓仔!")
case "吃啥!" => println("阿姨,你还没叫我靓仔呢!")
case _ =>{//因为MailBox是runnbale不能自己停,需要代码去停。
println("接收到exit~指令,退出系统.....")
context.stop(self) // 停止自己的actorRef
context.system.terminate() // 关闭ActorSystem
}
}
}
object HelloActorDemo {
//创建 ActorSystem
private val actorFactory = ActorSystem("ActorFactory")
//说明
//1.通过 actorFactory 创建 SayHelloActor 对应的 ActorRef
//2.目的是通过sayHelloActorRef 发送消息
//3.Props[HelloActor] 表示创建的是 HelloActor 这个actor(通过反射机制)
//4.actorFactory.actorOf 方法返回的是 actorRef,可以理解成是actor的代理对象/引用
//4."Actor01" 是actor/actorref的名字,由程序员指定
private val HelloActorRef = actorFactory.actorOf(Props[HelloActor], "Actor01")
def main(args: Array[String]): Unit = {
// 给HelloActorRef发送消息
// !是方法名,可以看下源码
HelloActorRef ! "靓仔吃啥!"
HelloActorRef ! "吃啥!"
//如果不发送exit, Actor就会等待接收消息,而不会退出程序.
HelloActorRef ! "exit"
}
}
结果
小结
a)当程序执行 aActorRef = actorFactory.actorOf(Props[AActor], “aActor”) ,会完成如下任务 [这是非常重要的方法]
b)actorFactory 是 ActorSystem(“ActorFactory”) 这样创建的。
这里的 Props[AActor] 会使用反射机制,创建一个AActor 对象,如果是c)actorFactory.actorOf(Props(new AActor(bActorRef)), “aActorRef”) 形式,就是使用new 的方式创建一个AActor对象, 注意Props() 是小括号。
d)会创建一个AActor 对象的代理对象 aActorRef , 使用aActorRef 才能发送消息
e)会在底层创建 Dispather Message ,是一个线程池,用于分发消息, 消息是发送到对应的Actor的 MailBox会在底层创建AActor 的MailBox 对象,该对象是一个队列,可接收Dispatcher Message 发送的消息
f)MailBox 实现了Runnable 接口,是一个线程,一直运行并调用Actor的receive 方法,因此当Dispather 发送消息到MailBox时,Actor 在receive 方法就可以得到信息.
g)aActorRef ! “吃啥!”, 表示把 "吃啥!"消息发送到A Actor 的mailbox (通过Dispatcher Message 转发)
5.2.2 Actor间的通讯
需求:AActor和BActor的对话
AActor:
package com.csq.akka.actors
import akka.actor.{Actor, ActorRef}
class AActor(actorRef: ActorRef) extends Actor {
val bActorRef: ActorRef = actorRef
override def receive: Receive = {
case "姐姐" => {
println("AActor:姐姐这个菜真好吃!")
bActorRef ! "AActor:姐姐这个菜真好吃!" //给BActor 发出消息
}
case "真可爱!那是我女儿和你差不多大,你该叫我啥!" => {
//给BActor 发出消息 谁主动发消息就必须要有持有对方的引用
//这里需要持有BActor的引用(BActorRef)这个不是自己的是传进来的,因此需要构建这个actor时就要传进来。
println("AActor:妈妈耶!")
Thread.sleep(1000)
bActorRef ! "妈妈耶!" //给BActor 发出消息
}
}
}
BActor
package com.csq.akka.actors
import akka.actor.Actor
class BActor extends Actor{
override def receive:Receive = {
case "AActor:姐姐这个菜真好吃!" => {
println("BActor:真可爱!那是我女儿和你差不多大,你该叫我啥!")
Thread.sleep(1000)
//通过sender() 可以获取到发现消息的actor的ref
sender() ! "真可爱!那是我女儿和你差不多大,你该叫我啥!"
}
case "AActor:姐姐这个菜真好吃!" => {
println("BActor:真可爱!那是我女儿和你差不多大,你该叫我啥!")
Thread.sleep(1000)
//通过sender() 可以获取到发现消息的actor的ref
sender() ! "真可爱!那是我女儿和你差不多大,你该叫我啥!"
}
case "妈妈耶!" => {
println("BActor:你脸皮真厚,还是叫姐姐吧!")
Thread.sleep(1000)
//通过sender() 可以获取到发现消息的actor的ref
sender() ! "姐姐"
}
}
}
ActorGame
package com.csq.akka.actors
import akka.actor.{ActorRef, ActorSystem, Props}
object ActorGame extends App {//不写main函数的话,可以继承App
//创建ActorSystem
val actorfactory = ActorSystem("actorfactory")
//先创建BActor引用/代理
val bActorRef: ActorRef = actorfactory.actorOf(Props[BActor], "bActor")
//创建AActor的引用
val aActorRef: ActorRef = actorfactory.actorOf(Props(new AActor(bActorRef)), "aActor")
//aActor发起
aActorRef ! "姐姐"
}
结果
小结
a)两个Actor通讯机制和Actor 自身发消息机制基本一样,只是要注意如下
b)如果A Actor 在需要给B Actor 发消息,则需要持有B Actor 的 ActorRef,可以通过创建时,传入 B Actor的 代理对象(ActorRef)
c)当B Actor 在receive 方法中接收到消息,需要回复时,可以通过sender() 获取到发送Actor的 代理对象。
如何理解Actor 的receive 方法被调用?
a)每个Actor对应MailBox
b)MailBox 实现了Runnable 接口,处于运行的状态
c)当有消息到达MailBox,就会去调用Actor的receive方法,将消息推送给receive