scala 学习总结

Scala 学习备忘要点

一、scala 基础

  • Scala 中所有的值都是有类型的,包括数值和函数

  • Scala 中 不需要带 分号 ;

  • 中括号代表 [ ] : 泛型

  • “+” ,“ - ” ,“ * ” ,“ / ” 在 Scala 里面都只是 方法

  • 定义方法:

    def 方法名称 (参数列表) 返回类型 方法体

    def add(x:Int,y:Int) :Int = x+y
    
    def add(x:Int,y:Int)(z:Int) :Int = (x+y) * z  // 多参数列表
    
  • 定义函数

    val function = (x:Int ,y:Int ) => Int = x+y

    val f1 = ((a: Int, b: Int) => a + b)
    
    val f2 = (a: Int, b: Int) => a + b
    
    val f3 = (_: Int) + (_: Int)
    
    val f4: (Int, Int) => Int = (_ + _)
    
  • 导入可变属性Map

    import mutable.Map

  • 神奇的下划线 _

    • 可以将方法变成函数
    • 用于一般的表达式,表示 所有
  • 数组

    • 不可变数组Array
    • 可变数组 ArrayBuffer
  • 除了 :+ , 其他的 .+: .::: 等 都会反过来

  • 导入可变的属性可以用这个方法:

    import scala.collection.mutable._
    
  • 要记住HshSet 是无序分布的,这个不是随机的意思,以为他们每次打印出的顺序是一样的

  • 一般情况下 += 什么的用在可变的集合

    ++ ++: : : : 之类的一般用在不可变的集合,会生成新的集合

  • apply 方法 的调用情况了解一下,例子就是 array数组

    val arr = Array("ha","bi","ci")  
    // 这里不需要new一个Array出来,会自动调用apply方法
    
  • 抽象的类里面的抽象方法不需要再用 abstract 修饰了

  • scala 里,单例对象的属性字段和方法都是静态的

  • 把类的class 修饰符 改成 object 就是第一个单例类,所有的方法也都是静态的

  • 伴生对象 和 他的半生类 之间的私有属性 和 私有方法 都说可以互相调用的,公共的更是可以了,所以这两基本就是互通的

  • 类的构造器有主构造器和辅助构造器,在我们调用辅助构造器的时候 他会自动调用主构造器的东西

  • 抽象类中的抽象方法不需要加abstract 关键字

  • 在scala 中的方法体里 可以在定义方法,但是 Java中 不可以

  • 补充知识:修饰类的 可见性范围 private [ 包名 ]

    private [cn] class Animal
    

    [ ] 里的就是表示该类的可见性范围,例如上面的是 在 cn.qphone… 在cn 包下可见

  • 模式匹配元素,元组 (都是可以带守卫的)

    arr match {  
    	case "xx" => xx
    }
    
    
  • 样例类:用于模式匹配的, 样例类可以不new,解析器会自动创建他的伴生对象和apply 方法

    :样例类是一种特殊的类,可用于模式匹配。case class是多例的,后面要跟构造参数,case object是单例的

    case class Person(val name:String)
    //值得注意的是。单例类是不能再类名后加 () 的,本身即是主构造器
    case object TimeOutTask
    
    val arr = Array(Person("Tom"),21,1000,TimeOutTask)  //然后这里面的case class,都不用new
    
  • Option 本身是接口(特质):子类有: some 和 none

    :Option类型样例类用来表示可能存在或也可能不存在的值(Option的子类有Some和None)。Some包装了某个值,None表示没有值

      val names = Map(("tom",80),("jder",44))
    
      val key = "tom"
      var score = names.get("tom") match {
        case Some(value) => value
        case None => 0
      }
    	// 等价于
      score = names.getOrElse(key,0)   // 如果是map 的话可以这样用
    
  • 偏函数:PartialFunction [A , B ] 里的两个参数,第一个是输入的 参数类型 ,第二个是返回类型

    :被包在花括号内没有match的一组case语句是一个偏函数

      def func1: PartialFunction[String, Int] = {
        case "one" => 1
        case "two" => 2
        case _ => -1
      }
      // 等价于
        def func2(num: String) : Int = num match {
        case "one" => 1
        case "two" => 2
        case _ => -1
      }
    

二、Scala 的高级特性

1. 闭包

2.有名函数,匿名函数,方法转换成函数

3.柯里化 (这个感觉用不到,挺鸡肋的一东西,但是底层用的极多),这就是柯里化

  // 定义一个奇怪的方法  (方法体里面包含了一个匿名的函数)
  def m(x:Int) = (y:Int) => x*y

  // 简化的写法             ------------》》   我觉着可以写成  def ma (x:int,y:Int) = x * y
  def ma(x:Int)(y:Int) =x * y
  println(m(1)(3))
  println(ma(1)(3))

4.关于 局部常量 和 函数定义 的一些内容, 不过作用范围不知道是否相同

  // 严格而言,f1 现在是局部常量,类型是匿名函数类型 用 :  定义,函数的声明不能写参数名,要写类型
  val f1 :(Int) => Int = { x => x * 4}
  // 简化一下
  val f1_1 :(Int) => Int = _ * 4

  println(f1(1))

  // 等价于 (这个是函数 用 = 定义,要写参数名和类型)
  val f2 = (x:Int) => x * 4

  // 还可以等价于
  val f3 = (_:Int) * 4

5.0 回顾Java中的 comparable 和 comparator

​ - 底层的数据结构是 红黑二叉树


5.1隐式转换案例:ordering —> 相当于 comparator

- gril 的实体类
			- 简单的赋予其属性
- 准备一个 比较器 的单例类,但是 implicit 修饰的单例对象不能作为顶级类,需要外套一个对象
			- 重写Ordering的 compare 的方法
- 准备一个选美的 类(比较颜值 )
			- choose (隐式参数) 方法,选择谁更美
- main方法:  - 导入隐式对象
			- 准备两个girl 实例
             - 准备选美的实例,并调用其中的方法
             - 显示结果
===========================================================================================     

- 在ordering 里,触发 隐式对象的时候是在 :order.gt(a ,b) 的时候会 触发 比较器 Mycompare 
- 利用隐式参数来将隐式对象传进来,不过要求隐式对象是单例的,即 比较器 是单里的
- 比较器 MyCompare 就是 Ordering 特质的实例

---- 源代码如下

/**
  * Description:scala中的的隐式参数传递演示
  * 隐式参数:
  * ①若一个方法的参数使用implicit来修饰,就是隐式参数
  * ②调用包含隐式参数的方法时,不要显示地传递参数值,前提:实现准备一个与隐式参数类型相同的实例
  * ③调用时,实例会自动传入到包含隐式参数的方法中
  */
//Girl实体类
class Girl(val name: String, val colorValue: Double) {
  override def toString = s"Girl($name, $colorValue)"
}

//准备一个比较器的实例(此处使用Orderring方式,相当于Java中的Comparator)
object Outer {

  //注意:使用implicit修饰的单例对象不能作为顶级类,对象,需要外套一个Object或是Class
  implicit object MyComparator extends Ordering[Girl] {
  
    override def compare(x: Girl, y: Girl): Int = (x.colorValue -y.colorValue).asInstanceOf[Int];
    
  }
}

//准备一个类,用于选美,M:在运行中会被Girl类型来取代,应为指定了比较规则,此处不会编译报错。编译器认为:Girl实例是可比较的
class SelectBeauty[M: Ordering](val girl1: M, val girl2: M) {

  /**
    * 选择一个颜值高的girl
    *
    * 比较器中的比较规则触发执行的时机是:
    * order.gt(girl1, girl2)
    *
    * 下述方法在调用时, 不需要传递参数,前提:准备号一个隐式单例对象即可,运行时会自动传入
    * *
    * 只能将隐式对象自动传入到隐式参数中!!
    *
    * @param order
    * @return
    */
  def choose(implicit order: Ordering[M]): M = if (order.gt(girl1, girl2)) girl1 else girl2

}

object OderringWay extends  App{
  //前提:导入隐式对象,就是Orderring 特质的实例(比较器的实例),就是MyComparator
  import Outer._
  //步骤:
  //①准备两个girl的实例

  val my:Girl = new Girl("曼玉",78.89)
  val yc:Girl = new Girl("宇春",70.99)
  //②构建SelectBeauty的实例
  val  beauty = new SelectBeauty(yc,my)

  //③调用实例中的选美方法,获得要给颜值高的girl显示
  val highColorGirl:Girl = beauty.choose

  //④显示结果
  println("高颜值的女该信息是:\n"+highColorGirl)

}

5.2隐式转换案例:ordered — > 相当于 comparable

- girl 的实体类
- 准备一个 object ,在里面写 隐式转换函数(thisGirl:Girl):(每部要重写compare(thatGirl:Girl)方法)
- 选美的类[ ] , 在类后面对[ T <% Ordered[T] ] 里放了个 视界 
	-(解释:A <% B 表示类型变量A 必须是 类型B`	的子类,或者A能够隐式转换到B)
	- 这次里面的方法不需要写 隐式参数
- main - 同上
==========================================================================================

- 在ordered 这里,触发 隐式函数的时机是在 new girl 的时候

---- 源代码如下

// Description:scala中的的隐式参数传递演示
//Girl实体类
class Girl(val name: String, val colorValue: Double) {
  override def toString = s"Girl($name, $colorValue)"
}

object ImplicitChange {

  /**
    * 下述隐式转换函数自动触发执行的时机是:当new了一个Girl的实例时
    */
  implicit def autoChangeGirlToOrdered(thisGirl: Girl) = new Ordered[Girl] {
    //本质:new的是特质Ordered匿名的实现类的实例
    /**
      * 方法中传入的当前girl的实例与别的实例进行比较
      */
    override def compare(thatGirl: Girl): Int = (thisGirl.colorValue - thatGirl.colorValue).asInstanceOf[Int]
  }

}

/**
  * 选美的类
  */
//视界定义: A <% B ,表示类型变量A 必须是 类型B`的子类,或者A能够隐式转换到B
class SelectBeauty[T <% Ordered[T]](val girl1: T, val girl2: T) {
  /**
    * 选美 (注意:下述方法没有使用隐式参数)
    * girl1 > girl2会去调用compare方法
    */
  def choose = if (girl1 > girl2) girl1 else girl2
}

object OderedWay extends App {
  //前提:导入隐式转换方法(函数)
  import ImplicitChange._

  //步骤:
  //①准备两个girl的实例

  val my: Girl = new Girl("曼玉", 50.9999)
  val yc: Girl = new Girl("宇春", 60.45)

  //②构建SelectBeauty的实例
  val beauty = new SelectBeauty(my, yc)

  //③调用实例中的选美方法,获得要给颜值高的girl信息
  val highColorGirl: Girl = beauty.choose

  //④显示结果
  println("~~>高颜值的女该信息是:\n" + highColorGirl)

}

6. 泛型类

6.1 协变

定义一个类型List[+A],如果A是协变的,意思是:对类型A和B,A是B的子类型,那么List[A]是List[B]的子类型。

6.2 逆变

定义一个类型Writer[-A],如果A是逆变的,意思是:对类型A和B,A是B的子类型,那么Writer[B]是Writer[A]的子类型。

6.3 上界

​ 上界定义: T <: A ,表示类型变量T 必须是 类型A 子类

6.4 下界

语法 B >: A 表示参数类型或抽象类型 B 须是类型A的父类。通常,A是类的类型参数,B是方法的类型参数。

6.5 视界 (已过时)

​ 视界定义: A <% B ,表示类型变量A 必须是 类型B`的子类,或者A能够隐式转换到B

6.6 上下文界定

上下文界定的形式为 T : M, 其中M 必须为泛型类, 必须存在一个M[T]的隐式值.

三、Actor 编程

1 定义 :

基于事件模型的并发机制,Scala是运用消息(message)的发送、接收来实现多线程的

2. Java 线程 与 actor 的区别

1539874250712

3. Actor 方法的执行顺序

- start() 开始线程
- act() 方法在线程启动之后会被执行
- 最后向Actor 发送消息 
	-发送消息的方式:  !(异步无返回值), !? (同步等待返回值) ,!!  (异步,返回值:Future[Any])

4. Actor 实战要点

- 可以用单例对象继承 Actor, 然后重写 act()方法
- recive  和 react 都是偏函数,react 会复用线程,比recive 更加的高效	
- loop 适合与 react 搭配着使用

5. apply 方法的作用:

//强行让子线程现执行,若是不调用apply()方法,主线程并不会等待子线程返回消息,会直接执行后面的代码

6. 线程的同步异步消息特点 ,内部运作流程

  //步骤:
    //准备线程的实例
    val thread = new MyThread
    //启动线程
    thread.start
    //Ⅰ - 发送异步消息 ,特点:主线程发送完毕后,继续执行后续的代码,主线程没有阻塞
    //向子线程发送异步消息(没有返回值)
        thread ! AnsyMsg(1,"你好!请为我做xxx")
       println("已经向子线程发送完异步消息了。。。。")

    println("\n________________________________________\n")

    // Ⅱ - 1 主线程向子线程发送同步消息,消息类型是: 样例类 ,建议消息类型多使用样例类,
    // 特点: 主线程将消息发送完毕之后,会处于阻塞状态,等待子线程执行完毕,子线程执行完毕后,主线程才会执行后续的代码
    val accpetMsg = thread !? AnsyMsg(1, "你好!请为我做xxx")
    println(s"主线程收到了来自子线程反馈过来的消息$accpetMsg")

    // Ⅱ - 2 主线程向子线程发送同步消息,消息的类型是  元组 (key:Long, value: AnsyMsg样例类类型),
    // 特点: 主线程将消息发送完毕之后,会处于阻塞状态,等待子线程执行完毕,子线程执行完毕后,主线程才会执行后续的代码
    //与上述的不同之处:若是子线程返回的信息采用的是异步发送的方式,主线程有可能没有收到结果,与上述样例类的消息不同
    val accpetMsg = thread !? (2000L, AnsyMsg(1, "你好!请为我做xxx"))
    Thread.sleep(3000)
    println(s"主线程收到了来自子线程反馈过来的消息$accpetMsg")

    println("\n________________________________________\n")

    // Ⅲ  主线程向子线程发送带返回值的异步消息
    // 特点:若是主线程中没有调用apply方法,发送完毕后,继续执行后续的代码,主线程没有阻塞
    //          若是主线程中消息发送完毕后,调用了apply方法,先执行子线程,子线程执行完毕之后,然后再执行主线程,若子线程有返回值,返回值就是apply方法的返回值。
    val msg: Future[Any] = thread !! SyncMsg(444, "哥们你好,吃饱了?")
    println(msg.isSet)
    println(msg)
    println("消息已经发送完毕\n\n")

    //apply: 强行让子线程先执行完毕
    val realRsult = msg.apply()
    println(realRsult)

    println(msg.isSet)
    println(msg)
    println("消息已经发送完毕")

  }

7. WordCount 案例 :

  • 教科书
object ActorExercise {
  def main(args: Array[String]): Unit = {
    //准备目录(或者是目录下的多个文件的路径)
    val files = Array("data/words.log", "data/words.txt", "data/words2.txt")
       //准备一个ListBuffer可变集合,用于存储所有文件中单词出现的总次数
    val container: ListBuffer[MiddleResult] = ListBuffer[MiddleResult]()
    //通过循环来计算各个文件单词出现的总次数
    for (file <- files) {
      //循环体:
      //每循环一次,构建一个全新的线程实例,求出每一个文件中单词出现的总次数,并存入到ListBuffer集合中
      //创建线程实例
      val thread = new Task
      thread.start()

      //发送带返回值的异步消息
      val fut = thread !! SubmitTask(file)
      val perResult: MiddleResult = fut.apply().asInstanceOf[MiddleResult]  // 这里要做一个转换
      //将结果存入容器中
      container += perResult
    }

    //分析ListBuffer,将所有文件中单词的总次数再次聚合起来,并显示结果
    //container.foreach(println)
    //  1 + 2 + 3
    //foldLeft(0) :0 和的初始值
    //(x:Int,y:(String,Int))=>x+y._2) : x ~>每次累加的和; y: ListBuffer中的每一个元组
    //println(container.flatMap(_.result).groupBy(_._1).mapValues(_.foldLeft(0)((x:Int,y:(String,Int))=>x+y._2)))
    //println(container.flatMap(_.result).groupBy(_._1).mapValues(_.foldLeft(0)(_+_._2)))
    container.flatMap(_.result).groupBy(_._1).mapValues(_.foldLeft(0)(_+_._2)).toList.sortBy(_._2).reverse.foreach(x=>println(x._1+"\t"+x._2))
  }
}

//设计一个样例类MiddleResult,用来存储每个文件中单词出现的总次数
case class MiddleResult(result: Map[String, Int])   // 这里定义的时候经常会出错
//设计一个样例类SubmitTask, 用作主线程向子线程发送的消息
case class SubmitTask(file: String)  // 这个可以不设


/**
 *  设计要给线程类Task,用于计算指定文件中单词出现的总次数,计算完毕后,向主线程反馈结果
 */
class Task extends Actor {
  override def act(): Unit = {
    loop {
      react {
        case SubmitTask(file) => {
          val lst: List[String] = Source.fromFile(file).getLines.toList
          val nowResult: Map[String, Int] = lst.flatMap(_.split(" "))
     .filter(_.trim != "").map((_, 1)).groupBy(_._1).map(t => (t._1, t._2.size))
          sender ! MiddleResult(nowResult)   //  发送的返回值要用样例类包起来
        }
      }
    }
  }
}
  • 精简版
class MyActor extends Actor {
  override def act(): Unit = {
    loop {
      react{
        case file:String => {
          val map:Map[String, Int] = Source.fromFile(file).getLines().flatMap(_.split(" ")).filter(_.trim != "").toList.map((_,1)).groupBy(_._1).map((t => (t._1,t._2.size)))//.toList.sortBy(_._2).reverse
          sender ! Tuple(map)
        }
        case _ => println("msg 错误")
      }
    }
  }
}

case class Tuple (map:Map[String,Int])

object Word_count {
  def main(args: Array[String]): Unit = {
    var list = ListBuffer[Tuple]()
    val files = List("E:\\1.txt","E:\\2.txt","E:\\3.txt")
    for(file <- files ) {
      val task = new MyActor
      task.start()
      val msg = task !! file
      val list2 = msg.apply().asInstanceOf[Tuple]  // 想要解决这个类型转换错误的问题还需要将上面传过来的值给封装一下,封装成样例类
      list.append(list2)
    }
//  list.flatMap(_.map).groupBy(_._1).mapValues(_.foldLeft(0)( (x:Int,y:(String,Int)) => x + y._2)).toList.sortBy(_._2).reverse.foreach(println)
  list.flatMap(_.map).groupBy(_._1).mapValues(_.foldLeft(0) (_ + _._2) ).toList.sortBy(_._2).reverse.foreach(println)
  }
}

四、Scala 编程实战: Akka 构建简易的 spark 框架

  • Akka 定义:
基于Actor模型,提供了一个用于构建可扩展的(Scalable)、弹性的(Resilient)、快速响应的(Responsive)应用程序的平台。(最底层是nety)
  • akka 涉及的一些核心API
- Configuration
- Config
- ActorSystem
- Props
- Actor

- Scheduler
- Actor从父类继承过来的一个属性context

- ActorRef
- ActorSelection -> 在Worker进程中获得对应的Master进程的实例
自定义spark框架
  • Master
- 样例类: 
			//Worker进程注册信息样例类
		  - case class RegistWorker(id:String,host:String,memory:Long,coreNum:Int)  
			//Worker进程已注册的信息样例类
		  - case class RegistedWorker(id: String)  
			//超时时间的样例类 信息
		  - case object TimeOutMsg 
			//容器,存储所有Worker进程的信息
		  - case class WorkerInfo(id:String,host:String,memory:Long,coreNum:Int){
 				var lastSendHeartBeatTime:Long = 0L // 封装最后一次心跳包的时间 }
- class Master extends Actor {
    - mutable.Map
    - mutable.Set
    - override def preStart():Unit {  // 线程初始化处理
        - import context.dispatcher  // 启动定时器,用于自检   
        	//dispatcher翻译:调度程序  scheduler翻译:调度器
        - context.system.scheduler.schedule( 		// 下面是参数
           		  - FiniteDuration(0,TimeUnit.SECONDS), // 第一次启动定时器的时候,延长多少时间启动
      			  - FiniteDuration(3,TimeUnit.SECONDS), // 以后定时器每隔多长时间启动一次
        		  - self,   // 是当前Actor 实例的代理对象 --- 类似明星的经济人
      			  - TimeOutMsg    // 样例类,要发送的消息    
        )
    } 
    
    - override def receive():Unit { // 线程体的具体逻辑,会被后台的循环体调用
         - case RegistWorker //判断容器中是否存在Worker,不存在就添加到容器中
         - case HeartBeatMsg //来自Worker心跳包信息,从容器中取出,给Worker的最后发送心跳包时间属性赋值
         - case TimeOutMsg//Maste给自己发送的,检测目前能正常工作的Worker进程的消息
        		- 这里面会 计算 当前时间与最后一次发送心跳包的时间差, 若 > 3,则Worker宕机,从容器中移除
    } 
}
              
object Master  {
 def main(args: Array[String]): Unit = {
        - 准备ActorSystem的实例,负责创建和监督线程
     		- host
     		- port
       		- configStr
     		- config
     		- actorSystem
     
        - 创建Actor线程的实例
 		 actorySytem.actorOf(Props[Master],"Master")
      	- 打印显示进程已启动
 }
}
  • Worker
- case class HeartBeatMsg(id:String) //心跳包样例类
class Worker extends Actor{
	- Master 的属性
    - 线程的ID
    
    - 线程的实例初始化处理
    override def preStart():Unit ={
        - 获得Master的进程实例
        - worker 进程向Master 发送注册信息
    }
    
    - 线程实例初始化完毕之后,执行下述的receive方法,也是会被后台的循环体调用
    override def receive: Receive = {
        - case RegistedWorker(id) =>{..} // 注册过得进程样例类,(启动定时器)
        - case HearBeattMsg(id) =>{..}// 心跳包样例类,里面要向Master发送心跳包
    }
}
object Woker {
  def main(args: Array[String]): Unit = {
        - 准备ActorSystem的实例,负责创建和监督线程
     		- host
     		- port
       		- configStr
     		- config
     		- actorSystem
     
        - 创建Actor线程的实例
 		 actorySytem.actorOf(Props[Worker],"Worker")    
      	- 打印显示进程已启动
  }    
}
  • 源代码如下:
  • Master
package cn.qphone.scala.Akka

import java.util.concurrent.TimeUnit

import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}

import scala.collection.mutable
import scala.concurrent.duration.FiniteDuration

/**
  * description :Master 进程,负责计算资源调度的进程
  *
  * @author 王友俊
  */
// 定义样例类
// Worker 进程注册信息样例类
case class RegistWorker(id:String,host:String,memory:Long,coreNum:Int)

// Worker 进程 已经 注册的信息样例类
case class RegistedWorker(id: String)

// 超时时间的样例类信息
case object TimeOutMsg

// 准备两个容器,存储所有的Worker进程的信息
//WorkerInfo
case class WorkerInfo(id:String,host:String,memory:Long,coreNum:Int){
  // 封装了最后一次发送心跳包的 时间(Worker to Master)
  var lastSendHeartBeatTime:Long = 0L
}


class Master extends Actor{

  // Map
  val container:mutable.Map[String,WorkerInfo] = mutable.HashMap[String,WorkerInfo]()
  //Set
  val containerSet:mutable.Set[WorkerInfo] = mutable.HashSet[WorkerInfo]()

  // 线程初始化处理
  override def preStart(): Unit = {

    // 启动定时器,用于自检(检查有没有宕机的Worker 进程信息)
    import context.dispatcher

    context.system.scheduler.schedule( // 这里面填的参数可以直接点进去schedule() 源代码查看
      FiniteDuration(0,TimeUnit.SECONDS), // 第一次启动定时器的时候,延长多少时间启动
      FiniteDuration(3,TimeUnit.SECONDS), // 以后定时器每隔多长时间启动一次
      self,   // 是当前Actor 实例的代理对象 --- 类似明星的经济人
      TimeOutMsg    // 样例类,要发送的消息
    )
  }
  // 线程体具体的逻辑,下面写的逻辑会被后台的循环体来调用
  override def receive: Receive = {
    // 来自Worker进程注册的消息
    case RegistWorker(id,host,memory,coreNum) => {
      // 判断容器中是否不存在该Worker,不存在就添加到容器中
      if(!container.contains(id)) {
        val workerInfo:WorkerInfo = WorkerInfo(id,host,memory,coreNum)
        container(id) = workerInfo  // 向 Map 中添加
        containerSet.add(workerInfo)  // 向 Set 中添加
        sender ! RegistedWorker(id)  // master 向 worker 进程反馈已注册的进程的信息
      }
    }
    // 来自Worker进程发送过来的心跳包的信息
    case HeartBeatMsg(id) => {
      try{
        val info:WorkerInfo = container.getOrElse(id,null)    // 从容器中取出心跳包中Worker进程的消息
        info.lastSendHeartBeatTime = System.currentTimeMillis()  // 给Worker进程的属性最后一次发送心跳包的时间赋值
      } catch {
        case e : NullPointerException =>println("容器中已经没有正在运行的进程了")
        case _ : Exception => println("未知错误,请检查你的机器")
      }

    }
    //Master进程给自己发送的用于检测目前能正常运作的的Worker进程的消息
    case TimeOutMsg => {
      println("TimeOurMsg 开始执行,当前容器中进程数为:" + container.size)
      val nowTime = System.currentTimeMillis()
      // 从容器中获取所有超时的Worker进程的信息取出来
      //若当前时间与Worker进程最后一次向主线程Master发送心跳包的时间差 > 3 ,此时,证明该Worker进程宕机。从容器中移除
      val willDelWorkers:Array[WorkerInfo] = containerSet.filter(nowTime - _.lastSendHeartBeatTime > 3*1000).toArray

      for (work <- willDelWorkers) {
        // 从 Map 中删除
        container.remove(work.id)
        // 从 Set 中删除
        containerSet.remove(work)
        println("将宕机的进程" + work.id + "删除...")
      }
    }
  }
}
object Master{
  def main(args: Array[String]): Unit = {
    // 步骤
    // 1. 准备ActorSystem的实例,这玩意是老大,负责创建 和 监督 线程
    val host = "127.0.0.1"
    val port = "9000"
    val configStr =
      s"""
                      |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
                      |akka.remote.netty.tcp.hostname = "$host"
                      |akka.remote.netty.tcp.port = "$port"
       """.stripMargin
    val config:Config = ConfigFactory.parseString(configStr)
    val actorySytem:ActorSystem = ActorSystem.create("MasterActorSystem",config)

    // 2. 构建Actor线程的实例---
    actorySytem.actorOf(Props[Master],"Master")

    // 3. 显示信息,表示Master进程已经启动
    println("Master 进程已启动 ...")
  }
}

  • Worker
package cn.qphone.scala.Akka

import java.util.UUID
import java.util.concurrent.TimeUnit

import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}

import scala.concurrent.duration.FiniteDuration

/**
  * description : worker 进程,是负责计算的进程
  *
  * @author 王友俊
  */
// 定义样例类
// Worker进程想Master进程发送的心跳包消息样例类
case class HeartBeatMsg(id:String)

class Worker extends Actor{
  // Maste 类型的属性
  var master:ActorSelection = null
  // 线程的ID,( UUID 可保证不重复)
  val id = UUID.randomUUID().toString

  // 线程实例初始化处理
  override def preStart(): Unit = {
    // 用来获得Master的进程实例  // 方法需要一个path路径:1、通信协议、2、master的IP地址、3、master的端口 4、创建master actor老大 5、actor层级
    master = context.system.actorSelection("akka.tcp://MasterActorSystem@127.0.0.1:9000/user/Master")
    // Worker进程想Master发送注册信息      //RegistWorker(id: String, host: String, memory: Long, coreNum: Int)
    master ! RegistWorker(id,"127.0.0.1",4,8)
  }
  // 线程实例初始化完毕之后,执行下述的receive方法,该方法会被后台得循环体调用
  override def receive: Receive = {
    // 注册过得Worker进程样例类(启动定时器)
    case RegistedWorker(id) => {
      import context.dispatcher
      context.system.scheduler.schedule(FiniteDuration(0,TimeUnit.SECONDS),FiniteDuration(4,TimeUnit.SECONDS),self,HeartBeatMsg(id))
    }
    // 心跳包样例类
    case HeartBeatMsg(id) => {
      // 向Master 发送 心跳包
      master ! HeartBeatMsg(id)
      println("worker 已发送心跳包...")
    }
  }

}

object Worker{
  def main(args: Array[String]): Unit = {
    // 步骤
    // 1. 准备ActorSystem的实例,这玩意是老大,负责创建 和 监督 线程
    val host = "127.0.0.1"
    val port = "4402"
    val configStr =
      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = "$host"
         |akka.remote.netty.tcp.port = "$port"
       """.stripMargin
    val config:Config = ConfigFactory.parseString(configStr)
    val actorySytem:ActorSystem = ActorSystem.create("WorkerActorSystem",config)

    // 2. 构建Actor线程的实例---
    actorySytem.actorOf(Props[Worker],"Worker")

    // 3. 显示信息,表示Master进程已经启动
    println("Worker 进程已启动 ...")
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值