1. 熟练使用Scala编写程序
2. 项目概述
2.1 需求
目前大多数的分布式架构层通信都是通过RPC实现的,RPC架构非常多,比如Hadoop项目的RPC通信框架,但是Hadoo在设计之初就是为了运行长达数小时的批量而设计的,在某些极端的情况下,任务提交的延迟很高,所有Hadoop的RPC显得有些笨重 Spark的RPC是通过AKKa类库实现的,Akka用Scala语言开发,基于Actor并发模型实现,Akka具有高可靠、高性能、可扩展等特点,使用Akka可以轻松实现分布式RPC功能。
2.2 Akka简介 Akka基于Actor模型,提供了一个用于构建可扩展(Scalable),弹性的(Resilient),快速响应的(Responsive)应用程序的平台。 Actor模型:在计算机科学领域,Actor模型是一个并行计算(ConCurrent Computation)模型,它把actor做出一些决策,如创建更多的actor,或发送更多的actor,或发送更多的消息,或者确定如何去响应接收到的下一个消息。
Actor是一个Akka中最核心的概念,它是一个封装了状态和行为的对象,Actor之间可以通过交换消息的方式进行通信,每一个Actor都有自己的收件箱(MailBox)。通过Actor能够简化锁及线程还礼,可以非常的开发出正确并发程序和并行系统,Actor具有如下特性:
1.提供了一种高级抽象,能够简化在并发(Concurrency)/并行(parallelism)应用场景下的编程开发
2. 提供了异步非阻塞的,高性能的时间驱动编程模型
3.超级了轻量级事件处理(每GB堆内存几百万Actor)
3. 项目实现
3.1 架构图
3.2 重要类介绍
3.2.1 ActorSystem
在Akka中,ActorSystem是一个重要级的结构,需要分配多个线程,所以在实际应用中,ActorSystem通常是一个单例对象,可以使用这个ActorSystem创建很多Actor.
3.2.2 Actor
在Akka中,Actor负责通信,在Actor中有一些重要的生命周期方法
1.preStart()方法:该方法Actor对象构造方法执行后执行,整个Actor生命周期中仅执行一次。
2.receive()方法:该方法在Actor的preStart方法执行完成后执行,用于接收消息,会被返回执行。
1.WorkInfo类
package com.zhiyou100.ScalaActor_akka.Worker
/*用来封装master接受的worker的信息
@param id
@param mem
@param cores
*/
class WorkInfo(val id:String,val mem:Int,val cores:Int) {
var LastBeat :Long = _
}
2.IntervalMeaage类
package com.zhiyou100.ScalaActor_akka.Worker
/*交互消息:用来封装Master与Worker进行交互的消息
1.注册 2.Master的反馈 3.心跳 4.检查信息(时间超时)
这里面都是样例类,因为在网络传输所以必须实现序列化
*/
trait IntervalMeaage extends Serializable //抽象类
//注册样例类,封装了从Worker到Master的注册消息
case class RegisterWorker(id:String,mem:Int,cores:Int) extends IntervalMeaage //mem 内存 core核数
//注册成功确认消息
case class RegisterFinish(masterURL:String) extends IntervalMeaage
//发送心跳 worker -->worker
case object SendHeartBeat
//发送心跳 worker -> master
case class HeartBeat (id:String) extends IntervalMeaage
//超时检查 master -->master
case object CheckTimeOut
3.Master类
package com.zhiyou100.ScalaActor_akka.Worker
import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import scala.collection.mutable
import scala.concurrent.duration._
class Master extends Actor{
//用来保存worker传输过来的信息,value最好一个实体类,以后方便使用其中属性
val ids=new mutable.HashMap[String,WorkInfo]()
//保存map中的workInfo的集合,方便以后属性的排序
val workers=new mutable.HashSet[WorkInfo]()
val CHECK_BEAT=2500
// 生命周期之启动Actor contrl+o
override def preStart():Unit ={
println("Actor is preStart")
//启动定时器,检查worker心跳是否正常
import context.dispatcher
context.system.scheduler.schedule(0.millis,CHECK_BEAT.millis,self,CheckTimeOut)
}
//用于接收消息
override def receive:Receive = {
case "connect" =>{
println("success")
sender ! "reply"
}
case "hello" =>{
println("1111" )
}
case RegisterWorker(id,mem,cores) =>{
//判断一下worker是否注册过
if (!ids.contains(id)){
//将封装实例化
val workInfo = new WorkInfo(id,mem,cores)
//保存数据策略: 1.保存内存 2.持久化到磁盘 3. 保存到zookeeper
//map的put的操作
ids (id) =workInfo
//set的追加操作
workers += workInfo
//创建样例类发送注册成功确认消息
sender ! RegisterFinish("akka.tcp://MasterSystem@$host:$port/user/Master")
}
}
case HeartBeat(id) =>{
if (ids.contains(id)){
val workInfo =ids(id)
val currentTime=System.currentTimeMillis()
//把心跳时间用当前的时间置换
workInfo.LastBeat =currentTime
}
}
case CheckTimeOut =>{
val concurrentTime=System.currentTimeMillis()
val toClean = workers.filter(x=> concurrentTime -x.LastBeat >CHECK_BEAT)
for (w<- toClean){
workers -=w
ids -=w.id
}
println("活着的wpoker数量:"+workers.size)
}
}
}
//老大
object Master {
def main(args: Array[String]): Unit = {
val host=args(0)
val port=args(1).toInt
//准备配置文件
val config =
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = "$host"
|akka.remote.netty.tcp.port = "$port"
""".stripMargin
val cfg = ConfigFactory.parseString(config) //解析配置文件
//actorSystem :Actor的领导,监控所有的actor,singletong
val actorSystem =ActorSystem("MasterSystem",cfg)
//创建actor
val master = actorSystem.actorOf(Props[Master],name="Master")
master ! "success"
actorSystem.awaitTermination() //让进程等待,不结束
}
}
4. Worker类
package com.zhiyou100.ScalaActor_akka.Worker
import java.util.UUID
import scala.concurrent.duration._
import akka.actor.{Actor, ActorSelection, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
class Worker (val masterHost:String,val masterPort: Int,val mem:Int,val cores:Int)
extends Actor{
var master:ActorSelection=_
val workerID=UUID.randomUUID().toString
val HEART_BEAT=2000
//建立连接
override def preStart(): Unit = {
//参数需要有/user/Master
master= context.actorSelection(s"akka.tcp://MasterSystem@$masterHost:$masterPort/user/Master")
// master ! "connect"
//向Master发送注册消息,Master的receive方法中的case class去接受
master ! RegisterWorker(workerID,mem,cores)
}
override def receive : Receive ={
//worker收到master返回的确认消息
case RegisterFinish (masterURL) =>{
println(masterURL)
//定时发送心跳
//导入影视转换
import context.dispatcher
context.system.scheduler.schedule(0.millis,HEART_BEAT.millis,self,SendHeartBeat)
//schedule(0.millis ,HEART_BEAT.millis,self,SendHeartBeat)
}
//接收自己发送给自己的心跳,然后在发送给Master
case SendHeartBeat=>{
println("发送心跳的Master")
master ! HeartBeat(workerID)
}
case "reply" =>{
println("haode")
}
}
}
object Worker{
def main(args: Array[String]): Unit = {
val host=args(0)
val port=args(1)
val masterHost=args(2)
val masterPort=args(3).toInt
val mem =args(4).toInt
val cores=args(5).toInt
//准备配置
val config=
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = "$host"
|akka.remote.netty.tcp.port = "$port"
""".stripMargin
val cfg = ConfigFactory.parseString(config)
val actorSystem = ActorSystem("WorkerSystem",cfg)
//创建worker
actorSystem.actorOf(Props(new Worker(masterHost,masterPort,mem,cores)),name = "Worker")
actorSystem.awaitTermination()
}
}