廢話就不要看了,看看線程和command的部分設(shè)計(jì)就可以了,還不錯的想法。
http://dev2dev.bea.com.cn/techdoc/webser/2005060104.html在請求驅(qū)動的環(huán)境中解耦合和延遲處理是創(chuàng)建健壯和可伸縮的分布式應(yīng)用程序的關(guān)鍵戰(zhàn)略之一。許多服務(wù)都單獨(dú)依賴于集群來確保可伸縮性,但是當(dāng)新發(fā)現(xiàn)的需求使應(yīng)用程序的復(fù)雜性增長時(shí),它們常常會遇到麻煩。
盡管服務(wù)器集群是推動可伸縮性的基本技術(shù),但是當(dāng)所有的處理都同步完成時(shí),它可能變得很低效。吞吐量可能會增加,但是響應(yīng)性卻會變得不可救藥。
在本文中,我討論了異步處理,并舉例說明了,巧妙的任務(wù)管理會如何提高您應(yīng)用程序的性能、可用性、可伸縮性和可管理性。我們將以一種高度可配置的方式,創(chuàng)建一個一般的任務(wù)分配框架,該框架可以發(fā)送任何任務(wù)給您的集群中的一臺或每臺服務(wù)器。通過使用多態(tài)和 Java 消息服務(wù)( Java Message Service , JMS ),我們的框架將實(shí)現(xiàn)著名的 Command 模式。
解耦合在實(shí)際中的意義
當(dāng)服務(wù)器收到客戶端請求時(shí),它通常需要在返回響應(yīng)之前執(zhí)行幾個單獨(dú)的任務(wù)。解耦合( decoupling )意味著不會一次執(zhí)行所有的任務(wù),而是把一些任務(wù)放入隊(duì)列并異步地進(jìn)行處理。因?yàn)榕抨?duì)通常是一項(xiàng)低開銷的操作,同步的請求會更快地結(jié)束。
解耦合的優(yōu)點(diǎn)
按照順序和并行地處理任務(wù),通常會比隨機(jī)地處理它們要更加高效(客戶端偶然發(fā)出請求的時(shí)候)。正面的影響比隨即表現(xiàn)出來的要大。理論上,解耦合可以提高以下各個方面的性能:
- 健壯性 : 提高,因?yàn)檎埱髮⒁蕾嚨目赡苁У倪^程更少。
- 響應(yīng)性 : 請求的部分后處理減少了接收請求和返回響應(yīng)之間的時(shí)間。
- 可伸縮性 : 所有解耦合后的過程在復(fù)雜性方面可能會增加,但不會有降低響應(yīng)度的危險(xiǎn)。
- 可用性 : 可以在服務(wù)器永遠(yuǎn)不知道故障原因的情況下處理故障。
在子系統(tǒng)不可用的情況下,配置自動重試要更加容易。
自然,理論與實(shí)踐之間的差別在每個應(yīng)用程序上的體現(xiàn)各不相同。然而,顯然,幾乎每種實(shí)現(xiàn)都至少具有前述的某些優(yōu)點(diǎn)。
解耦合的缺陷
和大部分好東西一樣,解耦合也存在缺點(diǎn)。其中最嚴(yán)重的缺點(diǎn)之一就是,如果您不能確保您擁有足夠強(qiáng)大的硬件來清空繁忙的處理隊(duì)列,那么您可能會發(fā)現(xiàn),它的可用性實(shí)際上降低了。如果進(jìn)入的異步請求比您的系統(tǒng)能夠處理的要多,隊(duì)列就會非常迅速地增長。您必須注意設(shè)計(jì)過程,而對隊(duì)列實(shí)行自動監(jiān)控?zé)o疑是可取的做法。另一個明顯的問題是,在請求驅(qū)動的環(huán)境中,大多數(shù)過程都不是很適合于解耦合。事實(shí)上,大多數(shù)處理都可能被要求返回響應(yīng)。有時(shí),它會需要一些開箱即用的思想,甚至可能是您為您的客戶端所提供的服務(wù)方式的變化。
哪些過程可以解耦合
從純技術(shù)的角度來講,幾乎所有的過程都可以解耦合。例如,您可以把一個所購買物品及客戶詳細(xì)信息的清單放入隊(duì)列,從而可以解耦合一個訂單事務(wù)——異步處理將負(fù)責(zé)余下的工作。不足之處在于您不能在響應(yīng)中包含任何處理細(xì)節(jié)。因此,謹(jǐn)慎地預(yù)先驗(yàn)證數(shù)據(jù)是很重要的,這樣可以確保不會出現(xiàn)問題。
一種越來越流行的實(shí)現(xiàn)是,馬上把請求放入隊(duì)列,然后持續(xù)輪詢服務(wù)器,以便了解何時(shí)可以獲得響應(yīng)。盡管這種方法在本質(zhì)上實(shí)際是同步的,而且不會增加請求的處理時(shí)間,但是它在心理上具有優(yōu)勢,因?yàn)榭梢栽谳喸兤陂g顯示進(jìn)度條。
除了解耦合完整的業(yè)務(wù)邏輯(這是一個巨大的難題)之外,更少的集中處理,比如日志記錄和發(fā)送電子郵件,是可以考慮的良好選擇。當(dāng)性能變得及其重要時(shí),沒有理由讓客戶端等待這種任務(wù)的完成。特別的電子郵件是用于解耦合的一個良好選擇。讓我們做進(jìn)一步的了解。
實(shí)例研究:異步的電子郵件
以傳統(tǒng)的方式發(fā)送電子郵件(作為同步請求的一部分)會引起一些問題。首先,連接到電子郵件服務(wù)器需要一次網(wǎng)絡(luò)往返,速度可能會很慢,尤其是在服務(wù)器非常繁忙的時(shí)候。過載的電子郵件服務(wù)器甚至可以使依賴于電子郵件的服務(wù)暫時(shí)不可用。
XA 事務(wù)支持
另一個顯而易見的問題是,電子郵件服務(wù)器通常在本質(zhì)上是非事務(wù)性的。當(dāng)事務(wù)被回滾時(shí),這可以導(dǎo)致出現(xiàn)不一致的通知——把一條消息放入隊(duì)列之后不能取消它。幸運(yùn)的是, JMS 支持事務(wù),而且可以通過把消息的發(fā)送延遲到提交底層事務(wù)的時(shí)候來解決這個問題。
考慮訪問數(shù)據(jù)庫和事務(wù)感知的 JMS 時(shí),您將需要使用 XA 和兩階段提交( 2PC )事務(wù)。可以使用非 XA 資源來模擬 XA ,但是您可能會得到不一致的數(shù)據(jù)。啟用 XA 只是一個配置問題,而且通常不需要修改代碼。參見 WebLogic 文檔以獲得相關(guān)的詳細(xì)信息。
通過 JMS 發(fā)送電子郵件
為了使用 JMS 來發(fā)送電子郵件,我們需要配置 JMS 組件(比如, JMS 服務(wù)器, JMS 隊(duì)列和連接工廠)。我們還需要編寫一個消息驅(qū)動 Bean ( Message Driven Bean , MDB )來執(zhí)行信件的實(shí)際發(fā)送。當(dāng)我們想要在我們的代碼中發(fā)送電子郵件時(shí),需要創(chuàng)建一條包含信件的屬性和內(nèi)容的 JMS 消息。之后,我們把它發(fā)送給處理隊(duì)列。
這樣做的工作量很大!幸運(yùn)的是, BEA WebLogic JMS 為我們提供了創(chuàng)建一個可以解耦合幾乎任何過程的框架所需的全部內(nèi)容。
用于異步執(zhí)行的框架
是動手看看一些代碼的時(shí)候了。我們將創(chuàng)建一個框架,這個框架支持在集群中的一臺或所有服務(wù)器上,異步地執(zhí)行代碼的任何部分。實(shí)現(xiàn)起來的確需要花費(fèi)些力氣,但是一旦框架完成,異步執(zhí)行就是再容易不過的事情了。
這個思路是編寫一些類,這些類包含一個帶有可運(yùn)行代碼的公共方法和另一個用于初始化參數(shù)的方法——可能是構(gòu)造器。封裝在 JMS 對象消息中之后,這些預(yù)先編寫好的類的實(shí)例(命令消息)就被發(fā)送給在您的服務(wù)器上配置的 JMS 隊(duì)列。至此,消費(fèi)者把它們?nèi)〕觯缓螽惒降貓?zhí)行它們(參見圖 1 )。
讓我們逐個看看這個框架的所有部分:
- JMS 隊(duì)列 : 應(yīng)該在每臺服務(wù)器上配置一個用于接收命令消息的 JMS 隊(duì)列。還應(yīng)該為重復(fù)保存故障消息配置錯誤隊(duì)列。
- JMS 連接工廠 : 為了支持事務(wù)性行為的運(yùn)行時(shí)選擇,應(yīng)該配置兩個連接工廠:一個支持 XA ,而另一個不支持 XA 。
- 命令對象接口 (CommandMessage) : 這是一個所有命令對象都需要實(shí)現(xiàn)的簡單 Java 接口。它擴(kuò)展了 java.io.Serializable 接口,該接口對于在 JMS 對象消息中嵌入我們的命令來說是必需的。現(xiàn)在,因?yàn)槲覀兿胍诓恢烂畹拇_切類型的情況下運(yùn)行它們,我們還要實(shí)現(xiàn) java.lang.Runnable 接口,稍后把它們簡單地轉(zhuǎn)換為 Runnable 對象,并執(zhí)行它們的運(yùn)行方法。我們在不知道我們運(yùn)行的確切內(nèi)容的情況下運(yùn)行了代碼。這是最理想的多態(tài)。
- 命令執(zhí)行程序 (CommandExecutionManager) : 我們將使用一個 MDB 來處理命令。實(shí)例池化防止了 JMS 初始化重復(fù)出現(xiàn),這使得 MDB 成為功能非常強(qiáng)大的消息監(jiān)聽器,非常適合于這項(xiàng)任務(wù)。編寫 Bean 類不需要很大的工作量,我們只需要在 onMessage 方法中編寫數(shù)行代碼(參見清單 1 )。
這樣就把收到的消息傳遞給一個 ObjectMessage ,獲得嵌入的命令對象,然后執(zhí)行它的運(yùn)行方法。通過在 config.xml 文件中,把隊(duì)列的重新發(fā)送限制設(shè)置為一個大于 0 的值,您可以配置一個重試計(jì)數(shù)器。從您的命令對象拋出一個運(yùn)行時(shí)異常,便可觸發(fā)重新發(fā)送的動作。此外,通過配置重新發(fā)送延遲,您還可以控制重試的頻率。
用于發(fā)送消息的一個幫助器類 (TaskDistributor)
從技術(shù)上說,這個部分并不是完全必要的,每次都可以手動進(jìn)行 JMS 排隊(duì)。然而,這是一個冗長乏味的過程,而且實(shí)際上是幫助器使這個框架變得如此實(shí)用。幫助器是一個常規(guī)的 Java 類,帶有用于對命令消息進(jìn)行排隊(duì)的靜態(tài)方法。您可以針對處理不同的場景編寫單獨(dú)的方法,但是為了簡明起見,我選擇了編寫一個可以處理大多數(shù)情況的方法:
static void execute(CommandMessage cm, long delay, b oo lean runEverywhere, b oo lean persisted, b oo lean
enableXA, int priority)
這個靜態(tài)方法帶有幾個用于精確執(zhí)行控制的參數(shù)。讓我們逐個討論這些參數(shù):
- CommandMessage cm : 一個命令消息實(shí)例。
- long delay : 代表發(fā)送屬性的時(shí)間,借助 weblogic.jms.extensions.WLMessageProducer 類進(jìn)行設(shè)置。這樣,就可以在夜間或者其他方便的時(shí)間執(zhí)行命令。接受一個 Date 對象也是可以的。
- b oo lean runEverywhere : 決定是否發(fā)送要執(zhí)行的消息給集群中的一臺隨機(jī)選中的訪問器或者所有的服務(wù)器。
- b oo lean persisted : 將通過使用隊(duì)列發(fā)送程序的 setDeliveryMode 方法選擇發(fā)送模式。應(yīng)該始終保持業(yè)務(wù)關(guān)鍵型的消息,從而在訪問器崩潰的時(shí)候,這些消息不會丟失。然而,持久性始終是以性能損失為代價(jià)的,這也應(yīng)該納入考慮的范圍內(nèi)。
- b oo lean enableXA : 將選擇方法是否使用支持 XA 的 JMS 連接工廠。此參數(shù)設(shè)置為 true 時(shí),排隊(duì)將參與底層事務(wù)(如果存在的話),在提交事務(wù)之前不會對消息進(jìn)行排隊(duì)。
- int priority : 決定消息的 JMS 優(yōu)先級。在發(fā)送之前,將使用給定的值調(diào)用 javax.jms.Message 類的 setJMSPriority 方法。有效的范圍是 0-9 。對于大多數(shù)應(yīng)用程序來說,給命令消息指派不同的優(yōu)先級似乎有些過頭,但是出于完整性方面的考慮,我還是在這里包括了這個選擇。
應(yīng)該針對您的特定執(zhí)行的需要,來量身打造 TaskDistributor 幫助器類的實(shí)現(xiàn)。在本文中,要包含一個例子似乎太長了,但是您可以從 WLDJ Web 站點(diǎn) www.sys-con.com/wldj/sourcec.cfm 下載一個。還可以添加幾個額外的參數(shù)來控制更高精度的執(zhí)行,但是另一方面,您可能已經(jīng)滿足于較少的幾個選項(xiàng)。
使用異步執(zhí)行
框架完成之后,我們就可以開始實(shí)現(xiàn)我們的命令消息了。讓我們看一個簡單的例子。首先,我們需要創(chuàng)建一個代表我們的命令消息的類(參見清單 2 )。為了調(diào)用執(zhí)行,我們使用了 TaskDistributor 類(參見清單 3 )。
當(dāng)調(diào)用例子中的執(zhí)行方法時(shí),一個包含 DistributedLogger 類實(shí)例的 ObjectMessage ( JMS 優(yōu)先級被設(shè)置為 4 )將在 1 秒延遲之后,被發(fā)送給集群中所有的服務(wù)器。隨后,記錄程序?qū)⒃谒械姆?wù)器上打印一個字符串給 stdout 。借助已經(jīng)完成的框架,異步執(zhí)行變得非常易于實(shí)現(xiàn),完全沒有障礙。節(jié)點(diǎn)到節(jié)點(diǎn)的通信變得前所未有的容易。
容器托管的任務(wù)分配
我們可以通過使用池化線程和虛擬內(nèi)存隊(duì)列,來創(chuàng)建一個類似的服務(wù),以處理異步請求。然而,我們強(qiáng)烈建議讓應(yīng)用服務(wù)器來管理所有的線程。
此外,因?yàn)?JMS 為我們提供了一個非常簡潔和靈活的解決方案,沒有理由不讓我們的服務(wù)器來處理錯綜復(fù)雜的過程。事實(shí)上,我們可以調(diào)用這個方法容器托管的任務(wù)分配( Container-Managed Task Distribution )。
性能問題
BEA WebLogic 可以處理很大的消息量,而且性能通常不是問題。盡管如此,當(dāng)生產(chǎn)非常大量的命令用于處理時(shí),還是推薦使用非持久性的消息和流水線操作。另外,消息流控制可以緩解:服務(wù)利用率的暫時(shí)高峰所導(dǎo)致的消息處理消耗過多資源。
并行處理
使用 MDB 的一個巨大優(yōu)點(diǎn)是,它們可以自動地并行處理消息。您可以通過顯示池化消費(fèi)者 Bean 的數(shù)量來調(diào)整消耗處理資源的數(shù)量。
WebLogic 提供大量有價(jià)值的 JMS 擴(kuò)展和配置選項(xiàng)——它們中有許多可以用在任務(wù)分配的各種實(shí)現(xiàn)中。選擇和優(yōu)化用于分頁、重新發(fā)送、持久性和調(diào)節(jié)(流控制)的 JMS 參數(shù)時(shí),應(yīng)該格外注意。
JMS 是一個非常完善的服務(wù),對其功能進(jìn)行仔細(xì)研究是值得的。想要了解有關(guān)提高性能的更多信息,請參見 WebLogic JMS 性能指南。
結(jié)束語
我們已經(jīng)討論了解耦合和異步的消息收發(fā)。作為一條經(jīng)驗(yàn)法則,我們可以說,處理異步請求的服務(wù)器,比專門處理同步請求的服務(wù)器的執(zhí)行效率要高。盡管解耦合并不總是件容易的事情,或者甚至不總是個可行的方法,但在考慮周全地實(shí)現(xiàn)時(shí),它仍然是一種功能非常強(qiáng)大的機(jī)制。我們獲得的不僅僅是幾個與性能相關(guān)的優(yōu)點(diǎn),而且能夠設(shè)計(jì)更加靈活的應(yīng)用程序。
BEA WebLogic JMS 遠(yuǎn)遠(yuǎn)不止是個用于數(shù)據(jù)傳輸?shù)姆?wù)。除了可配置性非常強(qiáng)之外,它還提供許多有用的功能,比如自動重新發(fā)送,消息的持久化,可調(diào)度性, XA 支持,調(diào)節(jié),瞬時(shí)分頁和循環(huán)故障消息的重定向。通過利用這種強(qiáng)大的通用性,我們可以創(chuàng)建一個功能強(qiáng)大而且可以擴(kuò)展的框架,來處理需要進(jìn)行異步處理的所有情形。
關(guān)于作者
John-Axel Strahlman 是 Sanda Interactive Ltd ( www.stcinteractive.com ) 的創(chuàng)立者和 CEO 。 Sanda Interactive Ltd 是一家軟件咨詢公司,總部位于芬蘭的 Espoo 。他是一位分布式系統(tǒng)專家,為他的客戶的項(xiàng)目提供咨詢已經(jīng)超過 5 年。( 更多信息 )
清單 1 :命令處理器 MDB
public void onMessage(Message msg) {
ObjectMessage om =
(ObjectMessage) msg;
Runnable command =
(Runnable) om.getObject();
command.run();
}
清單 2 :命令類示例
public class DistributedLogger
implements CommandMessage {
private String text = null;
public void setText(String text) {
this.text = text;
}
public void run() {
System.out.println(text);
}
}
清單 3 :框架使用示例
DistributedLogger logger =
new DistributedLogger();
String text =
"Hello asynchronous execution!"
logger.setText(text);
TaskDistributor.execute(
logger, //Command instance
1000, //delay
true, //runEverywhere
false, //persisted
false, //enableXA
4); //delay.
原文出處
http://sys-con.com/story/?storyid=48222&de=1