閱讀442 返回首頁    go 阿裏雲 go 技術社區[雲棲]


AKKA文檔(java)——角色係統

角色是封裝了狀態與行為的對象,它們通過交換放入接收者信箱的消息實現兩兩之間的通訊。從某種意義上說,角色是最嚴格的麵向對象編程,不過最好還是把它們當作人來看待:當用角色為一個方案建模時,想象有一群人,並給他們分配了任務,他們在一個組織結構中發揮職能作用,並想象如何做到故障升級(就像在不需要考慮實際利益的情況下與人打交道,也就是說我們不需要關心他們的情緒變化或道德問題)。這樣的結果可以充當構建軟件的心理腳手架。

注意:一個角色係統是一個會分配1…N個線程的重量級結構,因此為每個邏輯上的應用創建一個角色係統即可。

層次結構

就像在一個經濟組織內,角色自然形成了層次結構。一個程序中監控特定功能的角色,可能想把自己的任務分解為更小,更容易管理的片段。基於這一目的,它啟動了子角色,並管理它們。本節我們關注基本概念,更多細節在這裏。惟一的前提是每個角色都有一個管理者,也就是它的創建者。

角色係統的典型特性是任務的拆分與委派,直到任務拆分的足夠小。這樣做,不隻任務自己有清晰的結構,而且作為結果產生的角色也可以決定它們可以處理以及如何處理哪些消息,還有故障如何解決等等。如何一個角色遇到不能處理的情況,它會向管理者發送一條失敗消息,去尋求幫助。這種遞歸結構允許在合適的級別處理故障。

將這種思想與分層的軟件設計比較,後者更容易演變為防禦性編程,以開發沒有故障的軟件為目標:程序如何與正確的對象(譯者注:原文為person,但本人認為此處應該是指程序之間或模塊之間的交互方式)交互;一個更好的方案是如何發現故障,而不是把一切“都藏在地毯下麵”。

現在設計這樣一個係統的困難之處在於如何決定誰應該管理什麼。當然沒有一個最好的方案,但是有一些準則可能會有幫助:

  • 如果一個角色管理其它角色的工作,比如通過傳遞子任務,這個管理者就應當管理子角色。理由是管理者知道會發生哪些故障以及如何處理它們。
  • 如果一個角色持有數據(也就是說它的狀態要避免丟失),這一角色應當找出它監管的所有子角色處理的任何可能有危險的子任務,並適當處理這些子角色的故障。根據請求的性質,為每個請求創建一個新的子角色可能是最好的方案,這樣簡化了為收集應答的狀態管理。這種模式來自Erlang,被稱做錯誤內核模式。
  • 如果一個角色依賴於另一個才能履行它的職責,它應當監視其它角色的活躍度,並在收到終止通知時行動。這與監管不同,監視方對監管策略沒有影響,而且應當注意到的是,單純的功能性依賴不是決定是否要在層次結構的什麼位置放置一個子角色的標準。

對於這些規則,當然總有例外;但是不論你應當有充足的理由決定遵守這些規則還是破壞它們。

配置環境

作為一個角色的協作集合的角色係統是管理共享設施的天然單元,比如調度服務、配置、日誌等。擁有不同配置的多個角色係統可能無礙的共存於同一個JVM中,在Akka內部沒有全局共享狀態。還有一點,角色係統之間通訊的透明性——單節點內部或跨網絡節點的通訊——可以構建功能層次的模塊。

角色最佳實踐

  1. 角色應該像好同事:高效的完成工作而且不打擾他人,避免占用資源。翻譯成編程行為就是以事件驅動的方式處理事件生成響應(或更多請求)。除非是不可避免的,否則角色不應被外部的實體阻塞(也就是占用著一個線程的被動等待)——可能是一把鎖,一個網絡套接字等等。對於不可避免的情況請見下文。
  2. 不在可變對象之間傳遞可變對象。為了確保這一點,最好不改變消息。如果角色的封裝是因向外暴露自己的可變狀態而遭到破壞,你就退回到了通常的所有Java並發編程缺陷的境地。
  3. 角色是狀態和行為的容器,接受這一點意味著不常發送消息內的行為(可能使用Scala閉包很有誘惑力)。風險之一就是不小心就在角色之間共享了可變狀態,而這一點違反了角色模型的做法破壞了基於角色編程的良好體驗的一切特性。
  4. 頂級角色處於你的錯誤內核(Error Kernel)的最深處,所以盡量不要創建它們,喜歡真正的分層係統。這一點對故障處理有好處(同時考慮到配置與性能的粒度),還降低了監護人角色的重要性(譯者注:原文沒有重要性一詞,此處原文為strain,即血統,本人認為這句話原義是指監控護人角色的家族成員數量,後麵半句指出過度使用這種角色會形成單點爭用),如果過度使用這就形成了單點爭用。

阻塞需要仔細的管理

在某些情況下阻塞操作不可避免,也就是令一個線程進入不定時間的休眠,等待一個外部事件發生。傳統的RDBMS驅動或消息API就是例子,深層的原因通常是幕後發生了(網絡)I/O。麵對這一點,你可能傾向於僅僅用Future對象包裝這個阻塞調用,並用跟此對象代替直接與IO之間的交互,但是這個策略實在是太簡單了:當應用的負載增加時,你很可能會發現瓶頸所在,或耗盡內存,或線程過多。

下麵是“阻塞問題”恰當方案的不完全清單:

  • 在一個角色(或由路由器管理的角色組【JavaScala】)內部執行阻塞調用,確保配置一個足夠大的線程池專門用於這一目的。
  • 在一個Future對象內執行阻塞調用,確保此類調用在任意時間點內的上限(無限製的提交任務會耗盡你的內存或線程數)。
  • 在一個Future對象內執行阻塞調用,提供一個線程池,這個線程池的線程上限要與運行應用程序的硬件相符。
  • 專門用一個線程管理一組阻塞資源(比如說NIO選擇器驅動多個通道),以角色消息的形式調度事件。

第一種方案可能尤其適用於單線程模型,比如數據庫句柄傳統上一次隻能執行一個查詢,並使用內部同步方式確保這一點。一個常見的模式是為N個角色創建一個路由器,每個角色包裝一個數據庫連接,而查詢是發送到路由器的。數字N必須是最大吞吐量,而數字大小取決於在什麼樣的硬件上部署了哪種數據庫管理係統(DBMS)。

注意

配置線程池的工作最好委托給AKKA,簡單配置在application.conf文件裏並通過一個ActorSystem對象實例化【JAVAScala】。

最後更新:2017-05-23 16:33:33

  上一篇:go  AKKA文檔(java版)—位置透明性
  下一篇:go  微表情透視“愛樂之城”:10秒分手戲潛台詞知多少