Scala第二十章节
章节目标
- 理解Akka并发编程框架简介
- 掌握Akka入门案例
- 掌握Akka定时任务代码实现
- 掌握两个进程间通信的案例
- 掌握简易版spark通信框架案例
1. Akka并发编程框架简介
1.1 Akka概述
Akka是一个用于构建高并发、分布式和可扩展的基于事件驱动的应用工具包。Akka是使用scala开发的库,同时可以使用scala和Java语言来开发基于Akka的应用程序。
1.2 Akka特性
- 提供基于异步非阻塞、高性能的事件驱动编程模型
- 内置容错机制,允许Actor在出错时进行恢复或者重置操作
- 超级轻量级的事件处理(每GB堆内存几百万Actor)
- 使用Akka可以在单机上构建高并发程序,也可以在网络中构建分布式程序。
1.3 Akka通信过程
以下图片说明了Akka Actor的并发编程模型的基本流程:
- 学生创建一个ActorSystem
- 通过ActorSystem来创建一个ActorRef(老师的引用),并将消息发送给ActorRef
- ActorRef将消息发送给Message Dispatcher(消息分发器)
- Message Dispatcher将消息按照顺序保存到目标Actor的MailBox中
- Message Dispatcher将MailBox放到一个线程中
- MailBox按照顺序取出消息,最终将它递给TeacherActor接受的方法中
2. 创建Actor
Akka中,也是基于Actor来进行编程的。类似于之前学习过的Actor。但是Akka的Actor的编写、创建方法和之前有一些不一样。
2.1 API介绍
-
ActorSystem: 它负责创建和监督Actor
- 在Akka中,ActorSystem是一个重量级的结构,它需要分配多个线程.
- 在实际应用中, ActorSystem通常是一个单例对象, 可以使用它创建很多Actor.
- 直接使用
context.system
就可以获取到管理该Actor的ActorSystem的引用.
-
实现Actor类
- 定义类或者单例对象继承Actor(注意:要导入akka.actor包下的Actor)
- 实现receive方法,receive方法中直接处理消息即可,不需要添加loop和react方法调用. Akka会自动调用receive来接收消息.
- 【可选】还可以实现preStart()方法, 该方法在Actor对象构建后执行,在Actor生命周期中仅执行一次.
-
加载Actor
- 要创建Akka的Actor,必须要先获取创建一个ActorSystem。需要给ActorSystem指定一个名称,并可以去加载一些配置项(后面会使用到)
- 调用ActorSystem.actorOf(Props(Actor对象), “Actor名字”)来加载Actor.
2.2 Actor Path
每一个Actor都有一个Path,这个路径可以被外部引用。路径的格式如下:
Actor类型 | 路径 | 示例 |
---|---|---|
本地Actor | akka://actorSystem名称/user/Actor名称 | akka://SimpleAkkaDemo/user/senderActor |
远程Actor | akka.tcp://my-sys@ip地址:port/user/Actor名称 | akka.tcp://192.168.10.17:5678/user/service-b |
2.3 入门案例
2.3.1 需求
基于Akka创建两个Actor,Actor之间可以互相发送消息。
2.3.2 实现步骤
- 创建Maven模块
- 创建并加载Actor
- 发送/接收消息
2.3.3 创建Maven模块
使用Akka需要导入Akka库,这里我们使用Maven来管理项目, 具体步骤如下:
-
创建Maven模块.
选中项目, 右键 -> new -> Module -> Maven -> Next -> GroupId: com.itheima ArtifactId: akka-demo next -> 设置"module name"值为"akka-demo" -> finish
-
打开pom.xml文件,导入akka Maven依赖和插件.
//1. 直接把资料的pom.xml文件中的内容贴过来就行了. //2. 源码目录在: src/main/scala下 //3. 测试代码目录在: src/test/scala下. //4. 上述的这两个文件夹默认是不存在的, 需要我们手动创建. //5. 创建出来后, 记得要修改两个文件夹的类型. 选中文件夹, 右键 -> Mark Directory as -> Source Roots //存放源代码. Test Source Roots //存放测试代码.
2.3.4 创建并加载Actor
到这, 我们已经把Maven项目创建起来了, 后续我们都会采用Maven来管理我们的项目. 接下来, 我们来实现:
创建并加载Actor, 这里, 我们要创建两个Actor:
- SenderActor:用来发送消息
- ReceiverActor:用来接收,回复消息
具体步骤
-
在src/main/scala文件夹下创建包: com.itheima.akka.demo
-
在该包下创建两个Actor(注意: 用object修饰的单例对象).
-
SenderActor: 表示发送消息的Actor对象.
-
ReceiverActor: 表示接收消息的Actor对象.
-
-
在该包下创建
单例对象Entrance, 并封装main方法
, 表示整个程序的入口. -
把程序启动起来, 如果不报错, 说明代码是没有问题的.
参考代码
object SenderActor extends Actor {
/*
细节:
在Actor并发编程模型中, 需要实现act方法, 想要持续接收消息, 可通过loop + react实现.
在Akka编程模型中, 需要实现receive方法, 直接在receive方法中编写偏函数处理消息即可.
*/
//重写receive()方法
override def receive: Receive = {
case x => println(x)
}
}
object ReceiverActor extends Actor{
//重写receive()方法
override def receive: Receive = {
case x => println(x)
}
}
object Entrance {
def main(args:Array[String]) = {
//1. 实现一个Actor Trait, 其实就是创建两个Actor对象(上述步骤已经实现).
//2. 创建ActorSystem
//两个参数的意思分别是:ActorSystem的名字, 加载配置文件(此处先不设置)
val actorSystem = ActorSystem("actorSystem",ConfigFactory.load())
//3. 加载Actor
//actorOf方法的两个参数意思是: 1. 具体的Actor对象. 2.该Actor对象的名字
val senderActor = actorSystem.actorOf(Props(SenderActor), "senderActor")
val receiverActor = actorSystem.actorOf(Props(ReceiverActor), "receiverActor")
}
}
2.3.5 发送/接收消息
思路分析
- 使用样例类封装消息
- SubmitTaskMessage——提交任务消息
- SuccessSubmitTaskMessage——任务提交成功消息
- 使用
!
发送异步无返回消息.
参考代码
-
MessagePackage.scala文件中的代码
/** * 记录发送消息的 样例类. * @param msg 具体的要发送的信息. */ case class SubmitTaskMessage(msg:String) /** * 记录 回执信息的 样例类. * @param msg 具体的回执信息. */ case class SuccessSubmitTaskMessage(msg:String)
-
Entrance.scala文件中的代码
//程序主入口. object Entrance { def main(args: Array[String]): Unit = { //1. 创建ActorSystem, 用来管理所有用户自定义的Actor. val actorSystem = ActorSystem("actorSystem", ConfigFactory.load()) //2. 通过ActorSystem, 来管理我们自定义的Actor(SenderActor, ReceiverActor) val senderActor = actorSystem.actorOf(Props(SenderActor), "senderActor") val receiverActor = actorSystem.actorOf(Props(ReceiverActor), "receiverActor") //3. 由ActorSystem给 SenderActor发送一句话"start". senderActor ! "start" } }
-
SenderActor.scala文件中的代码
object SenderActor extends Actor{ override def receive: Receive = { //1. 接收Entrance发送过来的: start case "start" => { //2. 打印接收到的数据. println("SenderActor接收到: Entrance发送过来的 start 信息.") //3. 获取ReceiverActor的具体路径. //参数: 要获取的Actor的具体路径. //格式: akka://actorSystem的名字/user/要获取的Actor的名字. val receiverActor = context.actorSelection("akka://actorSystem/user/receiverActor") //4. 给ReceiverActor发送消息: 采用样例类SubmitTaskMessage receiverActor ! SubmitTaskMessage("我是SenderActor, 我在给你发消