Akka編寫RPC通信框架,模擬Worker連接Master小案例

指導思想:

1、利用RPC通信框架(AKKA)
2、定義2個類Master、Worker


-------------------------------------------------------------------------------------------------------------------------------
首先啓動Master,然後啓動所有的Worker
1、Worker啓動後,在PreStart方法中與Master建立連接,向Master發送註冊,將Worker的信息通過case class封裝起來發送給Master。
2、Master接受到Worker的註冊消息後將Worker的信息保存起來,然後向Worker反饋註冊成功。
3、Worker定期向Master發送心跳,目的是爲了報告存活狀態。
4、Master會定時清理超時的Worker。

--------------------------------------------------------------------------------------------------------------------------------

首先打開Idea軟件,建立maven項目:


編寫Pom文件,Pom文件的代碼如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.allengao.akka</groupId>
    <artifactId>my-rpc</artifactId>
    <version>1.0</version>

    <properties>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
        <encoding>UTF-8</encoding>
        <scala.version>2.10.6</scala.version>
        <scala.compat.version>2.10</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.10</artifactId>
            <version>2.3.14</version>
        </dependency>

        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-remote_2.10</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>-make:transitive</arg>
                                <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>cn.allengao.rpc.Worker</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>


RemoteMessage的代碼如下:


package cn.allengao.rpc

/**
 * class_name: 
 * package: 
 * describe: TODO
 * creat_user: Allen Gao
 * creat_date: 2018/1/18
 * creat_time: 16:36
 **/

trait RemoteMessage extends Serializable

//Worker -> Master
case class RegisterWorker(id: String, host: String, port: Int, memory: Int, cores: Int) extends RemoteMessage

case class Heartbeat(workId: String) extends RemoteMessage


//Master -> Worker
case class RegisteredWorker(masterUrl: String) extends RemoteMessage

//Worker -> self
case object SendHeartbeat

// Master -> self
case object CheckTimeOutWorker


WorkerInfo的代碼如下:


package cn.allengao.rpc

/**
 * class_name:
 * package:
 * describe: 把worker啓動的信息封裝起來,並且傳給master。
 * creat_user: Allen Gao
 * creat_date: 2018/1/18
 * creat_time: 16:37
 **/

class WorkerInfo(val id: String, val host:String, val port: Int, val memory: Int, val cores: Int) {

  //TODO 上一次心跳
  var lastHeartbeatTime : Long = _
}


Master的代碼如下:


package cn.allengao.rpc

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

import scala.collection.mutable
import scala.concurrent.duration._

/**
 * class_name: 
 * package: 
 * describe: TODO
 * creat_user: Allen Gao
 * creat_date: 2018/1/18
 * creat_time: 16:36
 **/
class Master(val host: String, val port: Int) extends Actor {

  // workerId -> WorkerInfo
  val idToWorker = new mutable.HashMap[String, WorkerInfo]()
  // WorkerInfo
  val workers = new mutable.HashSet[WorkerInfo]() //使用set刪除快, 也可用linkList
  //超時檢查的間隔
  val CHECK_INTERVAL = 15000

//preStart方法在構造器執行構造方法以後,receive執行方法之前執行,並且只執行一次。
  override def preStart(): Unit = {
    println("preStart invoked")
    //導入隱式轉換,在preStart啓動一個定時器,用於週期檢查超時的Worker
    import context.dispatcher //使用timer太low了, 可以使用akka的, 使用定時器, 要導入這個包
    context.system.scheduler.schedule(0 millis, CHECK_INTERVAL millis, self, CheckTimeOutWorker)
  }

  // 用於接收消息,receive方法循環執行。
  override def receive: Receive = {
    case RegisterWorker(id, host, port, memory, cores) => {
      //判斷一下,是不是已經註冊過
      if(!idToWorker.contains(id)){
        //把Worker的信息封裝起來保存到內存當中
        val workerInfo = new WorkerInfo(id, host, port, memory, cores)
      
        idToWorker += (id -> workerInfo)    //key是id,value是workInfo
        workers += workerInfo
        println("a worker registered")
//        sender ! RegisteredWorker(s"akka.tcp://MasterSystem@$host:$port/user/Master")
        sender ! RegisteredWorker(s"akka.tcp://${Master.MASTER_SYSTEM}" +
     s"@$host:$port/user/${Master.MASTER_ACTOR}")//通知worker註冊
      }
    }
    case Heartbeat(workerId) => {
      if(idToWorker.contains(workerId)){
        val workerInfo = idToWorker(workerId)
        //報活
        val current_time = System.currentTimeMillis()
        workerInfo.lastHeartbeatTime = current_time
      }
    }

    case CheckTimeOutWorker => {
      val currentTime = System.currentTimeMillis()
      val toRemove : mutable.HashSet[WorkerInfo] = workers.filter(w => currentTime - w.lastHeartbeatTime > CHECK_INTERVAL)
  //    for(w <- toRemove) {
   //     workers -= w
   //     idToWorker -= w.id
    //  }
      toRemove.foreach(deadWorker =>{
     idToWorker -= deadWorker.id
     workers -= deadWorker
     })
      println("num of workers " + workers.size)
    }
  }
}

object Master {
  //聲明兩個變量MaterSystem和MasterActor
  val MASTER_SYSTEM =  "MasterSystem"
  val MASTER_ACTOR = "Master"

  def main(args: Array[String]) {

    //解析傳入的參數主機名和端口號
    val host = args(0)
    val port = args(1).toInt
    // 準備配置信息:(|之間是解析數據,以“=”分割,“=”前面是參數(key),後面是數值(value)。)
    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)
    //ActorSystem老大,輔助創建和監控下面的Actor,他是單例的
    val actorSystem = ActorSystem(MASTER_SYSTEM, config)
    //創建Actor
    actorSystem.actorOf(Props(new Master(host, port)), MASTER_ACTOR)
    //調用線程等待
    actorSystem.awaitTermination()
  }
}


Worker的代碼如下:

package cn.allengao.rpc

/**
 * class_name:
 * package: 
 * describe: TODO
 * creat_user: Allen Gao
 * creat_date: 2018/1/18
 * creat_time: 16:37
 **/
import java.util.UUID

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

/**
  * Created by root on 2016/5/13.
  */
class Worker(val host: String, val port: Int,val masterHost: String,
             val masterPort: Int, val memory: Int, val cores: Int) extends Actor{

  val worker_id = UUID.randomUUID().toString
  var masterUrl : String = _
  val HEART_INTERVAL = 10000
  var master : ActorSelection = _
  
  //
  override def preStart(): Unit = {
    //跟Master建立連接
//    master = context.actorSelection(s"akka.tcp://MasterSystem@$masterHost:$masterPort/user/Master")
    master = context.actorSelection(s"akka.tcp://${Master.MASTER_SYSTEM}" +
     s"@$masterHost:$masterPort/user/${Master.MASTER_ACTOR}")
    //向Master發送註冊消息
    master ! RegisterWorker(worker_id,host,port, memory, cores)
  }

  override def receive: Receive = {
    case RegisteredWorker(masterUrl) => {
      println(masterUrl)
      //啓動定時器發送心跳,心跳是一個case class
      //導入一個隱式轉換,才能啓動定時器
      import context.dispatcher
      //多長時間後執行 單位,多長時間執行一次 單位, 消息的接受者
      // (直接給master發不好, 先給自己發送消息, 以後可以做下判斷, 什麼情況下再發送消息), 信息
      context.system.scheduler.schedule(0 millis, HEART_INTERVAL millis, self, SendHeartbeat)
    }

    case SendHeartbeat => {
      println("send heartbeat to master")
      //發送心跳之前要進行一些檢查
      master ! Heartbeat(worker_id)
    }
  }
}

object Worker {

  val WORKER_SYSTEM = "WorkerSystem"
  val WORKER_ACTOR = "Worker"

  def main(args: Array[String]) {
    val host = args(0)
    val port = args(1).toInt
    val masterHost = args(2)
    val masterPort = args(3).toInt
    //分析任務用到的內存,CPU核數
    val memory = args(4).toInt
    val cores = 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
    val config = ConfigFactory.parseString(configStr)
    //ActorSystem老大,輔助創建和監控下面的Actor,他是單例的
    val actorSystem = ActorSystem(WORKER_SYSTEM, config)
    actorSystem.actorOf(Props(new Worker(host, port, masterHost, masterPort, memory, cores)), WORKER_ACTOR)
   //調用線程等待
    actorSystem.awaitTermination()
  }
} 



運行Master的代碼:模擬傳入的參數如下


接着右鍵Run Master,效果如下:


運行Woker的程序:模擬傳入的參數如下


右鍵運行Worker的代碼,效果如下:


當停止worker運行時,可以從master的運行狀態中看出檢測到worker連接斷開,連接數重新回到“0”。

至此整個akka的RPC通信小實例測試完畢。



發佈了33 篇原創文章 · 獲贊 2 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章