1.创建Maven工程
maven工程中引入scala,akka,和akka联机模式的依赖
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<encoding>UTF-8</encoding>
<scala.version>2.12.10</scala.version>
<scala.compat.version>2.12</scala.compat.version>
<akka.version>2.4.17</akka.version>
</properties>
<dependencies>
<!-- scala的依赖 -->
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
<!-- akka actor依赖 -->
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.12</artifactId>
<version>${akka.version}</version>
</dependency>
<!-- akka远程通信依赖 -->
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-remote_2.12</artifactId>
<version>${akka.version}</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<!-- 编译scala的插件 -->
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<!-- 编译java的插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>add-source</goal>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 打jar插件 -->
<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>
<!-- 指定maven方法 -->
<!-- <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">-->
<!-- <mainClass>cn._51doit.rpc.Master</mainClass>-->
<!-- </transformer>-->
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
2.构建scala工程
创建一个scala的包,然后将此包设置为source root
主要流程
Master
主要职责
1启动后每隔15秒,检查结点存活情况
2接收worker的心跳机制,更新存活时间戳
3维护结点存活信息
package com.dt.rpc
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}
import scala.concurrent.duration._
import scala.collection.mutable
class Master extends Actor {
//定义一个map,在启动的时候就开始定时检查检查
//当注册的时候要把这个结点的注册信息保留下来放到一个map中
override def preStart(): Unit = {
import context.dispatcher //关闭隐式转换
//启动之后还是每15秒发送检查消息
context.system.scheduler.schedule(0 millisecond,15000 millisecond,self,CheckHeart)
}
private val workmap = new mutable.HashMap[String,WorkerInfo]()
override def receive: Receive = {
//模式匹配字符串,接收到消息就执行了,说明这里的是直接调用了方法
case "connect"=>{
println("has one connected")
sender()!"response" //注意此处的发送是! 没有等号
}
//接收注册信息
case RegisterInfo(id,workerMemory,workerCores) =>{
//todo 将注册信息添加到内存中
val l: Long = System.currentTimeMillis()
workmap(id) = new WorkerInfo(id,workerMemory,workerCores)
//todo 并回消息 success
sender() ! "success"
}
//更新心跳机制
case HeartBeat(id,time)=>{
//todo 收到心跳之后将心跳更新到存活队列中
if (workmap.contains(id)){
//如果存在,就将时间更新进去
val info: WorkerInfo = workmap(id)
info.lastHeartbeatTime=time//更新进去时间
}
}
case CheckHeart=>{
//检查15秒没有发送消息的
val stringToInfo: mutable.HashMap[String, WorkerInfo] = workmap.filter(x=>System.currentTimeMillis()-x._2.lastHeartbeatTime>=15000)
stringToInfo.foreach(x=>workmap -=x._1)//将map中移除掉过滤出来的超时对象
}
println(s"当前存活的结点个数有"+workmap.size)
}
}
object Master{
val MASTER_ACTOR_SYSTEM="MASTER_ACTOR_SYSTEM"
val MASTER_ACTOR="MASTER_ACTOR"
val HOSTNAME="localhost"
val PORT=8888
def main(args: Array[String]): Unit = {
//指定配置
val confStr=
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = $HOSTNAME
|akka.remote.netty.tcp.port = $PORT
""".stripMargin
//通过stripMargin切分出,配置
val config: Config = ConfigFactory.parseString(confStr)
//使用配置工厂返回配置
val actorSystem: ActorSystem = ActorSystem(MASTER_ACTOR_SYSTEM,config)
actorSystem.actorOf(Props[Master],MASTER_ACTOR)
//todo 启动完成之后,直接启动定时任务,检查队列
}
}
Worker
主要职责
1.启动后向主节点发送消息
2.得到回复之后将自己的结点信息返回给master
3.每隔10秒向master发送心跳
package com.dt.rpc
import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}
import scala.concurrent.duration._
/**
* 功能:启动之后向master注册,收到master的相应之后,开始每隔10秒发送心跳通知自己发送消息
* @param id
* @param workerMemory
* @param workerCores
*/
class Worker(id:String,workerMemory:Int,workerCores:Int) extends Actor{
var selection: ActorSelection = _
//在启动之前向master发送消息
override def preStart(): Unit ={
selection= context.actorSelection(s"akka.tcp://${Master.MASTER_ACTOR_SYSTEM}@${Master.HOSTNAME}:${Master.PORT}/user/${Master.MASTER_ACTOR}")
//指定链接,链接格式akka.tcp://master的名字@ip地址:端口号/user/master名字
//直接通过actor通道返回消息,返回注册信息
selection !RegisterInfo(id,workerMemory,workerCores)
}
override def receive: Receive ={
case "success"=>{
//连接成功之后触发定时任务
import context.dispatcher //关闭隐式转换
//从零秒开始,每隔10秒发送给自己一个标志信息
context.system.scheduler.schedule(0 microsecond,10000 microsecond,self,CallSelfSend)
}
//发送消息到master中
case CallSelfSend=> selection ! HeartBeat(id,System.currentTimeMillis())
}
}
object Worker{
def main(args: Array[String]): Unit = {
val id:String =args(0) //机器id
val host:String=args(1) //本机ip
val port:Int=args(2).toInt//端口
val name:String=args(3) //name 留空
val workerMemory = args(4).toInt
val workerCores = 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
//指定worker的运行端口
val config: Config = ConfigFactory.parseString(configStr)
val worker_Actor_System: ActorSystem = ActorSystem("WORKER_ACTOR_SYSTEM",config)
//创建一个actorsystem的类
worker_Actor_System.actorOf(Props(new Worker(id,workerMemory,workerCores)))
//创建一个actor
}
}
ObjectClass
package com.dt.rpc
//用来返回成功消息的封装类
case object RegistedInfo
//worker通知自己发送心跳机制
case object CallSelfSend
//master检查存活
case object CheckHeart
CaseClass样例类
package com.dt.rpc
//worker注册信息
case class RegisterInfo(id:String,memory:Int,cores:Int)
//心跳机制
case class HeartBeat(id:String,time:Long)
//master内存维护
class WorkerInfo(val id: String, var memory: Int, cores: Int) {
var lastHeartbeatTime: Long = _
}
效果
先启动master,然后启动worker
Master日志
worker日志
当master启动后,存活结点为0,接着worker启动后注册,结点变成1,当worker停止之后,master存活节点变成0