以RAID分析作為架構(gòu)驅(qū)動(dòng)力
一、尋找架構(gòu)驅(qū)動(dòng)力
人類(lèi)自開(kāi)始學(xué)會(huì)以智慧洗亮觀察世界的雙眼之后,就明白觀察事物不能淺嘗輒止停留在表面現(xiàn)象,而要去看透本質(zhì)。通過(guò)本質(zhì)規(guī)律去建模世界,才能以“一”推演萬(wàn)物。種種推演的過(guò)程,皆是要去尋找某種驅(qū)動(dòng)力量作為分析或建構(gòu)的起點(diǎn)。
例如,當(dāng)我們要分析一個(gè)運(yùn)動(dòng)中的物體會(huì)形成如何的運(yùn)動(dòng)軌跡時(shí),就需要尋找產(chǎn)生運(yùn)動(dòng)的力,包括初始的動(dòng)力、重力、摩擦力以及其他可能干擾物體運(yùn)動(dòng)的力。有的力會(huì)推動(dòng)者物體向前,例如初始動(dòng)力以及與運(yùn)動(dòng)方向保持一致的作用力;有的力會(huì)阻礙物體的運(yùn)動(dòng),如摩擦力或者空氣阻力等。通過(guò)分析這些力的方向及度量,大致可以描繪出物體可能的運(yùn)動(dòng)軌跡。
軟件系統(tǒng)的復(fù)雜度遠(yuǎn)遠(yuǎn)超過(guò)物體的運(yùn)動(dòng)模型(當(dāng)然,從確定性角度講,軟件或許比物體的運(yùn)動(dòng)更簡(jiǎn)單),但其推演的過(guò)程卻是相似的,因?yàn)橐粋€(gè)軟件系統(tǒng)并非完全獨(dú)立的存在,而是處在一個(gè)更大的生態(tài)環(huán)境圈中,包括客戶(hù)的需求與使用體驗(yàn)、上游依賴(lài)系統(tǒng)、下游依賴(lài)系統(tǒng)、硬件與網(wǎng)絡(luò)環(huán)境、團(tuán)隊(duì)技能水平等諸多因素縱橫交錯(cuò),顯式或隱式地對(duì)軟件架構(gòu)的走向施加影響。這些影響因素就相當(dāng)于是影響“軟件”這個(gè)物體運(yùn)動(dòng)的力量。架構(gòu)師要做的工作就是要敏銳地從這些紛繁復(fù)雜如蛛網(wǎng)一般糾纏的力量中梳理出清晰的脈絡(luò)。
所謂“力”,其實(shí)是一種隱喻。雖然觀察軟件系統(tǒng)的視角如萬(wàn)花筒一般繽紛多彩,然而若從“物理力學(xué)”的視角剖析架構(gòu),似乎更加準(zhǔn)確直接。軟件系統(tǒng)正如物體一般,在各種影響力之下不停變化(運(yùn)動(dòng))。不同的影響因素會(huì)決定著架構(gòu)師的設(shè)計(jì)決策,而這些決策之間又相互影響著,或者相吸,或者相斥,絕對(duì)不能孤立看待。于是,架構(gòu)分析與設(shè)計(jì)就變成了對(duì)軟件系統(tǒng)的影響力識(shí)別,這種設(shè)計(jì)的驅(qū)動(dòng)力即我們所謂的RAID分析法。
二、RAID分析法
所謂RAID分析法,即識(shí)別軟件系統(tǒng)的風(fēng)險(xiǎn)(Risk)、假設(shè)(Assumption)、問(wèn)題(Issue)、依賴(lài)(Dependency),準(zhǔn)確地說(shuō),就是:
- 評(píng)估風(fēng)險(xiǎn)
- 明確假設(shè)
- 分析問(wèn)題
- 識(shí)別依賴(lài)
正如在《架構(gòu)之美》中John Klein、David Weiss寫(xiě)道:
軟件架構(gòu)師的首要關(guān)注點(diǎn)不是系統(tǒng)的功能。……你關(guān)注的是需要滿(mǎn)足的品質(zhì)。品質(zhì)關(guān)注點(diǎn)指明了功能必須以何種方式交付,才能被系統(tǒng)的利益相關(guān)人所接受,系統(tǒng)的結(jié)果包含這些人的既定利益。 |
這里所謂的“品質(zhì)”,即我們常說(shuō)的質(zhì)量屬性(Quality Attribute)。對(duì)于架構(gòu)師而言,業(yè)務(wù)需求導(dǎo)致設(shè)計(jì)復(fù)雜度的增加僅僅是一種量的變化;而質(zhì)量屬性對(duì)設(shè)計(jì)的要求,則可能隨著復(fù)雜度的增加而產(chǎn)生質(zhì)變。以分布式系統(tǒng)為例,隨著對(duì)消息隊(duì)列、分布式存儲(chǔ)、服務(wù)通信與集成的引入,在數(shù)據(jù)一致性、可靠性、安全、運(yùn)維管理等諸多方面,產(chǎn)生的復(fù)雜度與單機(jī)系統(tǒng)不可同日而語(yǔ),設(shè)計(jì)挑戰(zhàn)與難度幾乎與規(guī)模形成指數(shù)增長(zhǎng)。
系統(tǒng)復(fù)雜度或許是沒(méi)有限制的,而人力卻有限。我們?cè)陂_(kāi)始軟件系統(tǒng)的建構(gòu)與設(shè)計(jì)時(shí),難免有考慮不周到之處,若是沒(méi)有掌握合理的設(shè)計(jì)方法而深陷浩瀚如滄海一般的各種需求中,牽扯到各個(gè)利益相關(guān)者的糾纏中,我們就可能會(huì)迷路、困惑,或者作出不適合當(dāng)下場(chǎng)景的設(shè)計(jì)決策。
RAID分析在一定程度上可以幫助我們重拾正確的方向,尤其在處理質(zhì)量屬性方面,頗有奇效。
我的建議是將RAID分析以Workshop的形式開(kāi)展,召集團(tuán)隊(duì)成員通過(guò)頭腦風(fēng)暴來(lái)完成。由于將所有軟件系統(tǒng)可能面臨的問(wèn)題分為了RAID四類(lèi),從而明確了討論的范圍與類(lèi)別,使得參與者能夠以更加收斂更加清晰的思路參與進(jìn)來(lái)。一個(gè)典型的RAID分析結(jié)果如下圖所示:
在進(jìn)行RAID分析之前,我們需要明確這四個(gè)概念之間的區(qū)別。
三、風(fēng)險(xiǎn)與問(wèn)題
風(fēng)險(xiǎn)(Risk)與問(wèn)題(Issue)常常被人混淆在一起,而二者在概念上卻有其相關(guān)性。風(fēng)險(xiǎn)其實(shí)就是未來(lái)可能出現(xiàn)的問(wèn)題。我們?cè)谲浖O(shè)計(jì)的過(guò)程中,一直都在未來(lái)與現(xiàn)實(shí)中徘徊。滿(mǎn)足現(xiàn)實(shí),卻又需要預(yù)測(cè)未來(lái)。然而,未來(lái)是不可預(yù)測(cè)的,所有的預(yù)測(cè)其實(shí)都是一種想象;我們夸夸其談?lì)A(yù)測(cè)未來(lái),其實(shí)不過(guò)是想象未來(lái)罷了。于是,現(xiàn)實(shí)與未來(lái)之間就開(kāi)始了痛苦的拉鋸戰(zhàn),我們既不能對(duì)未來(lái)做過(guò)多預(yù)測(cè)與判斷,卻又不能僅滿(mǎn)足于現(xiàn)狀,如何做到架構(gòu)設(shè)計(jì)的恰如其分,在規(guī)避過(guò)度設(shè)計(jì)的同時(shí),又能讓我們的架構(gòu)能夠在未來(lái)需求發(fā)生變化時(shí)以最小的成本應(yīng)對(duì)。我們真正要做到的是前瞻未來(lái),評(píng)估風(fēng)險(xiǎn)就是讓我們能夠前瞻未來(lái)的瞭望鏡(這世上并沒(méi)有預(yù)測(cè)未來(lái)的魔法水晶球)。
分析現(xiàn)在存在的問(wèn)題,評(píng)估未來(lái)風(fēng)險(xiǎn),將是這場(chǎng)拉鋸戰(zhàn)的關(guān)鍵制高點(diǎn)。在判定優(yōu)先級(jí)時(shí),問(wèn)題往往高于風(fēng)險(xiǎn),需要在解決現(xiàn)有問(wèn)題的前提上,考慮未來(lái)風(fēng)險(xiǎn)的應(yīng)對(duì)方案。譬如說(shuō),系統(tǒng)目前存在的問(wèn)題是性能堪憂(yōu),那么除了必要的調(diào)優(yōu)手段外,我們可以通過(guò)提高系統(tǒng)的可伸縮性來(lái)改進(jìn)性能。然而,要保證系統(tǒng)的可伸縮性,就需要保持服務(wù)的無(wú)狀態(tài),并在設(shè)計(jì)系統(tǒng)的各個(gè)分層時(shí)都需要支持水平擴(kuò)展,則可能引入數(shù)據(jù)不一致以及系統(tǒng)欠穩(wěn)定的風(fēng)險(xiǎn)。
四、假設(shè)
我們往往會(huì)忽略為系統(tǒng)給定假設(shè)(Assumption),而事實(shí)上,這種假設(shè)往往代表了關(guān)鍵的架構(gòu)約束。
架構(gòu)約束是一種非常重要的驅(qū)動(dòng)力。Roy Fielding在其論文Architectural Styles and the Design of Network-based Software Architectures(《架構(gòu)風(fēng)格與基于網(wǎng)絡(luò)的軟件架構(gòu)設(shè)計(jì)》)中如此勾勒出約束的重要性:
屬性是由架構(gòu)中的一組約束所導(dǎo)致的。約束往往是由在架構(gòu)元素的某個(gè)方面應(yīng)用軟件工程原則來(lái)驅(qū)動(dòng)的。例如,統(tǒng)一管道和過(guò)濾器(uniform pipe-and-filter)風(fēng)格通過(guò)在其組件接口之上應(yīng)用通用性原則——強(qiáng)迫組件實(shí)現(xiàn)單一的接口類(lèi)型,從應(yīng)用中獲得了組件的可重用性和可配置性的品質(zhì)。因此,架構(gòu)約束是由通用性原則所驅(qū)動(dòng)的“統(tǒng)一組件接口”,目的是獲得兩個(gè)想要得到的品質(zhì),當(dāng)在架構(gòu)中實(shí)現(xiàn)了這種風(fēng)格時(shí),這兩個(gè)品質(zhì)將成為可重用和可配置組件的架構(gòu)屬性。 |
我們?cè)诿鞔_假設(shè)時(shí),需要將這些約束甄別出來(lái),以之作為架構(gòu)設(shè)計(jì)的驅(qū)動(dòng)力。例如,對(duì)于一個(gè)移動(dòng)APP,我們明確假設(shè):用戶(hù)在斷開(kāi)網(wǎng)絡(luò)連接時(shí),能夠正常地查閱個(gè)人信息與產(chǎn)品信息。這個(gè)假設(shè)就對(duì)軟件架構(gòu)提出約束,即在APP的客戶(hù)端需要緩存數(shù)據(jù)信息,并在用戶(hù)連接WIFI時(shí),能夠自動(dòng)同步客戶(hù)端數(shù)據(jù)到服務(wù)端。
某些假設(shè)則是系統(tǒng)功能性的重要約定,好似契約一般,需要在整個(gè)設(shè)計(jì)與實(shí)現(xiàn)階段需要遵從。例如假設(shè)電商系統(tǒng)需要調(diào)用的推薦系統(tǒng)為第三方系統(tǒng),那么在設(shè)計(jì)時(shí)就需要明確推薦系統(tǒng)公開(kāi)的接口,系統(tǒng)之間如何集成,當(dāng)推薦系統(tǒng)的服務(wù)發(fā)生變更時(shí),客戶(hù)方該如何應(yīng)對(duì)。這些都會(huì)直接影響我們的設(shè)計(jì)決策。
五、依賴(lài)
在軟件設(shè)計(jì)中,我們無(wú)時(shí)不刻不在與依賴(lài)作斗爭(zhēng)。依賴(lài)本身是無(wú)善無(wú)惡的,關(guān)鍵在于我們?cè)撊绾畏纸?內(nèi)聚),如何協(xié)作(耦合),這就是我們需要遵循的高內(nèi)聚低耦合設(shè)計(jì)原則。在架構(gòu)層面,情況更顯復(fù)雜,除了系統(tǒng)內(nèi)部的依賴(lài)之外,還需要考慮系統(tǒng)外部上游與下游的依賴(lài)。尤其是跨越物理邊界(可以視為一個(gè)進(jìn)程)之間的通信,會(huì)直接影響到可靠性、性能、可伸縮性等諸多質(zhì)量屬性。
DDD的Context Map定義了九種Bounded Context之間的映射關(guān)系,其中包括防腐層、開(kāi)放主機(jī)服務(wù)與發(fā)布語(yǔ)言表達(dá)的就是Bounded Context之間的集成關(guān)系。如果我們能夠在架構(gòu)之處識(shí)別出系統(tǒng)存在的依賴(lài),再結(jié)合Cockburn提出的六邊形架構(gòu)對(duì)其進(jìn)行更加直觀的可視化,找出依賴(lài)途經(jīng)的端口(Port)與適配器(Adapter),然后確定依賴(lài)之間的通信(集成)方式,幾乎就可以得出整個(gè)軟件系統(tǒng)應(yīng)用邏輯架構(gòu)與物理架構(gòu)的雛形了。
下圖將六邊形架構(gòu)與識(shí)別的依賴(lài)結(jié)合起來(lái):
六、實(shí)施RAID分析的案例
在多個(gè)系統(tǒng)的架構(gòu)設(shè)計(jì)或Inception階段,我通過(guò)運(yùn)用RAID分析法驅(qū)動(dòng)系統(tǒng)的軟件架構(gòu)設(shè)計(jì),效果頗佳,雖然在細(xì)節(jié)處還欠缺精細(xì),但從大處著手,卻可以幫助我們高屋建瓴地分析與架構(gòu)整個(gè)系統(tǒng)。以下是針對(duì)某版本升級(jí)系統(tǒng)的RAID分析案例。
七、評(píng)估風(fēng)險(xiǎn)
通常而言,對(duì)風(fēng)險(xiǎn)的識(shí)別可以引導(dǎo)我們對(duì)系統(tǒng)質(zhì)量屬性的思考,利益相關(guān)者可
以充分表達(dá)對(duì)這些屬性的擔(dān)心,從而驅(qū)動(dòng)我們?nèi)ふ医鉀Q方案。
1. 穩(wěn)定性
在這次RAID分析中,有利益相關(guān)者明確提出了對(duì)穩(wěn)定性的擔(dān)憂(yōu)。系統(tǒng)的多個(gè)模塊駐留在不同的節(jié)點(diǎn)中,部分模塊還是以嵌入方式駐留在主控板上。由于業(yè)務(wù)需要,模塊之間的通信相對(duì)頻繁,主要的通信協(xié)議為T(mén)elnet與SSH。從舊有的系統(tǒng)表現(xiàn)來(lái)看,跨界點(diǎn)之間的通信在穩(wěn)定性方面表現(xiàn)欠佳。基于這一問(wèn)題,我們?cè)诤罄m(xù)的架構(gòu)設(shè)計(jì)中對(duì)此進(jìn)行了深入分析,除了保證通信實(shí)現(xiàn)自身的健壯性與異常處理之外,我們還決定在主控板一端設(shè)計(jì)粗粒度的接口,一次性地傳遞版本升級(jí)需要的信息,減少不必要的通信。
2. 可擴(kuò)展性
風(fēng)險(xiǎn)對(duì)擴(kuò)展性的識(shí)別,幫助我們確立了一個(gè)架構(gòu)原則,就是版本規(guī)格包的結(jié)構(gòu)不應(yīng)該影響到主控板的系統(tǒng)。這是因?yàn)橹骺匕逑到y(tǒng)的版本升級(jí)受到的制約最多,我們不希望當(dāng)產(chǎn)品發(fā)生變化時(shí),影響整個(gè)版本管理系統(tǒng)。
3. 性能
當(dāng)需要升級(jí)的系統(tǒng)數(shù)量較多時(shí),系統(tǒng)的版本升級(jí)過(guò)程會(huì)變得緩慢。而業(yè)務(wù)需求有要求了系統(tǒng)不能長(zhǎng)期處于shutdown狀態(tài),否則會(huì)增加運(yùn)營(yíng)成本。因此,升級(jí)過(guò)程通常會(huì)選在凌晨,并且要求在較短時(shí)間內(nèi)完成整個(gè)升級(jí)工作,故而性能可謂重中之重。
我們考慮采用并發(fā)方式為每個(gè)待升級(jí)系統(tǒng)進(jìn)行升級(jí)。升級(jí)過(guò)程是一個(gè)獨(dú)立的過(guò)程,卻又牽涉到較為復(fù)雜的業(yè)務(wù)流程以及跨節(jié)點(diǎn)通信。由于部署限制,后臺(tái)只能部署在一個(gè)JVM之上,通過(guò)啟動(dòng)多個(gè)并發(fā)線(xiàn)程來(lái)處理升級(jí)業(yè)務(wù)。執(zhí)行升級(jí)時(shí),需要加載配置文件到內(nèi)存中,若同時(shí)啟動(dòng)的線(xiàn)程數(shù)過(guò)多,則可能導(dǎo)致OutOfMemory異常。這個(gè)風(fēng)險(xiǎn)的識(shí)別及時(shí)地為我們敲響了警鐘。我們?yōu)榇税才帕思夹g(shù)Spike,以期找到合適的配置項(xiàng),在性能與可靠性之間進(jìn)行***權(quán)衡。
八、明確假設(shè)
假設(shè)(Assumption)可以是關(guān)鍵的架構(gòu)約束,也可以是系統(tǒng)功能性的約定。架構(gòu)約束既可能是設(shè)計(jì)的阻力,也可以成為動(dòng)力。經(jīng)過(guò)討論,我們基本上確定了兩條最為重要的假設(shè):
系統(tǒng)必須支持雙向兼容。這個(gè)假設(shè)的提出,則要求我們?cè)陂_(kāi)發(fā)過(guò)程中,只要接口已經(jīng)發(fā)布,就不能再修改接口。除修復(fù)缺陷外,我們不能刪除舊有功能,只能增加新功能。即使舊有功能已被新功能取代,為保持兼容性,我們也不能刪除,但可以將其置為@deprecated標(biāo)注。
版本升級(jí)過(guò)程中,若前后操作具有依賴(lài)關(guān)系,則必須保證事務(wù)的一致性,要么全部成功,要么全部失敗。事實(shí)上,這一條假設(shè)也是對(duì)質(zhì)量屬性“可靠性”的一個(gè)回應(yīng)。
九、分析問(wèn)題
整個(gè)RAID的識(shí)別都針對(duì)技術(shù)層面,而非管理層面。因此我們識(shí)別的問(wèn)題也限
制在技術(shù)范圍。
在我們識(shí)別出來(lái)的問(wèn)題中,最致命的一個(gè)問(wèn)題是關(guān)于模塊NVUM的加載。NVUM是一個(gè)JAR包。它并非一個(gè)獨(dú)立運(yùn)行的系統(tǒng),而是由管理系統(tǒng)動(dòng)態(tài)加載。之所以選擇動(dòng)態(tài)加載,而非靜態(tài)依賴(lài),原因包括:
- NVUM由我們項(xiàng)目組維護(hù),管理系統(tǒng)則屬于另外一個(gè)項(xiàng)目,兩邊的版本計(jì)劃完全不一致。網(wǎng)管系統(tǒng)為一個(gè)Client-Server系統(tǒng),相對(duì)成熟,目前已被獨(dú)立地部署到全球多個(gè)外場(chǎng)。若采用靜態(tài)依賴(lài),就需要我們將其納入到網(wǎng)管系統(tǒng)中。但NVUM的版本更新更加頻繁,外場(chǎng)不可能因?yàn)镹VUM一個(gè)模塊的調(diào)整,而付出頻繁更新管理系統(tǒng)的代價(jià)。
- 管理系統(tǒng)負(fù)責(zé)監(jiān)控外場(chǎng)各設(shè)備的運(yùn)轉(zhuǎn)狀況。雖然系統(tǒng)的重啟(耗時(shí)數(shù)十分鐘)并不會(huì)影響設(shè)備的功能,但卻可能在重啟過(guò)程中,因?yàn)槲茨芗皶r(shí)掌控設(shè)備狀態(tài),而導(dǎo)致無(wú)法及時(shí)發(fā)現(xiàn)問(wèn)題。必須避免這種事故的發(fā)生。換言之,管理系統(tǒng)的重啟代價(jià)太高,不能經(jīng)常重啟。
JAR包的動(dòng)態(tài)加載可以通過(guò)URLClassLoader來(lái)實(shí)現(xiàn),又或者選擇OSGI。前者需要充分驗(yàn)證其穩(wěn)定性,后者則過(guò)于重型,成本太高。另外,動(dòng)態(tài)加載方式對(duì)于模塊設(shè)計(jì)而言存在設(shè)計(jì)約束,即我們需要將NVUM分為interface和impl兩個(gè)模塊,且必須保證interface的穩(wěn)定性。
另一個(gè)方案是采用腳本,例如選擇能夠運(yùn)行在JVM上的Groovy腳本語(yǔ)言。我們只需要在Java中調(diào)用Groovy提供的GroovyShell,就能直接讀取groovy腳本文件;然后調(diào)用run()方法即可執(zhí)行腳本。
十、識(shí)別依賴(lài)
除了NVUM與管理系統(tǒng),NVUM與主控板,主控板與其他設(shè)備之間的依賴(lài)外,牽涉到的依賴(lài)還有很多。有的屬于輸入依賴(lài),有的則屬于輸出依賴(lài)。此外,還有版本制作工具等系統(tǒng)也會(huì)受到NVUM的影響。同時(shí),NVUM還需要訪問(wèn)內(nèi)建的文件系統(tǒng),通過(guò)FTP讀取諸多外部文件。通信則可能采用Telnet、SNMP、SSH等多種協(xié)議。
這些依賴(lài)的識(shí)別便于確定本系統(tǒng)對(duì)其他系統(tǒng)可能造成的影響,事先識(shí)別有利于我們及時(shí)做好溝通,同時(shí)還需要就一些架構(gòu)約定以及接口定義達(dá)成一致意見(jiàn)。依賴(lài)的識(shí)別也有利于我們?cè)O(shè)計(jì)系統(tǒng)的物理架構(gòu),考慮系統(tǒng)的部署方式。
【本文為51CTO專(zhuān)欄作者“張逸”原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)聯(lián)系原作者】