【Spark】编程实战之模拟SparkRPC原理实现自定义RPC
1. 什么是RPC
RPC(Remote Procedure Call)远程过程调用。在Hadoop和Spark中都使用了PRC,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。简单来说,就是有A、B两台机器,A机器可以调用B机器上的程序。
2. Spark 的RPC
Master和Worker的启动流程:
(1) 启动Master,会启动一个定时器,定时检查超时的Worker,并移除超时Worker信息。
(2) 启动Worker,向Master发送注册信息。
(3) Master收到Worker发来的注册信息后,保存到内存中,并返回一个响应信息,这个信息就是自己的masterUrl。
(4) Worker接收到Master发来的响应信息(masterUrl)之后,保存到内存中,并开启一个定时器,定时向Master发送心跳信息。
(5) Master 不断的接收Worker发来的心跳信息,并将每个Worker的最后一次心跳时间为当前接收到心跳信息的时间。
流程如下图。
3. 编程实战
3.1 项目代码(Scala语言)
WorkInfo.scala
package com.nova.rpc
/**
* @author Supernova
* @date 2018/06/15
*/
class WorkerInfo(val id: String, val host: String, val port: Int,val memory: Int, val cores: Int) {
// 记录最后一次心跳时间
var lastHeartbeatTime: Long = _
}
RemoteMsg.scala
package com.nova.rpc /**
* @author Supernova
* @date 2018/06/15
*/
trait RemoteMsg extends Serializable{ } // Master 向自己发送检查超时Worker的信息
case object CheckTimeOutWorker // Worker向Master发送的注册信息
case class RegisterWorker(id: String, host: String,port: Int, memory: Int, cores: Int) extends RemoteMsg // Master向Worker发送的响应信息
case class RegisteredWorker(masterUrl: String) extends RemoteMsg // Worker向Master发送的心跳信息
case class Heartbeat(workerId: String) extends RemoteMsg // Worker向自己发送的要执行发送心跳信息的消息
case object SendHeartbeat
Master.scala
package com.nova.rpc
import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}
import scala.collection.mutable
import scala.concurrent.duration._
/**
  * @author Supernova
  * @date 2018/06/15
  */
class Master(val masterHost: String, val masterPort: Int) extends Actor{
  // 用来存储Worker的注册信息: <workerId, WorkerInfo>
  val idToWorker = new mutable.HashMap[String, WorkerInfo]()
  // 用来存储Worker的信息,必须使用可变的HashSet
  val workers = new mutable.HashSet[WorkerInfo]()
  // Worker的超时时间间隔
  val checkInterval: Long = 15000
  /**
    * 重写生命周期preStart方法
    * 作用:当Master启动时,开启定时器,定时检查超时Worker
    */
  override def preStart(): Unit = {
    // 启动定时器,定时检查超时的Worker
    import context.dispatcher
    context.system.scheduler.schedule(0 millis,checkInterval millis, self,CheckTimeOutWorker)
  }
  /**
    *  重写生命周期receive方法
    *  作用:
    *  1.接收Worker发来的注册信息
    *  2.不断接收Worker发来的心跳信息,并更新最后一次心跳时间
    *  3.过滤出超时的Worker并移除
    */
  override def receive = {
    // 接收Worker给Master发送过来的注册信息
    case RegisterWorker(id, host, port, memory, cores) => {
      //判断改Worker是否已经注册过,已注册的不执行任何操作,未注册的将进行注册
      if (!idToWorker.contains(id)) {
        val workerInfo = new WorkerInfo(id, host, port, memory, cores)
        idToWorker += (id -> workerInfo)
        workers += workerInfo
        println("一个新的Worker注册成功")
        //向Worker发送响应信息,将masterUrl返回
        sender ! RegisteredWorker(s"akka.tcp://${Master.MASTER_SYSTEM}" +
          s"@${masterHost}:${masterPort}/user/${Master.MASTER_ACTOR}")
      }
    }
    //接收Worker发来的心跳信息
    case Heartbeat(workerId) => {
      // 通过传输过来的workerId获取对应的WorkerInfo
      val workerInfo = idToWorker(workerId)
      // 获取当前时间
      val currentTime = System.currentTimeMillis()
      // 更新最后一次心跳时间
      workerInfo.lastHeartbeatTime = currentTime
    }
    //检查超时Worker并移除
    case CheckTimeOutWorker => {
      val currentTime = System.currentTimeMillis()
      // 把超时的Worker过滤出来
      val toRemove: mutable.HashSet[WorkerInfo] =
        workers.filter(w => currentTime - w.lastHeartbeatTime > checkInterval)
      // 将超时的Worker移除
      toRemove.foreach(deadWorker => {
        idToWorker -= deadWorker.id
        workers -= deadWorker
      })
    }
    println(s"当前Worker的数量: ${workers.size}")
  }
}
object Master{
  val MASTER_SYSTEM = "MasterSystem"
  val MASTER_ACTOR = "Master"
  def main(args: Array[String]): Unit = {
    val host = args(0) // 通过main方法参数制定master主机名
    val port = args(1).toInt //通过main方法参数指定Master的端口号
    //akka配置信息
    val configStr: String =
      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = "$host"
         |akka.remote.netty.tcp.port = "$port"
      """.stripMargin
    // 配置创建Actor需要的配置信息
    val config: Config = ConfigFactory.parseString(configStr)
    // 创建ActorSystem
    val actorSystem: ActorSystem = ActorSystem(MASTER_SYSTEM, config)
    // 用actorSystem实例创建Actor
    actorSystem.actorOf(Props(new Master(host, port)), MASTER_ACTOR)
    actorSystem.awaitTermination()
  }
}
Worker.scala
package com.nova.rpc import java.util.UUID
import akka.actor.{Actor, ActorSelection, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory} import scala.concurrent.duration._
/**
* @author Supernova
* @date 2018/06/15
*/
class Worker(val host: String, val port: Int, val masterHost: String,val masterPort: Int, val memory: Int, val cores: Int) extends Actor{ // 生成一个Worker ID
val workerId: String = UUID.randomUUID().toString // 用来存储MasterUrl
var masterUrl: String = _ // 心跳时间间隔
val heartbeat_interval: Long = 10000 // Master的Actor
var master: ActorSelection = _ /**
* 生命周期preStart方法
* 作用:当启动Worker时,向master发送注册信息
*/
override def preStart(): Unit = {
// 获取Master的Actor
master = context.actorSelection(s"akka.tcp://${Master.MASTER_SYSTEM}" +
s"@${masterHost}:${masterPort}/user/${Master.MASTER_ACTOR}")
master ! RegisterWorker(workerId, host, port, memory, cores)
} /**
* 生命周期receive方法
* 作用:
* 定时向Master发送心跳信息
*/
override def receive: Receive = {
// Worker接收到Master发送过来的注册成功的信息(masterUrl)
case RegisteredWorker(masterUrl) => {
this.masterUrl = masterUrl
// 启动一个定时器, 定时的给Master发送心跳
import context.dispatcher
context.system.scheduler.schedule(
0 millis, heartbeat_interval millis, self, SendHeartbeat)
}
case SendHeartbeat => {
// 向Master发送心跳信息
master ! Heartbeat(workerId)
}
} } object Worker{
val WORKER_SYSTEM = "WorkerSystem"
val WORKER_ACTOR = "Worker" def main(args: Array[String]): Unit = {
/**
* 通过main方法参数指定相应的
* worker主机名、端口号,master主机名、端口号,使用的内存和核数
*/
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 //akka配置信息
val configStr =
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = "$host"
|akka.remote.netty.tcp.port = "$port"
""".stripMargin // 配置创建Actor需要的配置信息
val config: Config = ConfigFactory.parseString(configStr) // 创建ActorSystem
val actorSystem: ActorSystem = ActorSystem(WORKER_SYSTEM, config) // 用actorSystem实例创建Actor
actorSystem.actorOf(Props(new Worker(
host, port, masterHost, masterPort, memory, cores)), WORKER_ACTOR) actorSystem.awaitTermination()
}
}
3.2 测试运行
由于Master 和Worker的运行都是使用main方法参数传入相应的主机名端口等参数,所以在运行前要在IDEA中的Edit Configurations 窗口中传入相应的参数。在本次测试中,我指定的参数如图:
【Master端】
【Worker端】
【运行结果】
1. 先运行Master,可以看到一旦运行Master,就启动了定时器检查超时Worker,因为还没有Worker进行注册,所以结果一直为0
2. 启动Worker
3. 启动Worker后,再看Master的窗口可以发现Worker注册成功,并且数量为1
4. 关闭Worker,此时Worker已经宕掉了,可以发现Master窗口会收到一条警告信息,并且Master在定时检查超时Worker的时候移除了过期未收到心跳的Worker
【Spark】编程实战之模拟SparkRPC原理实现自定义RPC的更多相关文章
- Spark调研笔记第6篇 - Spark编程实战FAQ
		本文主要记录我使用Spark以来遇到的一些典型问题及其解决的方法,希望对遇到相同问题的同学们有所帮助. 1. Spark环境或配置相关 Q: Sparkclient配置文件spark-defaults ... 
- 《java并发编程实战》读书笔记11--构建自定义的同步工具,条件队列,Condition,AQS
		第14章 构建自定义的同步工具 本章将介绍实现状态依赖性的各种选择,以及在使用平台提供的状态依赖机制时需要遵守的各项规则. 14.1 状态依赖性的管理 对于并发对象上依赖状态的方法,虽然有时候在前提条 ... 
- Spark入门实战系列--7.Spark Streaming(上)--实时流计算Spark Streaming原理介绍
		[注]该系列文章以及使用到安装包/测试数据 可以在<倾情大奉送--Spark入门实战系列>获取 .Spark Streaming简介 1.1 概述 Spark Streaming 是Spa ... 
- 超级干货:动态防御WAF技术原理及编程实战!
		本文带给大家的内容是动态防御WAF的技术原理及编程实战. 将通过介绍ShareWAF的核心技术点,向大家展示动态防御的优势.实现思路,并以编程实战的方式向大家展示如何在WAF产品开发过程中应用动态防御 ... 
- Spark入门实战系列--3.Spark编程模型(上)--编程模型及SparkShell实战
		[注]该系列文章以及使用到安装包/测试数据 可以在<倾情大奉送--Spark入门实战系列>获取 .Spark编程模型 1.1 术语定义 l应用程序(Application): 基于Spar ... 
- Spark入门实战系列--3.Spark编程模型(下)--IDEA搭建及实战
		[注]该系列文章以及使用到安装包/测试数据 可以在<倾情大奉送--Spark入门实战系列>获取 . 安装IntelliJ IDEA IDEA 全称 IntelliJ IDEA,是java语 ... 
- Scala实战高手****第17课:Scala并发编程实战及Spark源码阅读
		package com.wanji.scala.test import javax.swing.text.AbstractDocument.Content import scala.actors.Ac ... 
- 【Java编程实战】Metasploit_Java后门运行原理分析以及实现源码级免杀与JRE精简化
		QQ:3496925334 文章作者:MG1937 CNBLOG博客ID:ALDYS4 未经许可,禁止转载 某日午睡,迷迷糊糊梦到Metasploit里有个Java平台的远控载荷,梦醒后,打开虚拟机, ... 
- 大数据学习day20-----spark03-----RDD编程实战案例(1 计算订单分类成交金额,2 将订单信息关联分类信息,并将这些数据存入Hbase中,3 使用Spark读取日志文件,根据Ip地址,查询地址对应的位置信息
		1 RDD编程实战案例一 数据样例 字段说明: 其中cid中1代表手机,2代表家具,3代表服装 1.1 计算订单分类成交金额 需求:在给定的订单数据,根据订单的分类ID进行聚合,然后管理订单分类名称, ... 
随机推荐
- Jmeter————监控服务器性能
			1. 下载jmeter插件 上面2个是jmeter插件,第3个要放在监控的服务器中. 2. 解压压缩包 找到解压包中的JAR文件,并拷贝到jmeter的lib/ext目录下,这里下载的1.4版本的插件 ... 
- Sqlserver新建随机测试数据
			USE Test --使用数据库Test(如果没有则需要新建一个) ----1.新建一个users表 create table users( uId int primary key identity( ... 
- springMVC入门-01
			这一系列是在看完网上SpringMVC(基于spring3.0)入门视频之后的个人总结,仅供参考,其中会添加一些个人的见解. 1.搭建SpringMVC所需jar包: org.springframew ... 
- 存储过程存储过程需要用两个'',先where再Group,再Order by
			存储过程需要用两个'',先where再Group,再Order by 未完,待续 
- Java常见错误列表
			Java常见错误列表: 找不到符号(symbol) 类X是public的,应该被声明在名为X.java的文件中 缺失类.接口或枚举类型 缺失X 缺失标识符 非法的表达式开头 类型不兼容 非法的方法声明 ... 
- Google的Python代码格式化工具YAPF详解
			平时习惯了杂乱无章地编写代码,而最后的代码勘定,却依赖于PyCharm自带的格式化工具,以及其自带的提示功能来规范代码.而pycharm里的格式化工具,不支持对多文件进行代码批量格式化,曾经尝试些解决 ... 
- ethereumjs/ethereumjs-vm-1-简介
			https://github.com/ethereumjs/ethereumjs-vm 其实这就是怎么自己使用该模块来生成一个类似geth客户端的以太坊虚拟机,然后进行各类区块链操作 SYNOPSIS ... 
- [转]TortoiseSVN客户端的安装
			TortoiseSVN是windows平台下Subversion的免费开源客户端. 一般我们都是先讲讲服务器的配置,然后再讲客户端的使用,但是在TortoiseSVN上,却可以反过来.因为,如果你的要 ... 
- 第2章 K近邻算法
			numpy中的tile函数: 遇到numpy.tile(A,(b,c))函数,重复复制A,按照行方向b次,列方向c次. >>> import numpy >>> n ... 
- Dubbo实践(二)架构
			架构 节点角色说明 节点 角色说明 Provider 暴露服务的服务提供方 Consumer 调用远程服务的服务消费方 Registry 服务注册与发现的注册中心 Monitor 统计服务的调用次数和 ... 
