使用akka框架编写RPC框架

来源:互联网 发布:借贷软件排行 编辑:程序博客网 时间:2024/06/11 10:38

RPC通信模型

这里写图片描述

项目概述
需求
目前大多数的分布式架构底层通信都是通过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)

重要类介绍
ActorSystem
在Akka中,ActorSystem是一个重量级的结构,他需要分配多个线程,所以在实际应用中,ActorSystem通常是一个单例对象,我们可以使用这个ActorSystem创建很多Actor。
Actor
在Akka中,Actor负责通信,在Actor中有一些重要的生命周期方法。

1.preStart()方法:该方法在Actor对象构造方法执行后执行,整个Actor生命周期中仅执行一次。
2.receive()方法:该方法在Actor的preStart方法执行完成后执行,用于接收消息,会被反复执行。
Master类
package ljt.scalalearing.myrpc.rpc

import scala.collection.mutable
import scala.concurrent.duration.DurationInt

import com.typesafe.config.ConfigFactory
import akka.actor.Actor
import akka.actor.ActorSystem
import akka.actor.Props

/**
* RPC的master 管理子进程
* Master为整个集群中的主节点
* 子worker向master注册状态
*/
class Master(val host: String, val port: Int) extends Actor {

// workerId -> WorkerInfo
val idToWorker = new mutable.HashMapString, WorkerInfo
// WorkerInfo
val workers = new mutable.HashSetWorkerInfo //使用set删除快, 也可用linkList
//超时检查的间隔
val CHECK_INTERVAL = 15000

override def preStart(): Unit = {
println(“preStart invoked”)
//导入隐式转换
import context.dispatcher //使用timer太low了, 可以使用akka的, 使用定时器, 要导入这个包
context.system.scheduler.schedule(0 millis, CHECK_INTERVAL millis, self, CheckTimeOutWorker)
}

// 用于接收消息
override def receive: Receive = {
case RegisterWorker(id, memory, cores) => {
//判断一下,是不是已经注册过
if(!idToWorker.contains(id)){
//把Worker的信息封装起来保存到内存当中
val workerInfo = new WorkerInfo(id, memory, cores)
idToWorker(id) = workerInfo
workers += workerInfo
sender ! RegisteredWorker(s”akka.tcp://MasterSystem@host:port/user/Master”)//通知worker注册
}
}
case Heartbeat(id) => {
if(idToWorker.contains(id)){
val workerInfo = idToWorker(id)
//报活
val currentTime = System.currentTimeMillis()
workerInfo.lastHeartbeatTime = currentTime
}
}

case CheckTimeOutWorker => {  val currentTime = System.currentTimeMillis()  val toRemove = workers.filter(x => currentTime - x.lastHeartbeatTime > CHECK_INTERVAL)  for(w <- toRemove) {    workers -= w    idToWorker -= w.id  }  println(workers.size)}

}
}

object Master {
def main(args: Array[String]) {

val host = args(0)val port = args(1).toInt// 准备配置val configStr =  s"""     |akka.actor.provider = "akka.remote.RemoteActorRefProvider"     |akka.remote.netty.tcp.hostname = "$host"     |akka.remote.netty.tcp.port = "$port"   """.stripMarginval config = ConfigFactory.parseString(configStr)//ActorSystem老大,辅助创建和监控下面的Actor,他是单例的val actorSystem = ActorSystem("MasterSystem", config)//创建Actorval master = actorSystem.actorOf(Props(new Master(host, port)), "Master")actorSystem.awaitTermination()

}
}

work子进程向master注册节点请求连接,然后向master发送心跳,master开启定时器监测心跳,超时判断改work进程死亡。
package ljt.scalalearing.myrpc.rpc

import java.util.UUID

import akka.actor.{Actor, ActorSelection, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import scala.concurrent.duration._

/**
* Created by root on 2016/5/13.
*/
class Worker(val masterHost: String, val masterPort: Int, val memory: Int, val cores: Int) extends Actor{

var master : ActorSelection = _
val workerId = UUID.randomUUID().toString
val HEART_INTERVAL = 10000

//
override def preStart(): Unit = {
//跟Master建立连接
master = context.actorSelection(s”akka.tcp://MasterSystem@masterHost:masterPort/user/Master”)
//向Master发送注册消息
master ! RegisterWorker(workerId, memory, cores)
}

override def receive: Receive = {
case RegisteredWorker(masterUrl) => {
println(masterUrl)
//启动定时器发送心跳
import context.dispatcher
//多长时间后执行 单位,多长时间执行一次 单位, 消息的接受者(直接给master发不好, 先给自己发送消息, 以后可以做下判断, 什么情况下再发送消息), 信息
context.system.scheduler.schedule(0 millis, HEART_INTERVAL millis, self, SendHeartbeat)
}

case SendHeartbeat => {  println("send heartbeat to master")  master ! Heartbeat(workerId)}

}
}

object Worker {
def main(args: Array[String]) {
val host = args(0)
val port = args(1).toInt
val masterHost = args(2)
val masterPort = args(3).toInt
val memory = args(4).toInt
val cores = args(5).toInt
// 准备配置
val configStr =
s”“”
|akka.actor.provider = “akka.remote.RemoteActorRefProvider”
|akka.remote.netty.tcp.hostname = “host|akka.remote.netty.tcp.port=port”
“”“.stripMargin
val config = ConfigFactory.parseString(configStr)
//ActorSystem老大,辅助创建和监控下面的Actor,他是单例的
val actorSystem = ActorSystem(“WorkerSystem”, config)
actorSystem.actorOf(Props(new Worker(masterHost, masterPort, memory, cores)), “Worker”)
actorSystem.awaitTermination()
}
}

将remote消息进行门面化设计:
package ljt.scalalearing.myrpc.rpc

/**
* 远程过程调用消息序列化特征
*/
trait RemoteMessage extends Serializable

//Worker -> Master
case class RegisterWorker(id: String, memory: Int, cores: Int) extends RemoteMessage

case class Heartbeat(id: String) extends RemoteMessage

//Master -> Worker
case class RegisteredWorker(masterUrl: String) extends RemoteMessage

//Worker -> self
case object SendHeartbeat

// Master -> self
case object CheckTimeOutWorker

子进程work发送心跳模板
package ljt.scalalearing.myrpc.rpc

/**
* Created by root on 2016/5/13.
*/
class WorkerInfo(val id: String, val memory: Int, val cores: Int) {

//TODO 上一次心跳
var lastHeartbeatTime : Long = _
}

测试:
先运行master
参数
172.28.235.173 8888

在运行两个work
参数
work1:
172.28.235.173 8088 172.28.235.173 8888 8 2
work2:
172.28.235.173 8099 172.28.235.173 8888 8 2

原创粉丝点击