Scala基础(四)高阶函数、隐式转换、AKKA编程
Scala高阶函数
函数
FP函数式编程中,函数是头等公民。函数的本质都是对象,都是FunctionN的实例(N是输入参数的个数)。函数可以像String、Int对象一样,作为参数传递给方法。
method1(x:Int,y:Int) //需要两个参数 类型都为Int 调用的时候传入两个Int method1(2,3)
method2(p:(Int) =>String) //需要一个参数 类型是函数式类型 输入一个Int返回一个String的函数
作为参数传递给方法
函数作为参数传递给方法:
scala> val a1 =Array(11,22,33)
a1: Array[Int] = Array(11, 22, 33)
scala> val f1 = (x:Int) =>{x * 10}
f1: Int => Int = <function1>
scala> a1.map(f1)
res0: Array[Int] = Array(110, 220, 330)
scala> val f1 = (x:Int) =>x * 10 //由于方法体只有1行,可以省略{}
f1: Int => Int = <function1>
scala> val f1 = (x) =>x * 10 //脱离方法的调用 编译器推导不出数据类型
<console>:11: error: missing parameter type
val f1 = (x) =>x * 10
匿名函数
函数可以省去函数名,作为匿名函数(省去了给函数命令的过程,直接把函数定义在方法中):
scala> a1.map((x:Int) =>x * 10)
res1: Array[Int] = Array(110, 220, 330)
scala> a1.map(x =>x * 10)
res2: Array[Int] = Array(110, 220, 330)
scala> a1.map(_ * 10) //使用_占位符代表传入的参数
res3: Array[Int] = Array(110, 220, 330)
scala> a1.map(element =>element * 10) //对应函数的形参名称自定,见面知意可以提高代码可读性
res4: Array[Int] = Array(110, 220, 330)
scala> a1.map(zz =>zz * 10)//只要符合语法,形参名称无所谓。。。都能正常运行
res5: Array[Int] = Array(110, 220, 330)
柯里化函数
没写错,就是柯里化(不是颗粒化!!!)。柯里化是指将原先接受多个参数的方法转换为多个只有一个参数的参数列表的过程。
m1(x,y,z)---->m2(x)(y)(z)
从现象上看,柯里化有多个参数列表,有多个小括号。好处是实现分步调用函数,提高了代码的灵活性。
scala> :quit
C:\Users\killer>scala
Welcome to Scala 2.11.12 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_241).
Type in expressions for evaluation. Or try :help.
scala> def m1(x:Int,y:Int) = x + y
m1: (x: Int, y: Int)Int
scala> m1(2,3)
res0: Int = 5
scala> def m2(x:Int)(y:Int) = x + y //柯里化的格式
m2: (x: Int)(y: Int)Int
scala> m2(2)(3) //柯里化完整调用
res1: Int = 5
scala> val tmp = m2(2) //末尾要+_下滑线占位符,代表还未结束,否则会报错
<console>:12: error: missing argument list for method m2
Unapplied methods are only converted to functions when a function type is expected.
You can make this conversion explicit by writing `m2 _` or `m2(_)(_)` instead of `m2`.
val tmp = m2(2)
^
scala> val tmp = m2(2)_ //柯里化的分步调用 首先输入x 返回结果是关于y的一个函数
tmp: Int => Int = <function1>
scala> tmp(3) //柯里化的最终调用
res2: Int = 5
scala> def m3(x:Int) = { //使用普通的方法定义格式模拟柯里化的过程
| (y:Int) =>x + y
| }
m3: (x: Int)Int => Int
scala> m3(2) // (y:Int) =>2 + y
res3: Int => Int = <function1>
scala> re //使用tab补全,发现之前用到的res都是已经能使用的全局变量
readBoolean readChar readFloat readLine readShort readf1 readf3 refArrayOps remote res0 res2
readByte readDouble readInt readLong readf readf2 ref reflect require res1 res3
scala> res3(2)
res4: Int = 4
闭包函数
闭包首先是个函数,但是函数的返回值依赖于声明在函数外部的变量。闭包就是指在定义函数的时候 对外部自由变量的捕获过程。
scala> def m1(x:Int,y:Int) = x + y //普通函数 其返回值都是通过入参来决定的
m1: (x: Int, y: Int)Int
scala> def m2(x:Int) = x + y //未定义y就先构建需要使用y的闭包函数,会报错
<console>:11: error: not found: value y
def m2(x:Int) = x + y
^
scala> val y:Int = 10 //先定义外部变量,才能定义需要使用该外部变量的方法
y: Int = 10
scala> def m2(x:Int) = x + y //闭包函数,其返回值除了依赖入参x之外,还依赖一个外部变量y
m2: (x: Int)Int //定义方法时,成功捕获了外部变量y,实现了闭包定义
scala> m2(3)
res5: Int = 13
scala> val y:Int =11 //闭包引用的是外部变量的指针,改变外部变量的数值后闭包函数的结果也会变化
y: Int = 11
scala> m2(3)
res6: Int = 13
Scala隐式转换
先举个普通栗子:
package com.aa.implicitDEMO
object ImplicitCase1{
def main(args: Array[String]): Unit = {
val a:Int = 0
println(a to 5) //中缀调用形式
println(a.to(5)) //后缀调用形式
}
}
运行后:
Range(0, 1, 2, 3, 4, 5)
Range(0, 1, 2, 3, 4, 5)
Process finished with exit code 0
ctrl+鼠标左键定位Int,发现:
package scala
final abstract class Int() extends scala.AnyVal {
def toByte : scala.Byte
def toShort : scala.Short
def toChar : scala.Char
def toInt : scala.Int
def toLong : scala.Long
def toFloat : scala.Float
def toDouble : scala.Double
def unary_~ : scala.Int
//后边还有一大坨方法,都不是to方法
}
跳转到了Int.class:
然而,该类内部并没有任何to方法(按首字母排序的,为了节省篇幅,后边都没有)。。。改变主意,定位to方法,发现跳转到RichInt.class:
package scala.runtime
final class RichInt(val self : scala.Int) extends scala.AnyVal with scala.runtime.ScalaNumberProxy[scala.Int] with scala.runtime.RangedProxy[scala.Int] {
//这里有很多无用内容,节省篇幅
def to(end : scala.Int) : scala.collection.immutable.Range.Inclusive = { /* compiled code */ }
def to(end : scala.Int, step : scala.Int) : scala.collection.immutable.Range.Inclusive = { /* compiled code */ }
}
在开启Scala的命令行:
scala> :implicit -v
/* 69 implicit members imported from scala.Predef */
/* 7 inherited from scala */
//省略一大坨
/* 40 inherited from scala.Predef */
//省略一大坨
/* 22 inherited from scala.LowPriorityImplicits */
//省略一大坨
implicit def intWrapper(x: Int): runtime.RichInt
//省略一大坨
scala>
说明Scala编译器背后悄悄地进行了类型转换。。。这就像C#和Java中在合适的场景下自动int→double(C#和Java是强类型语言。C和C++可以直接操作寄存器,非0→true这种也可以,按照指针进行越界访问也可以,和C#与Java有点不同。。。)。隐式转换本质是scala编译器提供的一种代码纠错的功能。
类调用一个不属于自己的方法(调用其他类的方法)或者调用方法时不给参数,程序代码铁定报错。。。但是,如果有了隐式转换。在编译期间,scala会通过隐式转换让程序可以继续执行,实现纠错。核心就是:定义类、定义方法、定义参数时使用implicit修饰。
用法
隐式参数
调用方法时没有参数列表不再报错,会自动寻找作用域中的隐式变量,尽量填充,实现纠错。但是参数过多、参数过少还是会报错。
scala> :quit
C:\Users\killer>scala
Welcome to Scala 2.11.12 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_241).
Type in expressions for evaluation. Or try :help.
scala> def m1(x:Int) = x +1
m1: (x: Int)Int
scala> m1(1,2) //传参过多会报错
<console>:13: error: too many arguments for method m1: (x: Int)Int
m1(1,2)
^
scala> m1() //缺少参数会报错
<console>:13: error: not enough arguments for method m1: (x: Int)Int.
Unspecified value parameter x.
m1()
^
scala> m1 //没有参数列表会报错
<console>:13: error: missing argument list for method m1
Unapplied methods are only converted to functions when a function type is expected.
You can make this conversion explicit by writing `m1 _` or `m1(_)` instead of `m1`.
m1
^
scala> m1(2) //正常的用法
res3: Int = 3
scala> def m2(implicit x:Int) = x +1 //定义隐式方法
m2: (implicit x: Int)Int
scala> implicit val a:Int = 10086 //定义隐式参数
a: Int = 10086
scala> m2(5)
res4: Int = 6
scala> m2 //没有参数列表不报错
res5: Int = 10087
scala> m2(5,6) //参数过多报错
<console>:14: error: too many arguments for method m2: (implicit x: Int)Int
m2(5,6)
scala> m1() //参数过少报错
<console>:14: error: not enough arguments for method m1: (x: Int)Int.
Unspecified value parameter x.
m1()
隐式方法
实现类型转换
scala> :quit
C:\Users\killer>scala
Welcome to Scala 2.11.12 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_241).
Type in expressions for evaluation. Or try :help.
scala> def m1(x:String) = println()
m1: (x: String)Unit
scala> m1("haha")
scala> def m1(x:String) = println(x)
m1: (x: String)Unit
scala> m1("haha")
haha
scala> m1(12345)
<console>:13: error: type mismatch;
found : Int(12345)
required: String
m1(12345)
^
scala> implicit def intToString(x:Int) = x.toString //定义转换方法,不用管警告
warning: there was one feature warning; re-run with -feature for details
intToString: (x: Int)String
scala> m1(12345) //自动调用隐式方法,不需要手动调用
12345
scala> m1("haha")
haha
调用其它类的方法
package com.aa.implicitDEMO
class Dog {}
class RichDog {
//必须定义在main方法前,否则main方法中找不到该方法,不能使用
def canLearnSkill(skillName: String) = println(s"狗狗能学会${skillName}技能")
}
object HahaImplicit {
//定义个隐式转换方法 让dog变成RichDog
//隐式方法也必须定义在main方法前,否则main方法找不到该方法,会爆红
implicit def dogWrapper(dog: Dog) = new RichDog
}
object ImplicitCase2 {
def main(args: Array[String]): Unit = {
//局部导包
import com.aa.implicitDEMO.HahaImplicit.dogWrapper
val dog = new Dog
dog.canLearnSkill("浑水摸鱼")
}
}
当然必须先定义好隐式方法及相关方法才能在后方的main方法中调用!!!main方法在前会爆红!!!这点和C语言中方法前后位置无所谓的情况不一样!!!
运行后:
狗狗能学会浑水摸鱼技能
Process finished with exit code 0
显然成功调用了其它类的方法。
隐式类
package com.aa.implicitDEMO
import java.io.File
import scala.io.Source
object implicit_Class {
//隐式类
implicit class ImpInt(temp1: Int) {
//隐式类的方法
def add(temp2: Int) = temp1 + temp2
}
implicit class FileLoad(file: File) {
def read() = Source.fromFile(file.getPath).mkString
}
}
object Test {
//局部导包,_代表全部导入
import com.aa.implicitDEMO.implicit_Class._
def main(args: Array[String]): Unit = {
//在当前作用域中寻找,将Int(1)作为变量的类同时具有add方法的类,如有,则执行
println(1.add(2))
//在当前作用域中寻找 将File 作为变量的类同时具有read的方法的类,如有,则执行
println(new File("D:\\datasets\\scala\\1.txt")
.read())
}
}
注意事项
无歧义原则
scala> :quit
C:\Users\killer>scala
Welcome to Scala 2.11.12 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_241).
Type in expressions for evaluation. Or try :help.
scala> def m1(implicit x:Int) = x+1
m1: (implicit x: Int)Int
scala> implicit val a:Int =10086
a: Int = 10086
scala> implicit val b:Int =10010
b: Int = 10010
scala> m1 //作用域中有多个同类型的隐式变量,有歧义,为了保证安全性不能隐式转换
<console>:15: error: ambiguous implicit values:
both value a of type => Int
and value b of type => Int
match expected type Int
m1
^
scala>
显式操作先行原则
scala> def m1(implicit x:Int) =x+1
m1: (implicit x: Int)Int
scala> implicit val a:Int = 10086
a: Int = 10086
scala> m1(2) //隐式变量不允许显式调用方法
res2: Int = 3
集中定义原则
通常把隐式转换参数、隐式转换方法集中定义在object中。在具体的项目中,哪里需要隐式转换,哪里手动导入(局部导包)。
AKKA编程
简介
AKKA和actor的关系:actor是一种并发编程通信的模型:基于actor的消息发送和接收。AKKA是基于actor模型的一个RPC框架(远程过程调用,跨网络实现多进程协同工作),实现进程间、跨网络通信。
RPC在大数据领域的应用:Hadoop自己封装了RPC框架HadoopRPC;Spark1.6前用AKKA,1.6后用Netty;Flink还在用AKKA。
AKKA特点:ActorSystem是创建监督管理actor的核心类,在akka中,不能自己new 创建actor,必须通过ActorSystem创建Actor。
运行jar包的套路:
java -jar xxx.jar [mainClass] args....
实现两个进程间的通信
准备工作
先构建Maven工程并设置pom.xml依赖:
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<encoding>UTF-8</encoding>
<scala.version>2.11.12</scala.version>
<scala.compat.version>2.11</scala.compat.version>
</properties>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.11</artifactId>
<version>2.3.14</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-remote_2.11</artifactId>
<version>2.3.14</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/scala</sourceDirectory>
<testSourceDirectory>src/test/scala</testSourceDirectory>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
<configuration>
<args>
<arg>-dependencyfile</arg>
<arg>${project.build.directory}/.scala_dependencies</arg>
</args>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>reference.conf</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.aa.scala.akka.Worker</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
打包时需要修改主类,才能分别有2种入口的Jar包。
Maven依赖中出现Failed to read artifact descriptor的问题。。。把idea卸载重装后发现并没有任何卵用,各种辣鸡帖子真是误人子弟。。。最终换一个Maven仓库路径(c盘→d盘)后奇迹般解决了。。。
代码实现
Master.scala:
package com.aa.scala.akka
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}
//使用akka实现两个进程间的通信 ---master端
class Master extends Actor{
println("Master主构造器开始执行...")
//preStart是初始化方法,在造器执行之后、receive执行之前执行,且只执行一次
override def preStart(): Unit = {
println("Master的初始化方法开始执行...")
}
//receive方法是akka中actor不断接受消息处理消息的方法,不需要用户再使用while控制
override def receive: Receive ={
//接收worker注册信息
case "hello" => {
println("a client connected......")
//返回注册成功信息
sender ! "ok"
}
}
}
object Master{
def main(args: Array[String]): Unit ={
//主机ip 端口
val host =args(0)
val port =args(1)
//配置参数
val configStr: String =
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)
//首先创建ActorSystem
val masterActorSystem: ActorSystem = ActorSystem.create("masterActorSystem", config)
//由ActorSystem去创建MasterActor
val master: ActorRef = masterActorSystem.actorOf(Props(new Master), "masterActor")
//测试用,给自己发个消息
//master ! "hello"
}
}
Worker.scala:
package com.aa.scala.akka
import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}
//使用akka实现两个进程间的通信 ---worker端
class Worker extends Actor {
override def preStart(): Unit = {
// akka.tcp://masterActorSystem@127.0.0.1:10086
// AKKA提供了一个上下文对象context,可以根据URI地址找到需要通信的actor
val master: ActorSelection = context.actorSelection("akka.tcp://masterActorSystem@127.0.0.1:10086/user/masterActor")
//worker向master发送注册信息
master ! "hello"
}
override def receive: Receive = {
//接收注册成功信息 处理
case "ok" => println("Worker注册成功")
}
}
object Worker {
def main(args: Array[String]): Unit = {
val host = args(0)
val port = args(1)
//1、配置参数
val configStr: String =
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = "$host"
|akka.remote.netty.tcp.port = "$port"
|""".stripMargin
//2、解析配置参数
val config: Config = ConfigFactory.parseString(configStr)
//3、创建ActorSystem
val workerActorSystem: ActorSystem = ActorSystem.create("workerActorSystem", config)
//4、创建workerActor
val worker: ActorRef = workerActorSystem.actorOf(Props(new Worker), "workerActor")
worker ! "hello"
//测试用,给自己发个消息
//worker ! "ok"
}
}
效果测试
可以先自己给自己异步发送个消息验证效果,可以运行后再测试通信。idea右上角:
可以设置传入变量。单个测试成功后,可以测试Worker与Master通讯(此时需要把Worker端口设置为与地址不同的端口,例如:10087,否则会有端口冲突的问题,导致Worker与Master后启动的一方闪退)。成功后可以多次断开并重新运行Worker,Master中输出多次。
事实上,换IP也是可行的,懒得用虚拟机测试了。。。由于Scala和Java都是运行在JVM中,故可以直接用win10本机测试:
在打包好Jar包的路径,shift+鼠标右键打开powershell,或者cmd手动切换到该路径:
java -jar master.jar 127.0.0.1 12321
java -jar worker.jar 127.0.0.1 12321
利用这种方式也可以传递参数。Linux中也可以使用命令行/shell脚本传递参数。
增加心跳检测功能
准备工作
构建项目及依赖。
代码实现
Master.scala:
package com.aa.scala.spark
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.aa.scala.akka.Master
import com.typesafe.config.{Config, ConfigFactory}
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
//需要进行时间单位的操作时候,导入该包
import scala.concurrent.duration._
//使用AKKA实现spark进程间的简易通信 master端
class Master extends Actor{
//定义个集合 保存worker注册信息
// <workerID,WorkerInfo>
private val workerInfoMap = new mutable.HashMap[String, WorkerInfo]()
//定义个集合 保存worker资源信息 便于后续根据资源大小排序 业务问题
private val workerInfoList = new ListBuffer[WorkerInfo]
//preStart是初始化方法 在 构造器执行之后 receive执行之前 执行且执行一次
override def preStart(): Unit = {
// println("Master的初始化方法执行了...")
//todo master启动之后 开始心跳超时检测的功能
/**
* schedule(什么时间首次,间隔时间下次,发给谁,发什么) 定时功能
* schedule(立即0s, 间隔10s, self ,CheckTimeOut)
*/
import context.dispatcher
context.system.scheduler.schedule(0 seconds,10 seconds){
self ! CheckTimeOut
}
}
//receive方法是akka中actor不断接受消息处理消息的方法 不需要用户再使用while控制
override def receive: Receive ={
//接收worker注册信息
case RegisterMessage(workerID,memory,cores) =>{
//判断worker是否已经注册 如果未注册 保存注册信息
if(!workerInfoMap.contains(workerID)){
//构造workerInfo
val info = new WorkerInfo(workerID, memory, cores)
//添加至workerInfoMap中
workerInfoMap.put(workerID,info)
workerInfoList += info
//给worker发送注册成功的信息
println(s"编号ID为:${workerID}的worker,注册成功")
sender ! RegisterSuccess(s"${workerID},注册成功,开始心跳")
}
}
//接收worker的心跳信息
case HeartBeat(workerID) =>{
//判断该worker是否已经注册,如果注册,就把当前时间更新为上次心跳时间
if(workerInfoMap.contains(workerID)){
val info: WorkerInfo = workerInfoMap(workerID)
//获取当前时间
val nowTime: Long = System.currentTimeMillis()
//把当前时间更新为该worker上次心跳时间
info.lastHeartBeatTime = nowTime
}
}
//用于接收自己的信息,进行心跳超时检测
case CheckTimeOut =>{
//todo 系统当前时间 - 上次心跳时间 > 7s 超时
val outTime: ListBuffer[WorkerInfo] = workerInfoList.filter(w => System.currentTimeMillis() - w.lastHeartBeatTime > 7 * 1000)
//遍历超时,剔除
for(o <- outTime){
println(s"超时的worker是:${o.workerID}")
//把超时的worker从集合中剔除
workerInfoMap.remove(o.workerID)
workerInfoList -= o
}
//打印一些信息
println(s"当前存活的worker的个数是:${workerInfoList.size}")
//根据memory的大小倒序进行排序
println(workerInfoList.sortBy(w => w.memory).reverse)
println("-----------------------------------------")
}
}
}
object Master{
def main(args: Array[String]): Unit ={
//主机ip 端口
val host =args(0)
val port =args(1)
//配置参数
val configStr: String =
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)
//首先创建ActorSystem
val masterActorSystem: ActorSystem = ActorSystem.create("masterActorSystem", config)
//由ActorSystem去创建MasterActor
val master: ActorRef = masterActorSystem.actorOf(Props(new Master), "masterActor")
}
}
SparkRemote.scala:
package com.aa.scala.spark
trait SparkRemote extends Serializable {
//根据需求集中定义一些共用的方法
}
//定义样例类 用于worker向master发送注册信息
case class RegisterMessage(workerID:String,memory:Int,cores:Int) extends SparkRemote
//定义样例类 用于master给worker发送注册成功信息
case class RegisterSuccess(msg:String) extends SparkRemote
//定义样例类 用于worker给master发送心跳信息
case class HeartBeat(workerID:String) extends SparkRemote
//定义样例对象 用于master自己给自己发送心跳超时检测信息
case object CheckTimeOut
Worker.scala:
package com.aa.scala.spark
import java.util.UUID
import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.aa.scala.akka.Worker
import com.typesafe.config.{Config, ConfigFactory}
//需要进行时间单位的操作时候,导入该包
import scala.concurrent.duration._
//使用AKKA实现spark进程间的简易通信,worker端
class Worker(var memory:Int,var cores:Int) extends Actor{
//workerID
private val workerID: String = UUID.randomUUID().toString.replaceAll("-", "")
var master: ActorSelection =_
override def preStart(): Unit = {
//akka.tcp://masterActorSystem@127.0.0.1:10086
//AKKA提供了一个上下文对象context,可以根据URI地址找到需要通信的actor
master = context.actorSelection("akka.tcp://masterActorSystem@127.0.0.1:10086/user/masterActor")
//worker向master发送注册信息
master ! RegisterMessage(workerID, memory, cores)
}
override def receive: Receive = {
//接收注册成功信息 处理
case RegisterSuccess(msg) =>{
println(msg)
//todo worker注册成功之后立即开始首次心跳 然后后续指定间隔时间重复心跳 本需求中约定5s钟心跳一次
/**
* schedule(什么时间首次,间隔时间下次,发给谁,发什么) 定时功能
* schedule(立即0s, 间隔5s, master,workerID)
*/
import context.dispatcher
context.system.scheduler.schedule(0 seconds,5 seconds){
//给master发送心跳信息
master ! HeartBeat(workerID)
}
}
}
}
object Worker{
def main(args: Array[String]): Unit = {
val host =args(0)
val port =args(1)
val memory =args(2).toInt
val cores = args(3).toInt
//1、配置参数
val configStr:String =
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = "$host"
|akka.remote.netty.tcp.port = "$port"
|""".stripMargin
//2、解析配置参数
val config: Config = ConfigFactory.parseString(configStr)
//3、创建ActorSystem
val workerActorSystem: ActorSystem = ActorSystem.create("workerActorSystem", config)
//4、创建workerActor
val worker: ActorRef = workerActorSystem.actorOf(Props(new Worker(memory,cores)), "workerActor")
}
}
Workerinfo.scala:
package com.aa.scala.spark
//定义一个普通的scalabean 用于保存每个worker的注册信息 及其它的资源信息
class WorkerInfo(var workerID:String,var memory:Int,var cores:Int) {
//记录上次心跳时间
var lastHeartBeatTime:Long =_
//重写对象的toString方法
override def toString = s"WorkerInfo($workerID, $memory, $cores)"
}
效果测试:
[WARN] [06/15/2021 23:31:15.765] [masterActorSystem-akka.remote.default-remote-dispatcher-6] [akka.tcp://masterActorSystem@127.0.0.1:10086/system/endpointManager/reliableEndpointWriter-akka.tcp%3A%2F%2FworkerActorSystem%40127.0.0.1%3A10087-0] Association with remote system [akka.tcp://workerActorSystem@127.0.0.1:10087] has failed, address is now gated for [5000] ms. Reason: [Disassociated]
当前存活的worker的个数是:1
ListBuffer(WorkerInfo(d4ea6dfb0a094f5da6ad68f7cd1f7af3, 32, 4))
-----------------------------------------
超时的worker是:d4ea6dfb0a094f5da6ad68f7cd1f7af3
当前存活的worker的个数是:0
ListBuffer()
这样就实现了心跳检测。。。TCP通讯默认就有心跳检测。为了确保安全性,工业中使用ModbusTCP写上位机时也经常要写心跳包,确保与下位机的可靠连接。