1. 程式人生 > >AKKA文件(java)——角色系統

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】。