Scala编程实战

Stella981
• 阅读 569

项目概述

需求

目前大多数的分布式架构底层通信都是通过RPC实现的,RPC框架非常多,比如前我们学过的Hadoop项目的RPC通信框架,但是Hadoop在设计之初就是为了运行长达数小时的批量而设计的,在某些极端的情况下,任务提交的延迟很高,所以Hadoop的RPC显得有些笨重。

Spark 的RPC是通过Akka类库实现的,Akka用Scala语言开发,基于Actor并发模型实现,Akka具有高可靠、高性能、可扩展等特点,使用Akka可以轻松实现分布式RPC功能。

Akka简介

友情链接:  Actors介绍:   https://www.iteblog.com/archives/1154.html

Akka基于Actor模型,提供了一个用于构建可扩展的(Scalable)、弹性的(Resilient)、快速响应的(Responsive)应用程序的平台。

Actor模型:在计算机科学领域,Actor模型是一个并行计算(Concurrent Computation)模型,它把actor作为并行计算的基本元素来对待:为响应一个接收到的消息,一个actor能够自己做出一些决策,如创建更多的actor,或发送更多的消息,或者确定如何去响应接收到的下一个消息。

Scala编程实战

Actor是Akka中最核心的概念,它是一个封装了状态和行为的对象,Actor之间可以通过交换消息的方式进行通信,每个Actor都有自己的收件箱(Mailbox)。通过Actor能够简化锁及线程管理,可以非常容易地开发出正确地并发程序和并行系统,Actor具有如下特性:

  (1)、提供了一种高级抽象,能够简化在并发(Concurrency)/并行(Parallelism)应用场景下的编程开发

(2)、提供了异步非阻塞的、高性能的事件驱动编程模型

(3)、超级轻量级事件处理(每GB堆内存几百万Actor)

项目实现

实战一:

利用Akkaactor编程模型,实现2个进程间的通信。

架构图

 Scala编程实战

重要类介绍

ActorSystem****:在Akka中,ActorSystem是一个重量级的结构,他需要分配多个线程,所以在实际应用中,ActorSystem通常是一个单例对象,我们可以使用这个ActorSystem创建很多Actor。

注意****:

(1)、ActorSystem是一个进程中的老大,它负责创建和监督actor

(2)、ActorSystem是一个单例对象

(3)、actor负责通信

Actor

在Akka中,Actor负责通信,在Actor中有一些重要的生命周期方法。

(1)preStart()方法:该方法在Actor对象构造方法执行后执行,整个Actor生命周期中仅执行一次。

(2)receive()方法:该方法在Actor的preStart方法执行完成后执行,用于接收消息,会被反复执行。

具体代码

Master****类

package cn.itcast.rpc
  
  import akka.actor.{Actor, ActorRef, ActorSystem, Props}
  import com.typesafe.config.ConfigFactory
  
  //todo:利用akka的actor模型实现2个进程间的通信-----Master端  
  class Master  extends Actor{
  //构造代码块先被执行
  println("master constructor invoked")
  
  //prestart方法会在构造代码块执行后被调用,并且只被调用一次
  override def preStart(): Unit = {
    println("preStart method invoked")
  }
  
  //receive方法会在prestart方法执行后被调用,表示不断的接受消息
  override def receive: Receive = {
    case "connect" =>{
      println("a client connected")

      //master发送注册成功信息给worker
      sender ! "success"
    }
  }
}

  object Master{
  def main(args: Array[String]): Unit = {
    //master的ip地址
    val host=args(0)
    //master的port端口
    val port=args(1)
  
    //准备配置文件信息
    val configStr=
      s"""
        |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
        |akka.remote.netty.tcp.hostname = "$host"
        |akka.remote.netty.tcp.port = "$port"
      """.stripMargin
  
    //配置config对象 利用ConfigFactory解析配置文件,获取配置信息
    val config=ConfigFactory.parseString(configStr)
  
      // 1、创建ActorSystem,它是整个进程中老大,它负责创建和监督actor,它是单例对象
    val masterActorSystem = ActorSystem("masterActorSystem",config)
     // 2、通过ActorSystem来创建master actor
      val masterActor: ActorRef = masterActorSystem.actorOf(Props(new Master),"masterActor")
    // 3、向master actor发送消息
    //masterActor ! "connect"
  }
}

  

Worker****类

package cn.itcast.rpc
 
  import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
  import com.typesafe.config.ConfigFactory

  //todo:利用akka中的actor实现2个进程间的通信-----Worker端
  class Worker  extends Actor{
  println("Worker constructor invoked")
  
  //prestart方法会在构造代码块之后被调用,并且只会被调用一次
  override def preStart(): Unit = {
      println("preStart method invoked")

    //获取master actor的引用
    //ActorContext全局变量,可以通过在已经存在的actor中,寻找目标actor
    //调用对应actorSelection方法,
    // 方法需要一个path路径:1、通信协议、2、master的IP地址、3、master的端口 4、创建master actor老大 5、actor层级
    val master: ActorSelection = context.actorSelection("akka.tcp://masterActorSystem@172.16.43.63:8888/user/masterActor")

    //向master发送消息
    master ! "connect"  
  }
  
  //receive方法会在prestart方法执行后被调用,不断的接受消息
  override def receive: Receive = {
    case "connect" =>{
      println("a client connected")
    }

    case "success" =>{
      println("注册成功")
    }
  }
}

  object Worker{
  def main(args: Array[String]): Unit = {
    //定义worker的IP地址
    val host=args(0)
    //定义worker的端口
    val port=args(1)

    //准备配置文件
    val configStr=
      s"""
        |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
        |akka.remote.netty.tcp.hostname = "$host"
        |akka.remote.netty.tcp.port = "$port"
      """.stripMargin

    //通过configFactory来解析配置信息
    val config=ConfigFactory.parseString(configStr)

     // 1、创建ActorSystem,它是整个进程中的老大,它负责创建和监督actor
    val workerActorSystem = ActorSystem("workerActorSystem",config)
    // 2、通过actorSystem来创建 worker actor
    val workerActor: ActorRef = workerActorSystem.actorOf(Props(new Worker),"workerActor")

    //向worker actor发送消息
    workerActor ! "connect"
  }
}

③ 运行

使用idea开发工具,配置参数时,多个参数之间用空格隔开****

Scala编程实战

Scala编程实战

启动Master

Scala编程实战

启动Worker

Scala编程实战

  

实战二

使用Akka实现一个简易版的spark通信框架

架构图

 Scala编程实战

具体代码

Master****类

package cn.itcast.spark

  import akka.actor.{Actor, ActorRef, ActorSystem, Props}
  import com.typesafe.config.ConfigFactory
  import scala.collection.mutable
  import scala.collection.mutable.ListBuffer
  import scala.concurrent.duration._

  //todo:利用akka实现简易版的spark通信框架-----Master端
  class Master  extends Actor{
  //构造代码块先被执行
  println("master constructor invoked")

  //定义一个map集合,用于存放worker信息
  private val workerMap = new mutable.HashMap[String,WorkerInfo]()

  //定义一个list集合,用于存放WorkerInfo信息,方便后期按照worker上的资源进行排序
  private val workerList = new ListBuffer[WorkerInfo]

  //master定时检查的时间间隔
  val CHECK_OUT_TIME_INTERVAL=15000 //15秒

  //prestart方法会在构造代码块执行后被调用,并且只被调用一次
  override def preStart(): Unit = {
    println("preStart method invoked")

      //master定时检查超时的worker
    //需要手动导入隐式转换
    import context.dispatcher
    context.system.scheduler.schedule(0 millis,CHECK_OUT_TIME_INTERVAL millis,self,CheckOutTime)
  }

  //receive方法会在prestart方法执行后被调用,表示不断的接受消息
  override def receive: Receive = {
    //master接受worker的注册信息
    case RegisterMessage(workerId,memory,cores) =>{
        //判断当前worker是否已经注册
      if(!workerMap.contains(workerId)){
        //保存信息到map集合中
        val workerInfo = new WorkerInfo(workerId,memory,cores)
        workerMap.put(workerId,workerInfo)

        //保存workerinfo到list集合中
        workerList +=workerInfo
  
        //master反馈注册成功给worker
        sender ! RegisteredMessage(s"workerId:$workerId 注册成功")
      }
    }

      //master接受worker的心跳信息
    case SendHeartBeat(workerId)=>{
      //判断worker是否已经注册,master只接受已经注册过的worker的心跳信息
      if(workerMap.contains(workerId)){
        //获取workerinfo信息
        val workerInfo: WorkerInfo = workerMap(workerId)

        //获取当前系统时间
        val lastTime: Long = System.currentTimeMillis()
  
        workerInfo.lastHeartBeatTime=lastTime
      }
    }

    case CheckOutTime=>{
      //过滤出超时的worker 判断逻辑: 获取当前系统时间 - worker上一次心跳时间 >master定时检查的时间间隔
        val outTimeWorkers: ListBuffer[WorkerInfo] = workerList.filter(x => System.currentTimeMillis() -x.lastHeartBeatTime > CHECK_OUT_TIME_INTERVAL)
      //遍历超时的worker信息,然后移除掉超时的worker
      for(workerInfo <- outTimeWorkers){
        //获取workerid
        val workerId: String = workerInfo.workerId
        //从map集合中移除掉超时的worker信息
        workerMap.remove(workerId)
        //从list集合中移除掉超时的workerInfo信息
        workerList -= workerInfo
        println("超时的workerId:" +workerId)
      }
      println("活着的worker总数:" + workerList.size)
  
      //master按照worker内存大小进行降序排列
     println(workerList.sortBy(x => x.memory).reverse.toList)
    }
  }
}

  object Master{
  def main(args: Array[String]): Unit = {
    //master的ip地址
    val host=args(0)
    //master的port端口
    val port=args(1)
  
    //准备配置文件信息
    val configStr=
      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = "$host"
         |akka.remote.netty.tcp.port = "$port"
      """.stripMargin

    //配置config对象 利用ConfigFactory解析配置文件,获取配置信息
    val config=ConfigFactory.parseString(configStr)
  
    // 1、创建ActorSystem,它是整个进程中老大,它负责创建和监督actor,它是单例对象
    val masterActorSystem = ActorSystem("masterActorSystem",config)
    // 2、通过ActorSystem来创建master actor
    val masterActor: ActorRef = masterActorSystem.actorOf(Props(new Master),"masterActor")
    // 3、向master actor发送消息
    //masterActor ! "connect"
  }
}

② Worker****类

package cn.itcast.spark

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

  //todo:利用akka实现简易版的spark通信框架-----Worker端
  class Worker(val memory:Int,val cores:Int,val masterHost:String,val masterPort:String)  extends Actor{
  println("Worker constructor invoked")

  //定义workerId
  private val workerId: String = UUID.randomUUID().toString

  //定义发送心跳的时间间隔
  val SEND_HEART_HEAT_INTERVAL=10000  //10秒
 
  //定义全局变量
  var master: ActorSelection=_
  
  //prestart方法会在构造代码块之后被调用,并且只会被调用一次
  override def preStart(): Unit = {
    println("preStart method invoked")
    //获取master actor的引用
    //ActorContext全局变量,可以通过在已经存在的actor中,寻找目标actor
    //调用对应actorSelection方法,
    // 方法需要一个path路径:1、通信协议、2、master的IP地址、3、master的端口 4、创建master actor老大 5、actor层级
     master= context.actorSelection(s"akka.tcp://masterActorSystem@$masterHost:$masterPort/user/masterActor")

    //向master发送注册信息,将信息封装在样例类中,主要包含:workerId,memory,cores
    master ! RegisterMessage(workerId,memory,cores)
  }  

  //receive方法会在prestart方法执行后被调用,不断的接受消息
  override def receive: Receive = {
    //worker接受master的反馈信息
    case RegisteredMessage(message) =>{
      println(message)

      //向master定期的发送心跳
      //worker先自己给自己发送心跳
      //需要手动导入隐式转换
      import context.dispatcher
      context.system.scheduler.schedule(0 millis,SEND_HEART_HEAT_INTERVAL millis,self,HeartBeat)
    }

      //worker接受心跳
    case HeartBeat =>{
      //这个时候才是真正向master发送心跳
      master ! SendHeartBeat(workerId)
    }
  }
}
 
  object Worker{
  def main(args: Array[String]): Unit = {
    //定义worker的IP地址
    val host=args(0)
    //定义worker的端口
    val port=args(1) 
    //定义worker的内存
    val memory=args(2).toInt
    //定义worker的核数
    val cores=args(3).toInt
    //定义master的ip地址
    val masterHost=args(4)
    //定义master的端口
    val masterPort=args(5)

    //准备配置文件
    val configStr=
      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = "$host"
         |akka.remote.netty.tcp.port = "$port"
      """.stripMargin 

    //通过configFactory来解析配置信息
    val config=ConfigFactory.parseString(configStr)
    // 1、创建ActorSystem,它是整个进程中的老大,它负责创建和监督actor
    val workerActorSystem = ActorSystem("workerActorSystem",config)
    // 2、通过actorSystem来创建 worker actor
    val workerActor: ActorRef = workerActorSystem.actorOf(Props(new Worker(memory,cores,masterHost,masterPort)),"workerActor")

    //向worker actor发送消息
    workerActor ! "connect"
  }
}

WorkerInfo****类

package cn.itcast.spark

  //封装worker信息
  class WorkerInfo(val workerId:String,val memory:Int,val cores:Int) {
        //定义一个变量用于存放worker上一次心跳时间
      var lastHeartBeatTime:Long=_

  override def toString: String = {
    s"workerId:$workerId , memory:$memory , cores:$cores"
  }
}

样例类

package cn.itcast.spark

  trait RemoteMessage  extends Serializable{}

  //worker向master发送注册信息,由于不在同一进程中,需要实现序列化
  case class RegisterMessage(val workerId:String,val memory:Int,val cores:Int) extends RemoteMessage

  //master反馈注册成功信息给worker,由于不在同一进程中,也需要实现序列化
  case class RegisteredMessage(message:String) extends RemoteMessage

  //worker向worker发送心跳 由于在同一进程中,不需要实现序列化
  case object HeartBeat

  //worker向master发送心跳,由于不在同一进程中,需要实现序列化
  case class SendHeartBeat(val workerId:String) extends RemoteMessage

  //master自己向自己发送消息,由于在同一进程中,不需要实现序列化
  case object CheckOutTime

⑤ 运行

配置参数时,多个参数之间用空格隔开

Scala编程实战

 Scala编程实战

首先启动Master_Spark 

启动work_spark-01

 Scala编程实战

  启动work_spark-02,然后关闭

 Scala编程实战

 观察Master_Spark 输出

 Scala编程实战

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
ES6 新增的数组的方法
给定一个数组letlist\//wu:武力zhi:智力{id:1,name:'张飞',wu:97,zhi:10},{id:2,name:'诸葛亮',wu:55,zhi:99},{id:3,name:'赵云',wu:97,zhi:66},{id:4,na
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这