Scala語法(六) Akka與線程通信

前言

在初期, Scala可以通過Akka來實現線程通信. 當然, 現在還支持使用Netty方式進行通信.

本章主要介紹使用Akka方式進行通信的寫法.


正文

  • Master結點

import akka.actor.Actor
import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory
import akka.actor.Props

class AkkaMaster extends Actor{
  // start 之前
  override def preStart() : Unit = {
    println("pre master invoke.")
  }
  // 用於接收消息
  override def receive:Receive = {
    case "connect" => {
        println("a client connected.")
        sender ! "reply"
      }
    case "hello" => {println("hello")}
  }
}
object AkkaMaster{
  def main(args: Array[String]): Unit = {
    // 使用創建ActorSystem來創建和監控下面的Actor對象. 單例的.
      val host = "127.0.0.1"
      val port = 8090
      // 準備配置
      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)
    // 注意名稱中間不要加空格
    val actorSysetm = ActorSystem("MasterSystem",config) 
    // 創建Actor
    val master = actorSysetm.actorOf(Props(new AkkaMaster),"Master")
    master ! "hello"
    // 等待信號停止
    actorSysetm.awaitTermination()
  }
}


// 順利輸出
//[INFO] [04/29/2019 16:43:20.512] [main] [Remoting] Starting remoting
//[INFO] [04/29/2019 16:43:20.770] [main] [Remoting] Remoting started; listening on addresses :[akka.tcp://[email protected]:8090]
//[INFO] [04/29/2019 16:43:20.771] [main] [Remoting] Remoting now listens on addresses: [akka.tcp://[email protected]:8090]
//pre master invoke.
//hello

// 1. 名稱中間不要加空格
//Exception in thread "main" java.lang.IllegalArgumentException: invalid ActorSystem name [Master System], must contain only word characters (i.e. [a-zA-Z0-9] plus non-leading '-' or '_')
//	at akka.actor.ActorSystemImpl.<init>(ActorSystem.scala:498)
//	at akka.actor.ActorSystem$.apply(ActorSystem.scala:142)
//	at akka.actor.ActorSystem$.apply(ActorSystem.scala:119)
//	at com.yanxml.quick_scala.multi.akka.AkkaMaster$.main(AkkaMaster.scala:33)
//	at com.yanxml.quick_scala.multi.akka.AkkaMaster.main(AkkaMaster.scala)
  • Worker結點
import akka.actor.Actor
import akka.actor.ActorSelection
import akka.actor.ActorSystem
import akka.actor.Props
import com.typesafe.config.ConfigFactory

class AkkaWorker extends Actor{
  // 成員變量
  val master:ActorSelection = null
  // 建立鏈接
  override def preStart():Unit = {
    val master = context.actorSelection("akka.tcp://[email protected]:8090/user/Master")
    println(master)
    master ! "connect"
  }
  
  override def receive:Receive = {
    case "reply" => {
      println("a reply from master")
    }
  }
}

object AkkaWorker{
   def main(args: Array[String]): Unit = {
     // 使用創建ActorSystem來創建和監控下面的Actor對象. 單例的.
      val host = "127.0.0.1"
      val port = 8091
      // 準備配置
      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)
    // 注意名稱中間不要加空格
    val actorSysetm = ActorSystem("WorkerSystem",config) 
    // 創建Actor
    val master = actorSysetm.actorOf(Props(new AkkaWorker),"Worker")
    master ! "hello"
    // 等待信號停止
    actorSysetm.awaitTermination()
   }
}

模擬RPC

在模擬RPC中主要有這樣的流程.

其中主要包括兩個結點: Worker結點&Master結點.

  • 運行流程:
    • Master結點先進行啓動.
    • Worker結點後進行啓動.
    • Worker結點Master結點發送註冊消息.
    • Matser結點接收註冊消息, 並進行記錄. 並將主結點的地址返回給Worker結點(模擬Master是集羣的情況).並記錄,最後的通信時間作爲心跳標誌.
    • Worker結點接收主結點地址, 並形成通信鏈接. 開始通信. 並定時發送心跳消息.

改造上方的Demo代碼. 其基本代碼如下所示:

  • RemoteMessage

trait RemoteMessage  extends Serializable{
  
}

// Worker -> Master 用來封裝Worker信息 
case class RegisterWorker(id:String,memory:Int,cores:Int)

class WorkerInfo(val id:String, val memory:Int, val cores:Int){
  // 上一次心跳
  var heartbeatTime:String = _
}
  • Master

import akka.actor.Actor
import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory
import akka.actor.Props
import scala.collection.immutable.HashMap

private [simulate] class AkkaMaster extends Actor{
  val idToWorker = new scala.collection.mutable.HashMap[String,WorkerInfo]()
  // start 之前
  override def preStart() : Unit = {
    println("pre master invoke.")
  }
  // 用於接收消息
  override def receive:Receive = {
    case "connect" => {
        println("a client connected.")
        sender ! "reply"
      }
    case "hello" => {println("hello")}
    // 傳輸樣例類
    case RegisterWorker(id,memory,cores)=>{
      if(!idToWorker.contains(id)){
        idToWorker.put(id, new WorkerInfo(id,memory,cores))
      }
      sender ! "123"
    }
  }
}
object AkkaMaster{
  def main(args: Array[String]): Unit = {
    // 使用創建ActorSystem來創建和監控下面的Actor對象. 單例的.
      val host = "127.0.0.1"
      val port = 8090
      // 準備配置
      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)
    // 注意名稱中間不要加空格
    val actorSysetm = ActorSystem("MasterSystem",config) 
    // 創建Actor
    val master = actorSysetm.actorOf(Props(new AkkaMaster),"Master")
    master ! "hello"
    // 等待信號停止
    actorSysetm.awaitTermination()
  }
}


// 順利輸出
//[INFO] [04/29/2019 16:43:20.512] [main] [Remoting] Starting remoting
//[INFO] [04/29/2019 16:43:20.770] [main] [Remoting] Remoting started; listening on addresses :[akka.tcp://[email protected]:8090]
//[INFO] [04/29/2019 16:43:20.771] [main] [Remoting] Remoting now listens on addresses: [akka.tcp://[email protected]:8090]
//pre master invoke.
//hello

// 1. 名稱中間不要加空格
//Exception in thread "main" java.lang.IllegalArgumentException: invalid ActorSystem name [Master System], must contain only word characters (i.e. [a-zA-Z0-9] plus non-leading '-' or '_')
//	at akka.actor.ActorSystemImpl.<init>(ActorSystem.scala:498)
//	at akka.actor.ActorSystem$.apply(ActorSystem.scala:142)
//	at akka.actor.ActorSystem$.apply(ActorSystem.scala:119)
//	at com.yanxml.quick_scala.multi.akka.AkkaMaster$.main(AkkaMaster.scala:33)
//	at com.yanxml.quick_scala.multi.akka.AkkaMaster.main(AkkaMaster.scala)

  • Worker

import akka.actor.Actor
import akka.actor.ActorSelection
import akka.actor.ActorSystem
import akka.actor.Props
import com.typesafe.config.ConfigFactory
import com.yanxml.quick_scala.multi.akka.simulate.RegisterWorker
import java.util.UUID

private [simulate] class  AkkaWorker(val masterHost:String, val masterPort:String, val memory:Int, val cores:Int) extends Actor{
  // 成員變量
  val master:ActorSelection = null
  // 建立鏈接
  override def preStart():Unit = {
    // 和Master建立鏈接
    val master = context.actorSelection(s"akka.tcp://MasterSystem@$masterHost:$masterPort/user/Master")
    val workerId = UUID.randomUUID().toString()
    //println(master)
    // 向Master發送消息
    master ! RegisterWorker(workerId,memory,cores)
  }
  
  override def receive:Receive = {
    case "reply" => {
      println("a reply from master")
    }
  }
}

object AkkaWorker{
   def main(args: Array[String]): Unit = {
     // 使用創建ActorSystem來創建和監控下面的Actor對象. 單例的.
      val host = "127.0.0.1"
      val port = 8091
      // 準備配置
      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)
    // 注意名稱中間不要加空格
    val actorSysetm = ActorSystem("WorkerSystem",config) 
    // 創建Actor
    val master = actorSysetm.actorOf(Props(new AkkaWorker("127.0.0.1","8090",2,2)),"Worker")
    master ! "hello"
    // 等待信號停止
    actorSysetm.awaitTermination()
   }
}

注: 後續的通信邏輯就是豐富雙方的receive()方法.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章