简介
在学习完Scala语言后,我们可以实现一个简单的RPC来巩固前面我们学习的Scala知识点,这里主要的知识点涉及样例类、模式匹配、类继承、隐式转化以及函数和方法,如果需要源码可以在我的Github上下载,地址是SRPC。
项目概述
需求
目前大多数的分布式架构底层通信都是通过RPC实现的,RPC框架非常多,比如前我们学过的Hadoop项目的RPC通信框架,但是Hadoop在设计之初就是为了运行长达数小时的批量而设计的,在某些极端的情况下,任务提交的延迟很高,所有Hadoop的RPC显得有些笨重。
Spark 的RPC是通过Akka类库实现的,Akka用Scala语言开发,基于Actor并发模型实现,Akka具有高可靠、高性能、可扩展等特点,使用Akka可以轻松实现分布式RPC功能。
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)
项目实现
架构图
实现的主要思路
该简单的框架主要有两个类Master、Worker,Master类主要用于Worke的注册,定时的检查节点是否存活,感知节点的上下线;Worker主要向Master注册信息,然后定期的调度任务。
代码实现
package cn.edu.hust
import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}
import scala.concurrent.duration._
import scala.collection.mutable
class Master(val host:String,val port:Int) extends Actor{
val works=new mutable.HashMap[String,WorkInfo]()
val works_avaliable=new mutable.HashSet[WorkInfo]()
val heartBeat_interval:Long=15000
//在构造方法之后,receive方法之前启动
override def preStart(): Unit = {
println("start method invoked")
import context.dispatcher
context.system.scheduler.schedule(0 millis ,heartBeat_interval millis,self,CheckHeartBeat)
}
def func(worker:WorkInfo):Boolean={
System.currentTimeMillis()-worker.Pre_Time>heartBeat_interval
}
override def receive():Receive = {
//worker向服务端注册信息
case RegisterWorkInfo(id,memory,cores) =>{
//如果没有包含这个worker,那么就将这个worker的信息保存在本地,然后发送信息
if(!works.contains(id))
{
val worker_new=new WorkInfo(id,memory,cores)
works.put(id,worker_new)
works_avaliable+=worker_new
}
//发送消息给worker,如果注册成功返回
sender ! RegisterResponse(s"akka.tcp://system@$host:$port/user/Master")
}
case HeartBeatToMaster(id) =>
{
if(works.contains(id))
works(id).Pre_Time=System.currentTimeMillis()
//works_avaliable.
//sender() ! "ok"
}
case CheckHeartBeat=>
{
//val func=(worker:WorkInfo)=>Boolean {}
val toRemove=works_avaliable.filter(func)
for(info <- toRemove) {
works -= info.id
works_avaliable -= info
}
println(works.size)
}
}
}
object Master {
def main(args: Array[String]): Unit = {
val host=args(0)
val port=args(1).toInt
val conf=s"""|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = "$host"
|akka.remote.netty.tcp.port = "$port"
""".stripMargin
val config=ConfigFactory.parseString(conf)
//1.创建一个ActorSystem用于监控和管理所有的actor
val actorSystem=ActorSystem.create("system",config)
//创建一个actor,这里表示Master
val master=actorSystem.actorOf(Props(new Master(host,port)), "Master")
//发送异步消息
//master!"connect"
//退出
actorSystem.awaitTermination()
}
}
Worker类的具体代码:
package cn.edu.hust
import java.util.UUID
import akka.actor.{Actor, ActorSelection, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import scala.concurrent.duration._
class Worker(val masterHost:String,val masterPort:Int) extends Actor{
var master:ActorSelection= _
//给每一个worker分配一个唯一的id
val id=UUID.randomUUID().toString
val beats=10000
override def preStart(): Unit = {
//1.连接server,获取master实例
master=context.actorSelection(s"akka.tcp://system@$masterHost:$masterPort/user/Master")
//2.使用master实例注册消息
master! RegisterWorkInfo(id,10240,4)
}
override def receive ():Receive = {
case RegisterResponse(url)=>{
println(s"the master work in $url")
import context.dispatcher
//配置一个定时调度器,定时向master发送任务
context.system.scheduler.schedule(0 millis ,beats millis,self,HeartBeat)
}
case HeartBeat=>{
master!HeartBeatToMaster(id)
}
}
}
object Worker
{
def main(args: Array[String]): Unit = {
val masterHost=args(0)
val masterPort=args(1).toInt
val workerHost=args(2)
val workerPort=args(3).toInt
val conf=s"""|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = "$workerHost"
|akka.remote.netty.tcp.port = "$workerPort"
""".stripMargin
val config=ConfigFactory.parseString(conf)
val system=ActorSystem.create("worker",config)
val worker=system.actorOf(Props(new Worker(masterHost,masterPort)),"Worker")
system.awaitTermination()
}
}
附带的代码:
package cn.edu.hust
trait RemoteMessage extends Serializable
//从worker到master的消息
case class RegisterWorkInfo(id:String,memory:Int,cores:Int) extends RemoteMessage
//master向worker发送消息
case class RegisterResponse(url:String) extends RemoteMessage
//master向worker发送心跳消息
case class HeartBeatToMaster(id:String)extends RemoteMessage
//worker自己向自己发送心跳信息
case object HeartBeat
case object CheckHeartBeat
package cn.edu.hust
class WorkInfo(val id:String,val memory:Int, val cores:Int) {
//存储上一次心跳通信的时间
var Pre_Time:Long=_
}