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