Java編程中,調(diào)度Task、Actor通常采用Executors及ExecutorService。對(duì)無狀態(tài)的任務(wù),通常可以很好的勝任。但對(duì)于大量并發(fā)的有狀態(tài)任務(wù),需要使用Actor模型。
Kontraktor是一個(gè)Java編寫的輕量級(jí)高效Actor模型實(shí)現(xiàn)。可以直接暴露Actor提供TCP服務(wù)、WebService或者WebSockets,從JavaScript客戶端調(diào)用Actor方法,用JavaScript實(shí)現(xiàn)Actor并通過Java調(diào)用。
對(duì)無狀態(tài)小任務(wù)單元,Executors可以很好的勝任。比如將計(jì)算任務(wù)分擔(dān)到多個(gè)CPU上。然而,對(duì)于運(yùn)行中的大任務(wù)單元Job調(diào)度,Executors只能做到次優(yōu)(sub-optimal)。例如Actor或輕量級(jí)進(jìn)程的消息調(diào)度。
許多Actor框架或類似的并發(fā)框架使用Executor service批量調(diào)度消息。由于Executor service是上下文不敏感的,因此會(huì)將單個(gè)Actor/Task消息安排多個(gè)線程或CPU處理。這會(huì)導(dǎo)致訪問Actor、Process或Task狀態(tài)時(shí)經(jīng)常出現(xiàn)緩存未命中(cache miss)的情況。更糟糕的是,因?yàn)槊總€(gè)新的“Runnable”會(huì)把先前處理的Task緩存沖掉,所以CPU無法維持緩存的穩(wěn)定。使用忙循環(huán)(busy-spin)會(huì)帶來第二個(gè)問題。如果框架使用忙循環(huán)讀取自己的隊(duì)列,每個(gè)處理線程的CPU負(fù)載會(huì)升到100%。
借助Kontraktor 2.0,我實(shí)現(xiàn)了一種不同的調(diào)度機(jī)制——使用簡(jiǎn)單的度量標(biāo)準(zhǔn)測(cè)試應(yīng)用實(shí)際需要的CPU資源,再進(jìn)行水平式擴(kuò)充。
每個(gè)Actor會(huì)固定分配到一個(gè)Workerthread(“DispatcherThread”)。調(diào)度器會(huì)定期重新調(diào)度Actor,根據(jù)信息判斷是否需要把它們移動(dòng)到另一個(gè)工作線程。
由于算法過于復(fù)雜通常會(huì)帶來更高的運(yùn)行時(shí)開銷,實(shí)際調(diào)度時(shí)采用了一種非常簡(jiǎn)潔的方式:
如果消費(fèi)循環(huán)連續(xù)處理N個(gè)消息沒有休息(目前設(shè)置N=1000),就認(rèn)定該線程超載。
一旦線程標(biāo)記為“超載”,只要SUM_QUEUED_MSG(線程A上運(yùn)行的Actor)大于SUM_QUEUED_MSG(新創(chuàng)建線程B上的 Actor),信箱(mailbox)中消息多的Actor會(huì)移動(dòng)到新的線程(直到#Threads == ThreadMax)。
如果#Threads == ThreadMax,Actor會(huì)根據(jù)目前收到的消息和“超載”信息重新分配。
問題:
如果處理消息的時(shí)間差別很大,對(duì)消息隊(duì)列的統(tǒng)計(jì)會(huì)產(chǎn)生誤導(dǎo)。一種改進(jìn)是為每個(gè)消息根據(jù)定期分析設(shè)定加權(quán)。可以簡(jiǎn)單地用每個(gè)Actor加權(quán)乘以隊(duì)列大小。
對(duì)爆發(fā)式負(fù)載會(huì)有延時(shí),延遲結(jié)束后所有可用的CPU才能被真正地使用。
JIT真正起效前會(huì)有延遲,這會(huì)導(dǎo)致錯(cuò)誤的分析數(shù)據(jù),從而將錯(cuò)誤放大(一段時(shí)間后能恢復(fù)正常,實(shí)際情況并沒有那么糟糕)。
性能
為了對(duì)比自動(dòng)化調(diào)度與Actor線程固定方式的開銷,我運(yùn)行了Computing-Pi測(cè)試(可參照前一篇博客)。這些數(shù)據(jù)并沒有展示局部性(locality)帶來的影響,只對(duì)固定方式與自動(dòng)化調(diào)度進(jìn)行了比較。
測(cè)試1 手動(dòng)為每個(gè)Pi計(jì)算Actor分配一個(gè)線程,
測(cè)試2 一旦監(jiān)測(cè)到實(shí)際的負(fù)載,總起啟動(dòng)一個(gè)worker并且自動(dòng)進(jìn)行比例調(diào)整。
(注意:示例要求kontraktor2.0-beta-2及更高版本。如果parkNanos選項(xiàng)啟用,kontraktor的比例調(diào)整會(huì)限制在2、3個(gè)線程)
該測(cè)試運(yùn)行了8次,每次運(yùn)行會(huì)都會(huì)增加thread_max。
測(cè)試結(jié)果:
Kontraktor Autoscale(通常運(yùn)行1個(gè)線程,然后比例調(diào)整到N個(gè)線程)
1 threads : 1527
2 threads : 1273
3 threads : 718
4 threads : 630
5 threads : 521
6 threads : 576
7 threads : 619
8 threads : 668
Kontraktor為每個(gè)Actor指定固定個(gè)數(shù)的線程(參見上面源碼中被注釋的行)
1 threads : 1520
2 threads : 804
3 threads : 571
4 threads : 459
5 threads : 457
6 threads : 534
7 threads : 615
8 threads : 659
結(jié)論
運(yùn)行結(jié)果的區(qū)別很大程度上可歸結(jié)與比例調(diào)整帶來的延遲。對(duì)負(fù)載明確的情況,預(yù)先安排的Actor調(diào)度會(huì)更有效率。然而,考慮到服務(wù)器收到的請(qǐng)求會(huì)不斷變化,自動(dòng)化地調(diào)度是一種高效的選擇。
將Executors/FJ與上面的調(diào)度策略進(jìn)行對(duì)比,測(cè)試它們各自的緩存(cache)效果是很有意思的。不幸的是,Kontraktor不具備基于ExecutorService的消息分發(fā),也沒有針對(duì)Akka的調(diào)度策略實(shí)現(xiàn)。
此外,還需要一個(gè)示例Actors管理私有狀態(tài)才可以觀察緩存效果。
原文鏈接: java-is-the-new-c 翻譯: ImportNew.com - 唐尤華
譯文鏈接: http://www.importnew.com/13481.html
本站文章版權(quán)歸原作者及原出處所有 。內(nèi)容為作者個(gè)人觀點(diǎn), 并不代表本站贊同其觀點(diǎn)和對(duì)其真實(shí)性負(fù)責(zé),本站只提供參考并不構(gòu)成任何投資及應(yīng)用建議。本站是一個(gè)個(gè)人學(xué)習(xí)交流的平臺(tái),網(wǎng)站上部分文章為轉(zhuǎn)載,并不用于任何商業(yè)目的,我們已經(jīng)盡可能的對(duì)作者和來源進(jìn)行了通告,但是能力有限或疏忽,造成漏登,請(qǐng)及時(shí)聯(lián)系我們,我們將根據(jù)著作權(quán)人的要求,立即更正或者刪除有關(guān)內(nèi)容。本站擁有對(duì)此聲明的最終解釋權(quán)。