前言:
Scala的Actor有点类似于Java中的多线程编程。但是不同的是,Scala的Actor提供的模型与多线程有所不同。Scala的Actor尽可能地避免锁和共享状态,从而避免多线程并发时出现资源争用的情况,进而提升多线程编程的性能。
Spark中使用的分布式多线程框架,是Akka,是Scala的一种多线程的类库。Akka也实现了类似Scala Actor的模型,其核心概念同样也是Actor。Scala Actor模型已经在2.1.0的时候还在用,但是在2.1.1的时候已经被遗弃了,Spark开始转换用AKKA来替代Scala Actor,但是Scala Actor概念和原理都还是相同的。所以学习Scala Actor对我们学习AKKA,Spark还是有所帮助的
另外值得注意的是Spark的源码在底层消息传递机制上大量使用AKKA的传送机制。
代码运行环境maven依赖:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.11.8</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.7.6</version>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-actors</artifactId>
<version>2.11.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.typesafe.akka/akka-actor -->
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.11</artifactId>
<version>2.3.16</version>
</dependency>
</dependencies>
一、Actor
1、单向打招呼实例
Scala提供了Actor trait让我们更方便地进行actor多线程编程,Actor
trait就类似于Java中的Thread和Runnable一样,是基础的多线程基类和接口。我们只要重写Actor trait的act方法,即可实现自己的线程执行体。
使用start()方法启动actor;使用!符号,向actor异步发送消息;actor内部使用receive和模式匹配接收消息
?:
import scala.actors.Actor
object ActorEasyOps {
def main(args: Array[String]) {
val demo = new SayHello
demo.start()
demo ! "张三"
}
}
class SayHello extends Actor{
override def act(): Unit = {
while(true){
receive {
case name:String =>{
println("hello:"+name)
}
case _ => println("你好啊~")
}
}
}
}
运行结果:
2、case class单向Actor实例
Scala的Actor模型与Java的多线程模型之间,很大的一个区别就是,Scala
Actor天然支持线程之间的精准通信;即一个actor可以给其他actor直接发送消息。这个功能是非常强大和方便的。
要给一个actor发送消息,需要使用“actor ! 异步消息”的语法。在scala中,一般都是使用样例类进行消息的发送,即case class来作为消息进行发送。然后在actor接收消息之后,可以使用强大的模式匹配去匹配不同的消息。
?:
import scala.actors.Actor
/**
* 强调:
* 在scala编程中,一般都使用case class来携带数据,
* 所以一般通信的时候,使用case class作为协议信息,
* 上一个案例我们就可以将普通的scala类型转化为case class
*/
object SayHelloCaseClassOps {
def main(args: Array[String]) {
val sayBye=new SayByeActor
sayBye.start()
sayBye ! Greet("zhangsan")
sayBye ! ByeBye("张三","再见啦~")
}
}
class SayByeActor extends Actor{
override def act(): Unit = {
while(true){
receive{
case ByeBye(name,contend) => println(name,contend)
case Greet(name) => println("hello~"+name)
case _ => println("nothing")
}
}
}
}
//创建两个样例类
case class ByeBye(name:String,contend:String)
case class Greet(name:String)
3、双向通讯Actor
两个Actor之间相互发送消息,一个发送并接收,然后再发送给另一个,另一个在接收消息。
?:
import scala.actors.Actor
/**
* 场景:
* 模拟的是公司晨会
*/
object _03CommunicationActorOps {
def main(args: Array[String]) {
val pActor = new ProgrammerActor
val mActor = new ManagerActor(pActor)
pActor.start()
mActor.start()
val meeting = "~每日晨会~"
mActor ! meeting
}
}
case class MorningGreet(name:String,greet:String)
case class WorkingInfo(content:String)
case class ManagerActor(pActor:ProgrammerActor) extends Actor{
override def act(): Unit = {
while(true){
receive{
case meeting:String =>{
println("开会通知:"+meeting)
//向同事们问候
pActor ! MorningGreet("hello","大家早上好")
}
case MorningGreet(name,greet) => {
//开会内容是什么
println("程序猿说:"+name+","+greet)
pActor ! WorkingInfo("每个人请报一下昨天工作的内容~")
}
}
}
}
}
case class ProgrammerActor() extends Actor{
override def act(): Unit = {
while(true){
receive{
case MorningGreet(name,greet) =>{
println("项目经理说:"+name+","+greet)
//向manager也礼貌性的回复一下 此时的sender就是MorningGreet消息的来源
sender ! MorningGreet("领导好","才是真的好")
}
case WorkingInfo(content) =>{
println(content)
}
}
}
}
}
运行结果:
二、Akka
1、引言:
随着计算机硬件技术和网络技术的飞速发展,计算机拥有越来越多数量的内核,分布式技术和集群技术的成熟使得一个应用程序可以被分块运行在多个独立的计算机上(可能安装不同的操作系统),这些技术使得程序可以真正的并行运行。但是,我们相信编写出正确的具有容错性、可扩展性和跨平台的并发程序太困难了。这多数是因为我们使用了错误的工具和错误的抽象级别。AKKA就是为了改变这种状况而生的。通过使用Actor模型我们提升了抽象级别,为构建正确的可扩展并发应用提供了一个更好的平台。在容错性方面我们采取了“let it crash”(让它崩溃)模型,人们已经将这种模型用在了电信行业,构建出“自愈合”的应用和永不停机的系统,取得了巨大成功。Actor还为透明的分布式系统以及真正的可扩展高容错应用的基础进行了抽象。AKKA是基于SCALA的软件框架,SCALA可以运行在JVM上,并且可以完全使用JAVA的类库,这样AKKA也拥有了JAVA跨平台的能力。
2、什么是Akka
Akka 是一个用 Scala 编写的库,用于简化编写容错的、高可伸缩性的 Java 和 Scala 的 Actor 模型应用。它已经成功运用在电信行业、银行和互联网行业。系统几乎不会宕机(高可用性 99.9999999 % 一年只有 31 ms 宕机)。
AKKA 是一款基于actor模型实现的并发处理框架。基于事件驱动的并发处理模型,每一个actor拥有自己的属性和操作,这样就避免了通常情况下因为多个线程之间要共享属性(数据)而是用锁机制的处理。这种机制在scala,cloure语言中应用的很好,将操作和属性放在一个独立的单元中进行处理,从而提高并发处理的能力。
3、Akka特点
AKKA实现了一个独特的混合
角色
角色给你提供了:
并发与并行性的高等级抽象
异步、无锁以及高性能的事件驱动编程模型
非常轻量级的事件驱动流程(1GB堆内存可容纳几百万的角色)
容错
拥有“让它崩溃”语义的管理层级
管理层级可跨越多个JVM,实现真正的容错系统
非常适合编写可自我修复且永不停机的高容错能力的系统
Akka 提供可伸缩的实时事务处理能力。
Akka在以下方面提供了一致的运行时和编程模型:
纵向扩展性(并发)
横向扩展性(远程调用)
容错性
4、Akka试用场景
Akka被很多大型组织在不同的领域所采用,如:
· 投资和商业银行业务
· 零售业
· 社交媒体
· 仿真
· 游戏和赌博
· 汽车和交通系统
· 医疗卫生
· 数据分析
. 互联网
等等。
任何系统只要是需要高吞吐量或低延迟率的都可以考虑使用Akka。
5、Akka模块说明
AKKA是调度模块化的,它由许多拥有不同特性的JAR组成。
akka-actor – 经典角色、类型角色、IO角色等。
akka-agent – 代理、整合了Scala的STM特性
akka-camel – 整合Apache的Camel
akka-cluster – 集群成员管理、弹性路由 akka-kernel –
AKKA微内核,运行着一个极简应用服务器
akka-osgi – 在OSG容器里使用AKKA的基本bundle,包括akka-actor的类
akka-osgi-aries – Aries——服务提供角色系统的蓝图
akka-remote – 远程角色 akka-slf4j –
SLF4J Logger (事件总线监听器)
akka-testkit – 测试角色系统的工具包Toolkit for testing
Actor systems akka-zeromq – 整合ZeroMQ
除了这些已进入稳定内核的稳定模块之外,还有许多标记为“试验(experimental)”的模块。这并不是意味着它们没有达到预期的功能,它的主要意义是它些模块的API尚不够稳定。
6、代码演示
6.1Akka消息本地传递
原理:
学生首先创建了一个ActorSystem;
学生通过ActorSystem创建一个叫ActorRef的对象,QuoteRequest消息就是发送给ActorRef的(实际上就相当于TeacherActor的一个代理);
ActorRef将消发送给Dispatcher;
Dispatcher将消息投递到目标Actor的邮箱中;
随后Dispatcher将MailBox扔给一个线程去执行(下一节重点介绍);
MailBox将消息出队并最终将其委托给真实的TeacherActor的接受方法去处理。
?:
import akka.actor.{Actor, ActorSystem, Props}
/**
* akka actor入门案例之单向通信
*/
object _04AkkaActorOps {
def main(args: Array[String]) {
val actorSystem = ActorSystem.create("teacher-actor-system")
val teacherRef=actorSystem.actorOf(Props(classOf[TeacherActor]))
teacherRef ! Request("老师,请问学scala难么?")
//2s后actorSystem自动关闭
Thread.sleep(2000)
actorSystem.shutdown()
}
}
case class Request(question:String)
class TeacherActor extends Actor{
override def preStart(): Unit ={
println("actor执行之前被调用")
}
override def receive ={
case Request(question) =>{
println("学生问的问题是"+question)
}
}
override def postStop(): Unit ={
println("actor执行完成后被调用")
}
}
运行结果:
6.2本地Akka与Actor之间通信
原理:
上图说明该段代码需要做的事情,为了简单点,图中没有画出ActorSystem、Dispatcher和MailBox。
DriverApp将一条InitSignal消息发送给StudentActor;
StudentActor响应InitSignal并将一条QuotRequest信息发送到TeacherActor;
正如之前所说,TeacherActor会回复一个QuoteResponse;
StudentActor将日志打印到控制台或者logger里。
代码结构:
?:
import akka.actor.{ActorSystem, Props}
import com.aura.com.day06.akka.TeacherActor
import com.aura.com.day06.akka.actorToActor.Protocol.InitSignal
/**
* akka actor之间的通信
* 模拟学生向老师提问,老师收到问题后给学生反馈
*/
object _05AkkaCommunicationOps {
def main(args: Array[String]) {
val actorSystem = ActorSystem.create("teach-actor-system")
val teacherRef = actorSystem.actorOf(Props(classOf[TeacherActor]),"teacher-ref")
val stuRef = actorSystem.actorOf(Props.create(classOf[StudentActor],teacherRef),"student-ref")
stuRef ! InitSignal("请问老师太阳跟月亮哪个离得我们更远啊~")
Thread.sleep(2000)
actorSystem.shutdown()
}
}
//通信的协议
object Protocol {
case class Requests(question:String)
case class Response(response:String)
case class InitSignal(question:String)
}
import akka.actor.{ActorLogging, ActorRef, Actor}
import com.aura.com.day06.akka.actorToActor.Protocol.{Response, Requests, InitSignal}
class StudentActor(teacherRef:ActorRef) extends Actor with ActorLogging{
override def receive = {
case InitSignal(question) =>{
log.info("老师老师,我有一个问题~")
teacherRef ! Requests(question)
}
case Response(response) =>{
log.info("老师回答说:"+response)
}
}
}
import akka.actor.{Actor, ActorLogging}
import com.aura.com.day06.akka.actorToActor.Protocol.{Requests,Response}
import scala.util.Random
class TeacherActor extends Actor with ActorLogging{
val quotes = List(
"It does not do to dwell on dreams and forget to live.",
"书山有路勤为径,学海无涯苦作舟",
"Paper to come to the light, must know this to practice"
)
val random = new Random()
override def receive = {
case Requests(question)=>{
print("123456")
log.info("学生问:"+question)
val response = quotes(random.nextInt(quotes.size))
sender ! Response(response)
}
}
}
运行结果:
6.3远程Akka通信
代码结构:
application.conf配置文件:
MyRemoteServerSideActor {
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "127.0.0.1"
port = 3456
}
}
}
}
MyRemoteClientSideActor {
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
}
}
?:
import akka.actor.{Props, ActorSystem}
import com.typesafe.config.ConfigFactory
object AkkaClientApplication extends App{
// 通过配置文件application.conf配置创建ActorSystem系统
val system = ActorSystem("client-system",ConfigFactory.load().getConfig("MyRemoteClientSideActor"))
val log = system.log
//获取到ClientActor的一个引用
val clientActor = system.actorOf(Props[ClientActor],"ClientActor")
}
import akka.actor.{ActorSystem, Props}
import com.typesafe.config.ConfigFactory
object AkkaServerApplication extends App {
// 创建名称为remote-system的ActorSystem:从配置文件application.conf中获取该Actor的配置内容
val system = ActorSystem("remote-system",
ConfigFactory.load().getConfig("MyRemoteServerSideActor"))
val log = system.log
log.info("===>Remote server actor started: " + system)
// 创建一个名称为serverActor的Actor,返回一个ActorRef,这里我们不需要使用这个返回值
system.actorOf(Props[RemoteServerActor], "serverActor")
}
import akka.actor.{Actor, ActorLogging}
class ClientActor extends Actor with ActorLogging {
// akka.<protocol>://<actor system>@<hostname>:<port>/<actor path>
val path = "akka.tcp://remote-system@127.0.0.1:3456/user/serverActor" // 远程Actor的路径,通过该路径能够获取到远程Actor的一个引用
val remoteServerRef = context.actorSelection(path) // 获取到远程Actor的一个引用,通过该引用可以向远程Actor发送消息
@volatile var connected = false
@volatile var stop = false
def receive = {
case Start => { // 发送Start消息表示要与远程Actor进行后续业务逻辑处理的通信,可以指示远程Actor初始化一些满足业务处理的操作或数据
send(Start)
if(!connected) {
connected = true
log.info("ClientActor==> Actor connected: " + this)
}
}
case Stop => {
send(Stop)
stop = true
connected = false
log.info("ClientActor=> Stopped")
}
case header: Header => {
log.info("ClientActor=> Header")
send(header)
}
case (seq, result) => log.info("RESULT: seq=" + seq + ", result=" + result) // 用于接收远程Actor处理一个Packet消息的结果
case m => log.info("Unknown message: " + m)
}
private def send(cmd: Serializable): Unit = {
log.info("Send command to server: " + cmd)
try {
remoteServerRef ! cmd // 发送一个消息到远程Actor,消息必须是可序列化的,因为消息对象要经过网络传输
} catch {
case e: Exception => {
connected = false
log.info("Try to connect by sending Start command...")
send(Start)
}
}
}
}
object Start extends Serializable
object Stop extends Serializable
trait Message {
val id: String
}
case class Shutdown(waitSecs: Int) extends Serializable
case class Heartbeat(id: String, magic:Int) extends Message with Serializable
case class Header(id: String, len: Int, encrypted: Boolean) extends Message with Serializable
case class Packet(id: String, seq: Long, content: String) extends Message with Serializable
import akka.actor.{Actor, ActorLogging}
class RemoteServerActor extends Actor with ActorLogging {
def receive = {
case Start => { // 处理Start消息
log.info("Remote Server Start ==>RECV Start event : " + Start)
}
case Stop => { // 处理Stop消息
log.info("Remote Server Stop ==>RECV Stop event: " + Stop)
}
case Shutdown(waitSecs) => { // 处理Shutdown消息
log.info("Remote Server Shutdown ==>Wait to shutdown: waitSecs=" + waitSecs)
Thread.sleep(waitSecs)
log.info("Remote Server Shutdown ==>Shutdown this system.")
context.system.shutdown // 停止当前ActorSystem系统
}
case Header(id, len, encrypted) => log.info("Remote Server => RECV header: " + (id, len, encrypted)) // 处理Header消息
case _ =>
}
}