Akka 學習筆記
容錯
容錯:不是抓住系統所有的錯誤並恢復,而是將錯誤(崩潰)孤立出來,不會導致整個系統崩潰(隔離故障元件),備份元件可以替換崩潰元件(冗餘)(可恢復性)
容錯方式:Restart, Resume, Stop, Escalate
let it crash原則
崩潰原因:網路,第三方服務,硬體故障
Akka容錯:分離業務邏輯(receive)和容錯邏輯(supervisorStrategy)
父actor自動成為子actor的supervisor
supervisor不fix子actor,而是簡單的呈現如何恢復的一個判斷==>
List(Restart, //重啟並替換原actor,mailbox訊息可繼續傳送,
//但是接收會暫停至替換完成,重啟預設重啟所有子actor
Resume, //同一個actor不重啟,忽略崩潰,繼續處理下一個訊息
Stop, //terminated 不再處理任何訊息,剩餘訊息會進入死信信箱
Escalate//交給上層處理
)
Akka提供兩種恢復策略:
OneForOneStrategy: 只針對單獨actor
override def supervisorStrategy = OneForOneStrategy() {
case _:XXXException => Restart
}
AllForOneStrategy: 對所有節點,一個節點重啟,其它節點全部重啟
不處理TheadDeath OOM等JVM Error,一直往上拋直到actorSystem頂層actor user guardian 會shutdown ,可通過配置使其優雅shutdown
擴充套件
scaling up 垂直擴充套件:某節點可以在單機(單JVM)上執行更多的數量
scaling out 水平擴充套件:某節點可以在多臺機器上執行
Akka 按地址(local, remote)投遞message, 可以方便實現scaling out
actor不知道是否是和remote actor通訊,local, remote區別在於配置,所以Akka可以透明的從scaling up轉向scaling out(僅通過修改幾行程式碼) 所以可以像積木一樣隨意組裝
/--
在單JVM中上鎖訪問的可變狀態(hhtp-session) 在scaling out時,直接寫入一個資料庫是最簡單的方式(同庫整合)
但是這意味著修改大量程式碼;
Akka用不可變訊息解決了以上問題
Akka actor 在dispatcher(底層排程執行pool)之上執行,dispatcher和配置直接對映(不同dispatcher型別可被選擇),所以在scaling up時,只修改配置,不需要動程式碼,其次actor和執行緒不是一一對映導致其非常輕量(比執行緒佔用空間更少)
/---
RPC 缺點 點對點通訊不適合大規模叢集(網路拓撲結構會很複雜,並伴隨負載)
面向訊息的中介軟體可以解決這個問題,but代價是應用層混入訊息系統(訊息中介軟體必須跟著應用演變)
Akka 使用分散式程式設計模型(Actor(抽象了本地和分散式環境))在scaling out時,頂層看起來是一樣的(透明)
分散式術語:
節點:通過網路通訊的應用
節點角色:不同的節點執行不同的任務
通訊協議:訊息被編碼和解碼為特定通訊協議的報文用於節點的通訊
序列化和反序列化:訊息的編碼和解碼
membership:同一個分散式系統的節點(可動態可靜態)
dynamic membership:節點數動態變化
Q:分散式環境難在哪裡?
A:時延,記憶體訪問,區域性失敗和併發性
//Akka remote example
object TestRemote extends App {
//////////////backend
val confBackend =
"""
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "127.0.0.1"
port = 2551
}
}
}
"""
val configBackend = ConfigFactory parseString confBackend
val backend = ActorSystem("backend", configBackend)//backend listens on address akka.tcp://backend@127.0.0.1:2551
backend.actorOf(Props[ConsoleActor], "console")
//////////////frontend
val confFrontend =
"""
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "127.0.0.1"
port = 2552
}
}
}
"""
val configFrontend = ConfigFactory parseString confFrontend
val frontend = ActorSystem("frontend", configFrontend)//frontend listens on address akka.tcp://backend@127.0.0.1:2552
val path = "akka.tcp://backend@127.0.0.1:2551/user/console"
val console = frontend.actorSelection(path)
console ! "Hello World"
}
//
class ConsoleActor extends Actor {
def receive = {
case m => println(s"received $m")
}
}
actorSelection
actorSelection方法會在本地建立一個遠端actor的代理,代理來處理和監聽遠端actor的重啟訊息接收等,可以有容錯作用,通過actorSelection直接獲取actorRef 也可直接通訊,但是遠端actor崩潰重啟,actorRef不會自動定位(容錯不好,效能不錯)
Akka 還可以遠端部署actor
可通過配置和程式碼兩種方式,遠端actor崩潰重啟,actorRef不會自動定位
遠端部署actor容錯:需要自己watch actor, 並且在遠端actor重啟時,重新deploy and watch
採用非遠端部署通過actorSelection獲取遠端actorRef無法實現watch
通過actorSelection獲得遠端actorRef方法:val console = context.system.actorSelection(path)
val actorRef = Await.result(console.resolveOne(), 5.seconds)
Futures
函式式併發
Actor基於訊息,他們是長久存在的物件,當某事(訊息)發生時作出行為
Future用函式替代物件,是某函式非同步執行的結果的佔位符
import scala.concurrent._
import ExecutionContext.Implicits.global //提供執行執行緒池 隱轉
val res = future {
1
} map { r =>
"res=" + r
} foreach { r =>
println(r)
}
//Future[Int] ==> Future[String]
//Future[T]和Option[T]類似
Future 錯誤處理
future {...}程式碼塊內報異常 後續操作將直接不進行
要想獲取錯誤值用onComplete方法
val fres = future {
throw new Exception("error")
1
} map { r =>
"res=" + r
} onComplete {
case Success(r) => println(r)
case Failure(NonFatal(e)) => println(e)
}//和ajax類似
while(true){} //需要堵塞
Future 恢復(容錯)
通過recover方法 可以定義在發生特定錯誤時的處理邏輯
val f = future {
throw new IllegalArgumentException("error")
1
} map { r =>
"res=" + r
} recover {
case e:IllegalArgumentException => 2 //返回某預設值
} onComplete {
case Success(r) => println(r)
case Failure(NonFatal(e)) => e.printStackTrace()
}
while(true){}
////////
val f1 = doAsyc1()
val f2 = f1.flatMap { r =>
doAsyc2(r).recover {
case e:XXXException => r
}
}
//onSuccess & onFailure
val f: Future[Int] = future {
val source = scala.io.Source.fromFile("myText.txt")
source.toSeq.indexOfSlice("myKeyword")
}
f onSuccess {
case idx => println("The keyword first appears at position: " + idx)
}
f onFailure {
case t => println("Could not process file: " + t.getMessage)
}
Future 組合
//1. firstCompletedOf
val f1 = future{1}
val f2 = future{"res="}
val f3 = Seq(f1, f2)
val f4 = Future.firstCompletedOf(f3) //取先執行完的結果
f4.onComplete {
case Success(r) => println(r)
case Failure(NonFatal(e)) => e.printStackTrace()
}
//2. zip
//Future[Int] zip Future[String] ==> Future[(Int, String)]
val f1 = future{1}
val f2 = future{"res="}
f1 zip f2 foreach(println) //等待一起執行完 並組合結果
===> (1,res=)
val f3 = f1 zip f2 map { case(i,s) =>
s+i
}
//3. sequence traverse
//Seq[Future[Int]] ==> Future[Seq[Int]] 多值對映為單值
val f = Seq(1,2,3) map { i =>
future{i}
}
Future.sequence(f) foreach println
/////////
val f = Future.traverse(Seq(1,2,3)) { i =>
future{i}
}
f foreach println
//4. fold
val f = Seq(f1, f2)
Future.fold(f)(0) { (sum:Int, i:Int) =>
...
}
//5.andThen
val allposts = mutable.Set[String]()
future {
session.getRecentPosts
} andThen {
posts => allposts ++= posts
} andThen {
posts =>
clearAll()
for (post <- allposts) render(post)
}
/**
* 組合器andThen的用法是出於純粹的side-effecting目的。
* 經andThen返回的新Future無論原Future成功或失敗都會返回與原Future一模一樣
* 的結果。一旦原Future完成並返回結果,andThen後跟的程式碼塊就會被呼叫,
* 且新Future將返回與原Future一樣的結果,這確保了多個andThen呼叫的順序執行
*/
//6. for
val f1 = future { connection.getCurrentValue(USD) }
val f2 = future { connection.getCurrentValue(CHF) }
val f3 = for {
usd <- f1
chf <- f2
if isProfitable(usd, chf)
} yield connection.buy(amount, chf)
f3 onSuccess {
case _ => println("Purchased " + amount + " CHF")
}
//f3只有當f1和f2都完成計算以後才能完成--
//它以其他兩個Future的計算值為前提所以它自己的計算不能更早的開始。
與Akka的結合 主要通過 akka.pattern包來實現
1.ask
actor.ask / ?
import akka.pattern.ask
implicit val timeout = Timeout(5 seconds)
val f = (actor ? Message).mapTo[Int]
f foreach println
2.pipe
用於actor 處理Future的結果
val f = (actor1 ? Message).pipeTo(actor2)
//把上一個結果傳遞給下一個actor; actor2 接收的訊息是值 不是Future引用
Await, Promise with Future
val f = future {
1
} map { r =>
"res=" + r
} recover {
case e:Exception => 2
}
val i = Await.result(f, 5.seconds) //同步等待 直到futrue執行結束 返回值
//promise是一個可寫的,可以實現一個future的單一賦值容器
val p = promise[T]
val f = p.future
val prod = future { //生產者
val r = calcResult()
p success r //此時f被寫入值
//do other things 同時 下面第19行程式碼的回撥可以執行了
}
val cons = future {
//do something...
f onSuccess { //獲取寫入值
case r => doSomethingWithRes(r)
}
}
future help doc url: http://docs.scala-lang.org/overviews/core/futures.html
or zh: https://code.csdn.net/DOC_Scala/chinese_scala_offical_document/file/Futures-and-Promises-cn.md
Akka配置
--載入
----預設載入applaction.conf, 程式碼不寫則載入akka-actor包下的reference.conf
----載入配置:val sys = ActorSystem("sys",ConfigFactory.load("sys"))
----獲取配置:sys.setting.config.getString("myApp.name")
"""sys.conf"""
myApp {
name = "mySystem"
}
--為不同子系統定義配置
----1.提取共享配置:baseConfig.conf
----2.子系統include "baseConfig" 並定義自己的部分 或覆蓋公共部分--akka應用日誌
----1.在Actor中用 val log = Logging(context.system, this)
----或者:with ActorLogging
----2.配置
akka {
event-handlers = ["akka.event.Logging$DefaultLogger"] #僅僅輸出到STDOUT
loglevel = "DEBUG"
}
----自定義日誌handler
class MyEventListener extends Actor {
def receive = {
case InistializeLogger(_) => //系統訊息
sender ! LoggerInitialized
case Error(cause, logSource, logClass, message) =>
println("ERROR " + message)
case Warning(logSource, logClass, message) =>
println("WARN " + message)
case Info(logSource, logClass, message) =>
println("INFO " + message)
case Debug(logSource, logClass, message) =>
println("DEBUG " + message)
}
}
使用slf4j的eventHandler
akka {
event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
loglevel = "DEBUG"
}
--配置akka日誌
akka {
loglevel = DEBUG #*必須設定成DEBUG 下面的debug才可以用
log-config-on-start = on #啟動時顯示用了哪個配置檔案
debug {
receive = on #記錄actor 接收的訊息(user-level級)由akka.event.LoggingReceive處理
autoreceive = on #記錄所有自動接收的訊息(Kill, PoisonPill)
lifecycle = on #記錄actor lifecycle changes
fsm = on #狀態機相關
event-stream = on #記錄eventSteam (subscribe/unsubscribe)
}
remote {
log-sent-messages = on #記錄出站的訊息
log-received-messages = on #記錄進站的訊息
}
}
class MyActor extends Actor with ActorLogging {
def receive = LoggingReceive { //記錄接收訊息
cae ... => ...
}
}
系統架構 與 Akka
1.管道和過濾器
--管道Pips:一個程式或執行緒將處理結果傳給下一個程式做額外處理的能力
(Unix |符)
--Pipeline:多個管道組合,大都為序列,akka提供並行
--過濾器:傳遞給下一個proccess之前的驗證邏輯
--Pip&Filter模式:input -pip-> check -pip-> check -> output
每個Filter包含3部分:進站pipe,處理器,出站pipe
進站訊息和出站訊息必須是一樣的(交給下一個處理),所以過濾器的順序是可以對調的,
--Akka的實現
class Filter1(pipe: ActorRef) extend Actor {
def receive = {
case msg:Message =>
if...cond1
pipe ! msg
}
}
class Filter2(pipe: ActorRef) extend Actor {
def receive = {
case msg:Message =>
if...cond2
pipe ! msg
}
}
2.發散聚合模式
並行的處理問題分 分發器和聚合器兩部分 map reduce
Akka 實現 Scatter & Gather 模式
class Filter1(pipe: ActorRef) extend Actor {
def receive = {
case msg:Message =>
val res1 = proccess...
pipe ! res1
}
}
class Filter2(pipe: ActorRef) extend Actor {
def receive = {
case msg:Message =>
val res2 = proccess...
pipe ! res2
}
}
//Scatter
class RecipientList(recipientList: Seq[ActorRef]) extend Actor {
def receive = { //分發給不同的收件人(pipe)
case msg:AnyRef => recipientList.foreach(_ ! msg)
}
}
//Gather 會快取訊息,當訊息都接收完了之後再處理,併發給下一個process
class Aggregator(timeout:Duration, pipe:ActorRef) extends Actor {
val msgs = new ListBuffer[Mssage] //快取訊息
def receive = {
case msg:Message => {
msgs.find(_.id == msg.id) match { //當有兩條一樣時傳送
case Some(existMsg) => {
pipe ! existMsg
msgs -= existMsg
}
case None => msgs += msg
}
}
}
}
Message Channels
1.點對點 Actor預設投遞方式
單鏈式:sender -3-2-1-> p2pchannel -3-2-1-> receiver保證訊息順序不會亂
多接收者:sender -3-2-1-> p2pchannel 1->receiver1 | 2->receiver2 | 3->receiver3 每個接收者只處理一種訊息
2.釋出訂閱
概述:發給多個接收者,並且不知道接收者是誰,channel負責跟蹤接收者,多個接收者處理同個訊息,接收者的數量可動態變動(unSubscribe/Subscribe) 訊息有序
場景:發貨和庫存更新 需要同一份訂單訊息,並且可並行執行
EventStream: 支援釋出訂閱channel, 支援接收者動態變動, 可看成多個Publish-Subscribe channels的管理者
Actor接收任何來自EventStream的訊息,不需要額外編碼,只用receive方法就行
註冊和登出自己:
system.eventStream.subscribe(slef, classOf[Message])
system.eventStream.unsubscribe(slef, classOf[Message])
釋出訊息
system.eventStream.publish(msg)
可訂閱多種訊息,登出時需要一一登出
2.5. EventBus 一個概念而已
3.自定義EventBus
需要實現三種實體:
Event 訊息
Subscriber (提供被註冊的能力,EventStream裡面就是ActorRef)
Classifier(選擇訂閱者分發事件,EventStream裡面Classifier 就是訊息的型別)
Akka有三種可組合的traits 可用來追蹤訂閱者
LookupClassification:基本分類器,維護了訂閱者集合,實現classify方法來從事件中提取classifier
SubchannelClassification: 它希望監聽者不只是葉子節點,實現有層級的結構
ScanningClassification:最複雜的一個
他們實現了unSubscribe/Subscribe方法,但是有別的方法需要實現
classify(event:Event):Classifier //從事件中提取分類器
compareSubscribers(a:Subscriber, b:Subscriber):Int //訂閱者的比較器
publish(event:Event, subscriber:Subscriber) //
mapSize:Int //返回不同classifier的數目
class MessageBus extends EventBus with LookupClassification {
type Event = Message
type Classifier = Boolean //自定義 這裡以訊息的number欄位是不是>1
def mapSize = 2 //true,false 兩種
//按型別來,就不需要自定義了
protected def classify(event:MessageBus#Event) = {
event.number > 1
}
}
//test
val bus = new MessageBus
bus.subscribe(actorRef1, false) //監聽 < =1
bus.subscribe(actorRef2, true) //監聽 > 1
bus.publish(Message(1))
bus.publish(Message(2))
- DeadLetter(死信) channel
概念:只有失敗的訊息才會進入,監聽這裡可以幫助發現系統的問題,不可以通過它傳送訊息
死信:所有不能被處理或者投遞(達到)的訊息
Akka用EventStream去實現死信佇列
system.eventStream.subscribe(monitorRef, classOf[DeadKetter])
actor1 ! PoisonPill //自殺
actor1 ! Message //接收不到
val dead = monitorRef.expectMsgType[DeadLetter]
dead.message
dead.sender //傳送人
dead.recipient //收信人
//當Actor不知道怎麼處理訊息時可以顯示傳送給deadLetter
system.deadLetters ! msg
5.Cuaranteed deliver channel (屬於點對點channel)
概念:保證所有訊息傳送和投遞 分幾個等級,但是akka不保證訊息100%投遞成功
構建系統時,我們需要考慮怎樣的程度的擔保是足夠的
一般規則:訊息至多投遞一次
Akka保證訊息投遞一次或者投遞失敗(不是很好) 但是有兩種解決方式:
a. 傳送本地訊息不太會失敗(單JVM傳送訊息只是方法呼叫,失敗就是報異常了,此時會有別的策略去處理(容錯),訊息確實不需要到達了),所以單JVM訊息投遞是可靠的
b. remote actors, 非常可能丟失訊息,尤其是在不可靠網路之上,ReliableProxy被用來解決這個問題,它使得傳送訊息如傳送local message一樣可靠。
唯一的顧慮是傳送和接收者所在JVM
Q: How does the ReliableProxy work?
A: 當開啟ReliableProxy(Actor) 在不同的節點間建立了一個隧道
{client-node: sender -> ReliableProxy} ---> {server-node: egress -> service }
Egress是一個被ReliableProxy啟動的Actor 兩個Actor都實現了check resend 功能去跟蹤哪個訊息被遠端接收者接收;
當訊息投遞失敗時,ReliableProxy會重試,直到Egress接收到,Egress轉發給真正的接收者;
當目標actor終止時,本地ReliableProxy將會終止
ReliableProxy限制:只能正對一個接收者,並且單向(若要回發,會再啟兩個代理)
val remoteActorRef = system.actorFor(path)
val proxy = system.actorOf(Props(new ReliableProxy(remoteActorRef,500.millis)), "proxy")
proxy ! Message
//akka 2.3.8之後不可用system.actorFor方法,只能用actorSelection方法,而Selection底層就是用的Proxy
STM (借鑑Clojure的東西)
val s = Ref(Set(1,2,3))
val reservedS = atomic { implicit tx = {
val head = s().head
s() = s().tail
head
}}
原始碼分析
actors
address --> ActorRef 替換直接引用(記憶體引用)
mailboxes
ActorCell.newActor 會委託props.newActor建立,並將behaviorStack(為毛是棧?為了實現become和unbecome)head 設定為instance.receive。
actorRef ! Message ; 呼叫ActorCell.tell --> dispatcher.dispath(分發)-->將訊息放入ActorCell的佇列mbox.enqueue裡,並executor.execute(mbox) -->執行mbox的run方法(mailbox被分發到某執行緒上)-->先處理系統訊息(mailBox.processAllSystemMessages),再處理使用者訊息(mailBox.processMailbox) --> dequeue佇列獲得訊息--> actor invoke next --> ActorCell.recevieMessage(msg) --> Actor.aroundReceive(behaviorStack.head, msg)執行receive方法
...
叢集
目標:全自動的管理actor叢集, 負載均衡,故障處理
目前的features:
--membership:容錯的membership
--負載均衡:根據路由演算法 路由actors的message
--節點分割槽:節點可以有自己的角色,使路由器可以配置為只給某角色的節點發訊息
--Partition points(分割槽點):一個ActorSystem 可以有一部分子樹在別的節點上(目前只有top level actors可以這樣)
不提供: 狀態冗餘,重分割槽,重負載
最適用:單一目的的資料處理應用(影象處理,實時資料分析)
種子節點(是一種節點角色):
a.最基本的用於啟動叢集的節點(其實是冗餘的控制節點); b.沒有任何的actors,是個純的節點;
c.是其它節點的中間聯絡人
d.要啟動akka cluster 必須配置一系列的種子節點,並且第一個種子節點有特殊的角色,First seed Node必須最先啟動;
e.其它種子節點可以和第一種子節點一起啟動,它們會等待第一種子節點啟動好
f.其它成功join啟動叢集后,第一種子節點可以安全的離開叢集
注:種子節點不是必須的,你可以手動啟動一個節點,讓它自己加入自己程式設計叢集,這樣就是手動需要了解地址等
配置:
akka {
loglevel = INFO
stdout-loglevel = INFO
event-handlers = ["akka.event.Logging$DefaultLogger"]
actor { #修改provider
provider = "akka.cluster.ClusterActorRefProvider"
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
log-remote-lifecycle-events = off
netty.tcp {
hostname = ""
host = ${HOST} #每個節點的主機
port = ${PORT} #每個節點的監聽埠
}
}
cluster {
seed-nodes = ["akka.tcp://words@127.0.0.1:2551",#First seed Node
"akka.tcp://words@127.0.0.1:2552", #actorsystem名稱必須相同
"akka.tcp://words@127.0.0.1:2553"] #種子節點列表
roles = ["seed"]
}
}
此時啟動三個JVM配置不同的port,就建立三個seed node了,並且自動組成cluster
//first seed node
val seedConfig = ConfigFactory.load("seed")
val seedSystem = ActorSystem("words", seedConfig)
//啟動後自己join自己
//狀態變化:Join -> Up
//同樣的程式碼啟動其它兩個種子節點
//first seed node 離開叢集
val address = Cluster(seedSystem).selfAddress
Cluster(seedSystem).leave(address)
//狀態變為Leaving -> Exiting -> Unreachable
在first seed node Exiting狀態時,seed node2自動變成leader(處理Join請求)
節點的狀態(Joining,Up...)變化通知,會在所有節點間傳遞
Gossip(閒聊) 協議:
在節點之間傳遞節點狀態的協議,每個節點描述自己的狀態和它看到的別的節點的狀態,最後節點的狀態會收斂成一致狀態,每次收斂之後都可以確定Leader
first seed node退出後,不能Cluster(seedSystem).join(selfAddress),而是應該重啟
seedSystem.shutdown
val seedSystem = ActorSystem("words", seedConfig)
//一個actorsystem只能加入cluster一次,但是可以以相同的配置啟動一個新actorsystem
Akka Cluster會探測Unreachable的節點,並解除安裝(down),leader節點在Unreachable無法正常工作,可以用down方法,解除安裝任意一個節點
val address = Address("akka.tcp", "words", "127.0.0.1", 2551)
Cluster(seedSystem).down(address)
節點狀態轉換:
-join-> [Joining] -leader action-> [Up] -leave-> [Leaving] -leader action->
[Exiting] -leader action-> [Unreachable] -down-> [Down] --> Removed
訂閱Cluster Domain Events
class ClusterDomainEventListener extends Actor with ActorLogging {
Cluster(context.system).subscribe(self, classOf[ClusterDomainEvent])
def receive ={
case MemberUp(member) => log.info(s"$member UP.")
case MemberExited(member) => log.info(s"$member EXITED.")
case MemberRemoved(m, previousState) =>
if(previousState == MemberStatus.Exiting) {
log.info(s"Member $m gracefully exited, REMOVED.")
} else {
log.info(s"$m downed after unreachable, REMOVED.")
}
case UnreachableMember(m) => log.info(s"$m UNREACHABLE")
case ReachableMember(m) => log.info(s"$m REACHABLE")
case s: CurrentClusterState => log.info(s"cluster state: $s")
}
override def postStop(): Unit = {
Cluster(context.system).unsubscribe(self)
super.postStop()
}
}
為MemberUp事件配置最小數量節點
#在master上配置
role {
worker.min-nr-of-members = 2 #當worker節點數超過兩個時 處罰 memberup事件
}
object Main extends App {
val config = ConfigFactory.load()
val system = ActorSystem("words", config)
println(s"Starting node with roles: ${Cluster(system).selfRoles}")
val roles = system.settings.config
.getStringList("akka.cluster.roles")
if(roles.contains("master")) { //當是master節點時 註冊事件
Cluster(system).registerOnMemberUp { //worker節點2個時觸發
val receptionist = system.actorOf(Props[JobReceptionist],
"receptionist")
println("Master node is ready.")
}
}
}
Routers 叢集的路由
和用本地的Router是一樣的,用路由去worker node上建立worker actor
trait CreateWorkerRouter { this: Actor =>
def createWorkerRouter: ActorRef = {
context.actorOf(
ClusterRouterPool(BroadcastPool(10), //用Pool來建立 廣播路由
ClusterRouterPoolSettings(
totalInstances = 1000, //叢集中最多多少個
maxInstancesPerNode = 20, //每個節點最多多少個
allowLocalRoutees = false, //不在本地節點建立, 只在worker節點上建立
useRole = None
)
).props(Props[JobWorker]),
name = "worker-router")
}
}
編寫master
class JobMaster extends Actor
with ActorLogging
with CreateWorkerRouter {
// inside the body of the JobMaster actor..
val router = createWorkerRouter
def receive = idle
def idle: Receive = {
case StartJob(jobName, text) =>
textParts = text.grouped(10).toVector //分割文字
val cancel = system.scheduler.schedule( 0 millis, #定期給路由發
1000 millis,
router,
Work(jobName, self))
//發Work訊息建立worker
become(working(jobName, sender, cancel))
}
// more code
//TODO
Persistence
當有狀態的actors崩潰時,做資料恢復
點對點訊息保證一次投遞
Scheduler
JDK ScheduledThreadPoolExecutor 通過DelayedWorkQueue 是一個PriorityQueue
(take 是會堵塞的)優先順序為Delay時間, 每次新增task都會重排序,時間短排前面,排程時根據Delay時間判斷是否執行
Akka Scheduler 則通過Netty HashedWheelTimer ,通過設定Tick Duration,掃一次Task佇列,(會Thread.sleep(sleepMs))
哪個效能更好??? 不知道
相關文章
- numpy的學習筆記\pandas學習筆記筆記
- Akka學習——術語和概念
- IT學習筆記筆記
- 學習筆記筆記
- 【學習筆記】數學筆記
- 《JAVA學習指南》學習筆記Java筆記
- Elasticsearch學習筆記Elasticsearch筆記
- Scala學習筆記筆記
- MySql學習筆記MySql筆記
- jQuery 學習筆記jQuery筆記
- react學習筆記React筆記
- 學習筆記(4.3)筆記
- 學習筆記(4.4)筆記
- 學習筆記(3.29)筆記
- 學習筆記(4.1)筆記
- AOP學習筆記筆記
- AspectJ學習筆記筆記
- 學習筆記(3.27)筆記
- 學習筆記(4.2)筆記
- golang 學習筆記Golang筆記
- Zookeeper學習筆記筆記
- 學習筆記(3.24)筆記
- 學習筆記(3.25)筆記
- 學習筆記(3.21)筆記
- GitHub學習筆記Github筆記
- jest 學習筆記筆記
- typescript 學習筆記TypeScript筆記
- Echarts學習筆記Echarts筆記
- js學習筆記JS筆記
- shell學習筆記筆記
- Dubbo 學習筆記筆記
- SVN 學習筆記筆記
- 笨笨學習筆記筆記
- vue學習筆記Vue筆記
- wepack學習筆記筆記
- redis學習筆記Redis筆記
- PureMVC學習筆記REMMVC筆記
- gitee 學習筆記Gitee筆記