欧美在线观看视频网站,亚洲熟妇色自偷自拍另类,啪啪伊人网,中文字幕第13亚洲另类,中文成人久久久久影院免费观看 ,精品人妻人人做人人爽,亚洲a视频

自組織分布式金融業(yè)務(wù)系統(tǒng)的制作方法

文檔序號:6470923閱讀:722來源:國知局

專利名稱::自組織分布式金融業(yè)務(wù)系統(tǒng)的制作方法
技術(shù)領(lǐng)域
:本發(fā)明是應(yīng)用于金融業(yè)務(wù)領(lǐng)域使用pos機的支付以及網(wǎng)上電子支付的持久穩(wěn)定業(yè)務(wù)處理系統(tǒng)。
背景技術(shù)
:金融行業(yè)業(yè)務(wù)系統(tǒng)一般由高性能計算機主機(通常做了雙機熱備、自動數(shù)據(jù)備份等安全措施)以及與高性能計算機主機相連的遵循指定業(yè)務(wù)通訊協(xié)議的各種終端設(shè)備(pos機、客戶端pc機等)構(gòu)成。對其進行比較深入的維護(比如遇有關(guān)鍵設(shè)備故障或軟件升級等情形)時需要離線處理。本發(fā)明的目的是使系統(tǒng)可容錯(包含軟件、硬件錯誤)和可在線維護。
發(fā)明內(nèi)容本發(fā)明名為自組織分布式金融業(yè)務(wù)系統(tǒng),英文譯名Self-OrganizedDistributedBankingSystem,縮寫為S0DBS。本系統(tǒng)在采取了傳統(tǒng)的數(shù)據(jù)自動備份等技術(shù)的基礎(chǔ)之上,又采用了改進型Beowulf(見注①)式服務(wù)器集群(無固定管理節(jié)點)分布式處理的方式,用多臺相對廉價的標準pc服務(wù)器即可實現(xiàn)通常需要小型機才可以達到的性能,獲得了傳統(tǒng)的雙機熱備技術(shù)無法實現(xiàn)的高性能和故障時快速響應(yīng)能力。而且所有服務(wù)器硬件節(jié)點進行網(wǎng)狀連接,沒有特定的關(guān)鍵節(jié)點,實現(xiàn)了即使部分服務(wù)器硬件或軟件發(fā)生故障也不致引起系統(tǒng)整體癱瘓。系統(tǒng)內(nèi)的軟、硬件故障可以由系統(tǒng)自身自動檢測出來,并作自動智能處理(如重啟或初始化出錯節(jié)點),同時以聲光、手機短信等方式報警。使用自組織的體系結(jié)構(gòu),達到了硬件節(jié)點的故障可以自行檢測及自行修復(fù),硬件節(jié)點增減無需停機,軟件升級也無需重啟或暫停服務(wù)的有益效果。而且整體造價低廉,性價比高??蛻魧⑾硎艿较到y(tǒng)持久在線的高質(zhì)量金融服務(wù)。注①Beowulf是一種系統(tǒng)結(jié)構(gòu),它使得多個計算機組成的系統(tǒng)能夠用于并行計算。Beowulf系統(tǒng)通常有一個管理節(jié)點和多個計算節(jié)點構(gòu)成。它們通過以太網(wǎng)(或其他網(wǎng)絡(luò))連接。管理節(jié)點監(jiān)控計算節(jié)點,通常也是計算節(jié)點的網(wǎng)關(guān)和控制終端。當然它通常也是集群系統(tǒng)文件服務(wù)器。在大型的集群系統(tǒng)中,由于特殊的需求,這些管理節(jié)點的功能也可能由多個節(jié)點分攤。Beowulf系統(tǒng)通常由最常見的硬件設(shè)備組成,例如,PC、以太網(wǎng)卡和以太網(wǎng)交換機。Beowulf系統(tǒng)很少包含用戶定制的特殊設(shè)備。Beowulf系統(tǒng)通常采用那些廉價且廣為傳播的軟件,例如Linux操作系統(tǒng)、并行虛擬機(PVM)和消息傳遞接口(MPI)。近年來,在全球最強超級計算機的top500排行榜(麗w.top500.org)里,越來越多地被Beowulf式工作站集群所占據(jù),截至2008年8月,排名第一的超級計算機已經(jīng)不再是昂貴的藍色基因或者地球模擬器這種巨無霸式的大型機了,取而代之的是采用開源li皿x系統(tǒng)的Beowulf式微機工作站集群(命名為走鵑,由近3000臺標準pc服務(wù)器組成)。這些微機工作站集群應(yīng)用在核武器安全保障以及太空項目等要求高度安全、持久穩(wěn)定運行的場圖1為系統(tǒng)整體硬件連接及通訊結(jié)構(gòu)圖概覽,服務(wù)器集群是網(wǎng)狀連接的,連接至服務(wù)器集群的每條線路以及線路上的其它設(shè)備也都是冗余、并行式的,沒有單點故障點,這樣任何一個設(shè)備的故障或任何一條線路的中斷都不會影響到系統(tǒng)功能的正常運行。當系統(tǒng)自動檢測出問題,如某節(jié)點軟件或硬件發(fā)生故障時,系統(tǒng)將發(fā)出"警告"性聲光提示和手機短信提示(提示信息中包含必要的診斷信息),提醒監(jiān)控人員注意,同時采用自動重啟出錯部分的軟、硬件的方式試圖從錯誤中恢復(fù)。如遇自動修復(fù)無效的問題,如某節(jié)點無法重啟或重啟后仍然報錯,系統(tǒng)將發(fā)出"嚴重錯誤"聲光提示和手機短信提示(提示信息中包含必要的診斷信息),提醒監(jiān)控人員對相關(guān)設(shè)備采取人工方式修復(fù)。圖2為監(jiān)督者與工作者的圖示,監(jiān)督者記作方角矩形。在矩形的右上角用一個符號T來標明監(jiān)督者的類型。T的值要么為"O",代表"或(or)"型監(jiān)督,要么為"A",代表"與(and)"型監(jiān)督。監(jiān)督者能夠監(jiān)督任意個數(shù)的工作者或監(jiān)督者。對每一個被監(jiān)督的實體,監(jiān)督者都要知道如何來啟動、停止和重啟該實體。這種信息被保存在SSRS中,SSRS即"StartStopandRestartSpecification"(啟動停止重啟說明)。每個監(jiān)督者(監(jiān)督層次體系中的頂層監(jiān)督者除外)都有且僅有一個監(jiān)督者直接在它的上方,我們稱直接上層監(jiān)督者為直接下層的監(jiān)督者的父親(parent)。相反地,在監(jiān)督層次體系中某個監(jiān)督者直接下方的監(jiān)督者為該監(jiān)督者的孩子(children)。工作者被記作圓角矩形。工作者由乖函數(shù)來參數(shù)化。圖3為線性層次結(jié)構(gòu)的或式監(jiān)督樹,每個監(jiān)督者針對其每一個孩子都有一個SSRS,遵守下面的規(guī)則如果一個監(jiān)督者被其父親停止,那么該監(jiān)督者將停止其所有的孩子。如果一個監(jiān)督者的任何一個孩子崩潰,那么該監(jiān)督者將重啟該孩子。系統(tǒng)通過最頂層的監(jiān)督者啟動而啟動。最頂層的監(jiān)督者第一次啟動時,需要用到SSRS1。頂層監(jiān)督者有兩個孩子,即一個工作者和一個監(jiān)督者。頂層監(jiān)督者啟動一個工作者(為一個通過用乖函數(shù)WBF1進行參數(shù)化的behaviour),同時啟動一個子監(jiān)督者。層次體系中的下層監(jiān)督者也是按照類似的方式啟動起來,整個系統(tǒng)就跑起來了。圖4為AND/0R層次監(jiān)督樹。帶記號"A"表示一個"與"監(jiān)督者,帶記號"0"表示一個"或"監(jiān)督者。在一個與/或樹中的監(jiān)督者應(yīng)遵循如下規(guī)則如果一個監(jiān)督者被其父親停止,那么該監(jiān)督者將停止其所有的孩子。如果一個監(jiān)督者的一個孩子崩潰了,而自己是一個"與"監(jiān)督者,那么該監(jiān)督者將停止所有的孩子,然后重啟所有的孩子。如果一個監(jiān)督者的一個孩子崩潰了,而自己是一個"或"監(jiān)督者,那么該監(jiān)督者將重啟該孩子。"與"型監(jiān)督用于依賴性(d印endent)或關(guān)聯(lián)性(co-ordinate)的進程。在"與"型樹中,系統(tǒng)運行的成功依賴于所有孩子的成功——因此,當有任何一個孩子崩潰時,就應(yīng)該停止所有孩子并重啟它們。"或"型監(jiān)督可以用來協(xié)調(diào)獨立進程(ind印endentprocess)的行為。在"或"型樹中,孩子們的行為被認為是彼此獨立的,所以一個孩子不會影響到其它孩子,因此一個孩子出錯只需將該孩子進程或孩子設(shè)備重啟。圖5:兩個進程X、Y和一個協(xié)議檢查器C。當X向Y發(fā)送一個消息Q(Q是一個詢問)時,Y會以一個響應(yīng)R和一個新狀態(tài)S作為回應(yīng)。值對{R,S}就可以用協(xié)議描述中的規(guī)則進行類型檢查了。協(xié)議檢查器C位于X和Y之間,根據(jù)協(xié)議描述對X和Y之間來往的所有消息進行檢查。具體實施例方式我們在設(shè)計中,采用了"一切皆進程"的核心理念,依托服務(wù)器硬件集群設(shè)計了S0DBS。把軟件所有要處理的事務(wù)劃分成一系列層次化的任務(wù),每個任務(wù)有一個"強隔離的進程"來執(zhí)行,進程之間沒有任何共享狀態(tài),只能通過"消息傳遞"來通信。這種"強隔離的進程"不僅可以更真實地描述現(xiàn)實世界的信息處理過程,還成為軟件錯誤發(fā)生時保護系統(tǒng)的可靠性的最有力模型。另一個需要強調(diào)的思想是關(guān)于故障處理。由于業(yè)務(wù)處理都在一個個"強隔離的進程"中——我們把它們稱為"工作者",就防止了一個進程出錯會傳播到其他的進程。業(yè)務(wù)處理進程的運行狀況由另外專門的進程來看護——我們把它們稱為"監(jiān)視者"。"工作者"和"監(jiān)視者"組成一個層次化的監(jiān)督模型,使得一個進程發(fā)生故障時,整個系統(tǒng)可以作出相應(yīng)的調(diào)整,保障系統(tǒng)最大限度地提供服務(wù)。對于軟件系統(tǒng)中behaviour(行為機制)庫的設(shè)計思想,則是將程序的并發(fā)處理和順序化處理分開。這樣,我們把并發(fā)部分抽象出來,讓系統(tǒng)從最初的"脆弱"演化到"可容錯"。金融類應(yīng)用都是一些復(fù)雜、大型的程序,雖然經(jīng)過了嚴密的測試,但是投入運行后還是難免會有錯誤產(chǎn)生,硬件也會有意外崩潰的可能。我們假設(shè)這些程序和硬件不可避免地會含有錯誤,進而尋求在軟硬件包含錯誤的情況下構(gòu)建可靠系統(tǒng)的方法。我們主要關(guān)注點之一是在程序自身包含有錯誤的情況下如何構(gòu)建出可靠的系統(tǒng)這一問題。構(gòu)建這樣的系統(tǒng)對所采用的任何一種編程語言都有一些特殊的要求。這里會討論這些對語言的具體的特殊要求,并將展示本系統(tǒng)是如何滿足這些要求的。這些要求可以在編程語言中解決,也可以在語言所附帶的標準庫中解決。我們將論證在構(gòu)建可容錯系統(tǒng)時,哪些要求應(yīng)該在語言中解決,而哪些要求可以重新構(gòu)建標準庫來解決。這些合起來構(gòu)成了構(gòu)建可容錯軟件系統(tǒng)SODBS的基礎(chǔ)。1緒論我們?nèi)绾稳ゾ帉懺谟熊浖蛘哂布e誤的條件下有合理行為的軟件呢?這是我們想要回答的核心問題。比如個人電腦的windows操作系統(tǒng),正式發(fā)布后的每個月都會針對新發(fā)現(xiàn)的錯誤而發(fā)布一些新的補丁程序,大型的系統(tǒng)也是一樣,往往在交付的時候還存在著許多軟件錯誤,然而我們卻奢望它們能夠運行正常。系統(tǒng)有不完善的環(huán)節(jié),而我們又希望它可靠,這就對系統(tǒng)提出了一定的要求。這些要求是能夠被滿足的,要么在所采用的編程語言中,要么在應(yīng)用程序所調(diào)用的標準庫中。在此文檔中,我們會列舉出我們認為的可容錯系統(tǒng)所必須具備的本質(zhì)特性,我們還將展示這些特征在我們的系統(tǒng)中如何被滿足。某些本質(zhì)特性是在我們的編程語言中被滿足的,而另外一些則是在我們編寫的庫模塊中被滿足的。語言和庫合起來構(gòu)成了構(gòu)建可靠軟件系統(tǒng)的基礎(chǔ),使得即使存在編程錯誤,系統(tǒng)仍然能夠按照合理的方式運行。此文檔關(guān)注的是軟件的容錯性在語言、庫和操作系統(tǒng)方面的要求以及實現(xiàn)辦法。我們用C語言搭建了一種純消息傳遞平臺——即一種基于獨立性很強的并行進程的新描述性準函數(shù)語言,我們的編程模型廣泛使用了速錯(fail-fast)進程。這項技術(shù)在構(gòu)建可容錯系統(tǒng)的硬件平臺中被普遍使用,但是在軟件設(shè)計方面卻用得不多。這主要是因為傳統(tǒng)的編程語言并不允許不同的軟件模塊以彼此互不干擾的方式存在。當前普遍使用的是多線程的編程模型,該模型中資源是共享的,這樣就造成了線程很難真正隔離起來,就可能導致一個線程中的錯誤會傳播到另一個線程中,這就破壞了系統(tǒng)內(nèi)部的堅固性。2架構(gòu)模型這里我們提出了一個用于構(gòu)建容錯系統(tǒng)的軟件架構(gòu)。雖然每個人對于架構(gòu)一詞都有一個模糊的概念,但是這個詞卻幾乎沒有一個廣為接受的定義,這就導致了很多誤解。我認為如下定義對軟件架構(gòu)進行了比較全面的總結(jié)架構(gòu)是一組有關(guān)軟件系統(tǒng)組織方式的重要決策;是對系統(tǒng)構(gòu)成元素、元素接口以及這些元素間協(xié)作行為方式的選擇;是一種把這些結(jié)構(gòu)和行為元素逐步組合為更大子系統(tǒng)的合成方式;也是一種構(gòu)建風格,在其指導下把這些元素、元素接口、元素間的協(xié)作和合成組織起來。2.1架構(gòu)的定義從最高的抽象層次上看,架構(gòu)就是"一種思考世界的方式"。然而,從實用性的層次上看,我們就必需得把我們看待世界的方式轉(zhuǎn)化為一本實用的手冊和一組規(guī)程,它們可以告訴我們?nèi)绾问褂梦覀兛创澜绲奶囟ǚ绞絹順?gòu)造一個特定的系統(tǒng)。我們的軟件架構(gòu)通過如下一些方面來刻畫1.問題領(lǐng)域——我們的架構(gòu)是為解決什么類型的問題而設(shè)計的?軟件架構(gòu)一定不是通用的,而是為解決某一類特定問題而設(shè)計的。缺少了關(guān)于用來解決哪類問題的描述的架構(gòu)是不完整的。2.哲學——軟件構(gòu)造方法背后的原理是什么?架構(gòu)的核心思想是什么?3.軟件構(gòu)造指南——我們?nèi)绾蝸硪?guī)劃一個系統(tǒng)?我們需要一個明確的軟件構(gòu)造指南集。我們的系統(tǒng)將由一個程序員團隊來編寫和維護——所以對所有的程序員和系統(tǒng)設(shè)計者來說,理解系統(tǒng)的架構(gòu)和它的潛在哲學是很重要的。從實用性的角度來講,這些知識以軟件構(gòu)造指南的方式表現(xiàn)出來更便于維持。一個完整的軟件構(gòu)造指南集包括編程規(guī)則集、例子程序和培訓資料等等。4.預(yù)先定義好的部件——以"從一組預(yù)先定義好的部件中選擇"的方式進行設(shè)計遠比"從頭設(shè)計"的方式要來得容易。我們的庫包含了一個完整的現(xiàn)成部件集(稱之behaviour庫),一些常用的系統(tǒng)都可以使用這些部件構(gòu)建起來。例如sodbsserver這種behaviour就可以用來構(gòu)建client—server系統(tǒng),sodbs—event這禾中behaviour可以用來構(gòu)建基于事件(event-based)的程序。5.描述方式——我們?nèi)绾蚊枋瞿骋徊考慕涌??我們?nèi)绾蚊枋鱿到y(tǒng)中兩個部件之間的通信協(xié)議?我們?nèi)绾蝸砻枋鱿到y(tǒng)中的靜態(tài)和動態(tài)結(jié)構(gòu)?為了回答這些問題,我們將介紹一些專門的符號。其中一些用來描述程序的API,而其他的則用來描述協(xié)議和系統(tǒng)結(jié)構(gòu)。6.配置方式——我們?nèi)绾蝸韱印⑼V购团渲梦覀兊南到y(tǒng)?我們可以在系統(tǒng)工作過程中進行重配置嗎?2.2問題領(lǐng)域我們的系統(tǒng)平臺是為開發(fā)金融軟件而設(shè)計的。金融系統(tǒng)對可靠性和容錯性有著苛刻的需求。金融系統(tǒng)需要"永久地"運行,必須有軟實時的響應(yīng)能力,當發(fā)生軟件和硬件故障的時候要有合理的反應(yīng)。我們總結(jié)了金融系統(tǒng)需要具有的十條屬性要求。1.系統(tǒng)必須能夠應(yīng)對超大量的并發(fā)活動。2.必須在規(guī)定的時刻或規(guī)定的時間內(nèi)完成任務(wù)。3.系統(tǒng)應(yīng)該可以跨計算機分布運行。4.系統(tǒng)要能夠控制硬件。5.軟件系統(tǒng)往往很龐大。6.系統(tǒng)要具有復(fù)雜的功能,例如特性沖突。7.系統(tǒng)應(yīng)該能不間斷運行許多年。8.軟件維護(例如重配置等)應(yīng)該能在不停止系統(tǒng)的情況下進行。9.滿足苛刻的質(zhì)量和可靠性需求。10.必須提供容錯功能,包括硬件失靈和軟件錯誤。我們可以對上述需求作出如下分析并發(fā)(concurrency)—金融系統(tǒng)天生就應(yīng)該是并發(fā)的,因為對于金融設(shè)備來說,經(jīng)常同時有數(shù)以萬計的用戶在與金融設(shè)備進行交互。這就意味著金融系統(tǒng)必須能夠有效地處理成千上萬的并發(fā)活動。軟實時(softreal-time)——在金融系統(tǒng)中,很多操作必須要在規(guī)定的時間內(nèi)完成。其中有些操作是嚴格要求實時的,也就是說如果給定的操作在給定的時段里沒有執(zhí)行完,整個操作就被取消。而有些操作只是受到某種形式的定時器的監(jiān)視,如果定時器超時而操作尚未完成,則重新執(zhí)行一遍。編寫這樣的系統(tǒng),就需要有效地管理起數(shù)以萬計的定時器。分布式(distributed)——金融系統(tǒng)并不是天生分布式的,我們的系統(tǒng)應(yīng)該以一種便于從單節(jié)點系統(tǒng)(single-nodesystem)向多節(jié)點分布式系統(tǒng)(multi-nodedistributedsystem)轉(zhuǎn)變的方式來創(chuàng)建。硬件交互(hardwareinteraction)——金融系統(tǒng)有大量的外圍硬件需要控制和監(jiān)控。這就意味著要能夠?qū)懗龈咝У脑O(shè)備驅(qū)動程序,并且不同的設(shè)備驅(qū)動之間進行上下文切換也要高效。大型軟件系統(tǒng)(largesoftwaresystems)——金融系統(tǒng)都很龐大,這就意味著金融軟件系統(tǒng)必須在源代碼達到數(shù)百萬行的時候也能工作。復(fù)雜的功能(complexfunctionality)——金融系統(tǒng)都有著復(fù)雜的功能。市場的壓力迫使系統(tǒng)的開發(fā)和部署要具有許多復(fù)雜的特性。通常,在這些特性之間的相互影響還沒有被很好的理解的情況下,就必須得部署系統(tǒng)。在系統(tǒng)的運行期間,這些特性集很可能需要以多種方式進行修改和擴展。功能和軟件的升級必須"就地進行",也就是說,不能夠讓系統(tǒng)停下來。持續(xù)運行(continuousoperation)——金融系統(tǒng)要設(shè)計成可以持續(xù)運行許多年。這就意味著在系統(tǒng)不停下來的情況下進行軟件和硬件的維護(通常要求在40年里停機時間不超過2小時)。高質(zhì)量要求(qualityrequirements)——即使在發(fā)生錯誤時,金融系統(tǒng)也應(yīng)該提供可接受的服務(wù)。特別是銀行存取款設(shè)備,可靠性要求極高。容錯性(faulttolerance)——金融系統(tǒng)應(yīng)該是"容錯"的。即從開始我們就知道會發(fā)生故障,但是我們必須設(shè)計出一些可以處理這些錯誤的軟件和硬件基礎(chǔ)設(shè)施,并在發(fā)生故障的時候仍然能夠提供可接受的服務(wù)。雖然這些需求最初是來自金融界,但決不僅僅適用于該特定問題領(lǐng)域。許多現(xiàn)代互聯(lián)網(wǎng)服務(wù)(例如電子商務(wù)服務(wù)器)就有著非常相似的需求列表。2.3哲學我們怎么才能夠構(gòu)建出在軟件存在錯誤的時候具有合理行為的可容錯的軟件系統(tǒng)呢?這是此文檔余下部分要回答的問題。我們先給出一個簡潔的答案,在本文的剩余部分會對其進行細化。為了構(gòu)建出在軟件存在錯誤的時候仍具有合理行為的可容錯軟件系統(tǒng),我們做了如下這些事情我們將軟件組織成一個系統(tǒng)要完成的任務(wù)的層次結(jié)構(gòu),每一個任務(wù)對應(yīng)于一組目標,具有給定任務(wù)的軟件必須嘗試去完成和該任務(wù)相關(guān)的目標。所有任務(wù)按照復(fù)雜性排序。最頂層的任務(wù)最復(fù)雜。如果最頂層任務(wù)完的目標都被完成,那么整個系統(tǒng)就運轉(zhuǎn)正常。較低層次的任務(wù)應(yīng)當能夠保持系統(tǒng)以一種可接受的方式運轉(zhuǎn),即使系統(tǒng)所提供的服務(wù)有所折扣。系統(tǒng)中低層任務(wù)較高層任務(wù)更容易完成其目標。我們將盡力完成頂層的任務(wù)。當在完成某一目標的過程中檢測到了一個錯誤,我們將嘗試糾正這個錯誤。當我們不能夠糾正該錯誤的時候,我們將立即取消當前的任務(wù)而啟動一個更簡單一些的任務(wù)。編寫這樣一個任務(wù)層次需要一套強有力的封裝方法。我們需要強有力的封裝方法來隔離錯誤。我們不想再去編寫那種系統(tǒng)中的一個部分發(fā)生的錯誤會對其他部分產(chǎn)生不利影響的系統(tǒng)。我們需要以一種能夠檢測到在試圖完成目標時所發(fā)生的所有錯誤的方式,來隔離為了完成某一目標而編寫的所有代碼。并且,當我們在試圖同時完成多個目標時,我們不希望系統(tǒng)中某個部分所發(fā)發(fā)生的錯誤,會傳播到系統(tǒng)的另外一個部分中。因此,在構(gòu)建可容錯軟件系統(tǒng)的過程中要解決的本質(zhì)問題就是故障隔離。不同的程序員會編寫不同的模塊,有的模塊正確,有的存在錯誤。我們不希望有錯誤的模塊對沒有錯誤的模塊產(chǎn)生任何不利的影響。為了提供這種故障隔離機制,我們采用了傳統(tǒng)操作系統(tǒng)中進程的概念。進程提供了保護區(qū)域,一個進程出錯,不會影響到其他進程的運行。不同程序員編寫的不同應(yīng)用程序分別跑在不同的進程中;一個應(yīng)用程序的錯誤不會對系統(tǒng)中運行的其他應(yīng)用程序產(chǎn)生副作用。這種選擇當然滿足了初步的要求。然而因為所有進程使用同一片CPU、同一塊物理內(nèi)存,所以當不同進程爭搶CPU資源或者使用大量內(nèi)存的時候,還是可能對系統(tǒng)中的其他進程產(chǎn)生負面影響。進程間的相互沖突程度取決于操作系統(tǒng)的設(shè)計特性。在我們的系統(tǒng)中,進程和并發(fā)編程是語言的一部分,而不是由宿主操作系統(tǒng)提供的。這樣做比直接采用操作系統(tǒng)進程擁有很多優(yōu)勢并發(fā)程序可以一致地運行在不同的操作系統(tǒng)上——不同的特定操作系中是如何實現(xiàn)進程的不會對我們造成限制。我們的程序運行在不同的操作系統(tǒng)和處理器上唯一可見的差異就是CPU的處理速度和內(nèi)存的大小。所有的同步問題和進程間通信都應(yīng)當跟宿主的操作系統(tǒng)的特性沒有一點關(guān)系。我們這種基于語言的進程比傳統(tǒng)的操作系統(tǒng)進程要輕量得多。在我們的語言里,創(chuàng)建一個進程是非常高效的,要比大多數(shù)操作系統(tǒng)中進程的創(chuàng)建快幾個數(shù)量級,甚至比大多數(shù)語言中線程的創(chuàng)建都快幾個數(shù)量級。我們的系統(tǒng)對操作系統(tǒng)的要求非常少。我們只用了操作系統(tǒng)很小的一部分服務(wù),所以把我們的系統(tǒng)移植到譬如嵌入式系統(tǒng)等特定環(huán)境下是相當簡單的。我們的應(yīng)用程序是通過大量互相通信的并行進程構(gòu)建起來的。我們采用這種方式是因為它提供了一個架構(gòu)基礎(chǔ)設(shè)施——我們可以用一組相互通信的進程組織起我們的系統(tǒng)。通過枚舉出系統(tǒng)中的所有進程,并定義出進程間消息傳遞的通道,我們就可以很方便地把系統(tǒng)劃分成定義良好的子部件,并可以對這些子部件進行單獨實現(xiàn)和測試。這種方法學也是SDL系統(tǒng)設(shè)計方法學的最高境界。巨大的潛在效率——設(shè)計成以許多獨立的并行進程來實現(xiàn)的系統(tǒng),可以很方便地實現(xiàn)在多處理器上,或者運行在分布式的處理器網(wǎng)絡(luò)上。注意,這種效率的提升只是潛在的,只有當應(yīng)用程序可以被分解成許多真正獨立的任務(wù)時,才能產(chǎn)生實效。如果任務(wù)之間有很強的數(shù)據(jù)依賴,這種提升往往是不可能的。故障隔離——沒有共享數(shù)據(jù)的并發(fā)進程提供了一種強大的故障隔離方法。一個并發(fā)進程的軟件錯誤不會影響到系統(tǒng)中其他進程的運行。在并發(fā)的這三種用法中,前兩條并不是其本質(zhì)特性,可以由某種內(nèi)置的調(diào)度程序通過在進程間提供不同的偽并行(pseudo-parallel)時分方式來獲得。第三個特性對于編寫可容錯系統(tǒng)的軟件來說,則是本質(zhì)性的。每一項獨立的活動都在一個完全獨立的進程中來執(zhí)行。這些進程沒有共享數(shù)據(jù),進程之間只通過消息傳遞的方式進行通信,這就限制了軟件錯誤造成的影響。一旦進程之間共享有任何公共資源,譬如內(nèi)存,或指向內(nèi)存的指針,或互斥體等等,一個進程中的一個軟件錯誤破壞共享資源的可能性就存在。因為消除大型軟件系統(tǒng)中的這類軟件錯誤仍然是一個未解的難題,所以我們認為構(gòu)建大型的可靠系統(tǒng)的唯一現(xiàn)實的方法就是把系統(tǒng)分解成許多獨立的并行進程,并為監(jiān)控和重啟這些進程提供一些機制。2.4支持高并發(fā)的系統(tǒng)在我們的系統(tǒng)中,并發(fā)扮演著核心角色,它是如此核心以至于我們塑造了面向并發(fā)編程(ConcurrencyOrientedProgramming)這個術(shù)語,以把這種編程風格和其他編程風格區(qū)分開來。在面向并發(fā)編程中,程序的并發(fā)結(jié)構(gòu)應(yīng)該遵循應(yīng)用本身的并發(fā)結(jié)構(gòu)。這種編程風格特別適用于編寫那些對現(xiàn)實世界建?;蚺c現(xiàn)實世界進行交互的應(yīng)用程序。面向并發(fā)編程同樣也具有面向?qū)ο缶幊痰膬蓚€主要優(yōu)點。即多態(tài)(polymorphism)以及使用預(yù)先定義的協(xié)議使得不同進程類型的實例之間可以具有相同的消息傳遞接口。當我們把一個問題分解成許多并發(fā)進程的時候,我們可以讓所有的進程響應(yīng)同一種消息(即多態(tài)),并且可以讓所有的進程都遵循相同的消息傳遞接口。并發(fā)一詞是指同時發(fā)生的活動集合。現(xiàn)實世界就是并發(fā)的,是由無數(shù)同時發(fā)生的活動組成的。在微觀上看,我們自己的身體就是由同時運動著的原子、分子組成的。從宏觀上看,整個宇宙也是由同時運動著的星系組成的。我們做一件簡單的事情的時候,譬如在高速公路上開車時,我們能覺察到身邊行駛著飛速的車流,但是我們一樣能夠完成開車這一復(fù)雜的任務(wù),并且可以不假思索就避開潛在的危險。在現(xiàn)實世界中,順序化的(sequential)活動非常罕見。當我們走在大街上的時候,如果只看到一件事情發(fā)生的話我們一定會感到不可思議,我們期望碰到許多同時進行的活動。如果我們不能對同時發(fā)生的眾多事件所造成的結(jié)果進行分析和預(yù)測的話,那么我們將會面臨巨大的危險,像開車這類的任務(wù)我們就不可能完成了。事實上我們是可以做那些需要處理大量并發(fā)信息的事情的,這也表明我們本來就是具有很多感知機制的,正是這些機制讓我們能夠本能地理解并發(fā),而無需有意識地思考。然而對于計算機編程來說,情況卻突然變得相反。把活動安排成一個順序發(fā)生的事件鏈被視為是一種規(guī)范,并認為在某種意義上講這樣更簡單,而把程序安排成一組并發(fā)活動則是要盡可能避免的,并常常認為會困難一些。我相信這是由于幾乎所有傳統(tǒng)的編程語言對真正的并發(fā)缺乏有力支持造成的。絕大多數(shù)的編程語言本質(zhì)上都是順序化的;在這些編程語言中所有的并發(fā)性都僅僅由底層操作系統(tǒng)來提供,而不是由編程語言來提供。在此文檔中,我展現(xiàn)了這樣的一個世界,其中并發(fā)是由編程語言來提供的,而不是由底層操作系統(tǒng)來提供。我把對并發(fā)提供良好支持的語言稱為面向并發(fā)的語言(ConcurencyOrientedLMigimge),簡禾爾C0PL。2.4.1基于現(xiàn)實世界編程我們常常想編寫一些對現(xiàn)實世界進行建?;蛘吆推浣换サ某绦颉S肅0PL編寫這樣的程序相當容易。首先,我們來進行一個分析,它有三個步驟1.從真實世界中的活動中識別出真正的并發(fā)活動;2.識別出并發(fā)活動之間的所有消息通道;3.寫下能夠在不同的消息通道中流通的所有消息;然后我們來編寫程序。程序的結(jié)構(gòu)要嚴格保持與問題的結(jié)構(gòu)一致,即每一個真實世界里的活動都嚴格映射到我們編程語言中的一個并發(fā)進程上。如果從問題到程序的映射比例為l:l,我們就說程序與問題是同構(gòu)(isomorphic)的。映射比例為1:1這一點非常重要。因為這樣可以使得問題和解之間的概念隔閡最小化。如果比例不為i:i,程序就會很快退化而變得難以理解。在使用非面向并發(fā)的編程語言來解決并發(fā)問題時,這種退化是非常常見的。在非面向并發(fā)的編程語言中,為了解決一個問題,通常要由同一個語言級的線程或進程來強制控制多個獨立的活動,這就必然導致清晰性的損失,并且會使程序滋生復(fù)雜的、難以復(fù)現(xiàn)的錯誤。在分析問題時,我們還必須為我們的模型選擇一個合適的粒度。比如,我們在編寫一個即時通信系統(tǒng)(instantmessagingsystem)時,我們使用每個用戶一個進程的方式,而不是將用戶身上的每一個原子對應(yīng)到一個進程。2.4.2C0PL的特征COPL可以由如下6個特性來刻畫l.COPL應(yīng)當支持進程。每一個進程應(yīng)該可以看作是一個自包含的虛擬機器(self-containedvirtualmachine)。2.運行在同一機器上的各個進程應(yīng)該被高度隔離。一個進程中的故障不能對其他進程產(chǎn)生副作用,除非這種交互在程序中被明確化。3.每個進程必須用一個唯一的、不可仿造的標識符來標識。我們稱之為進程的Pid。4.進程之間沒有共享狀態(tài)。進程只通過消息傳遞來進行交互。只要知道進程的Pid,就可以向它發(fā)消息。5.消息傳遞被認為是不可靠的,無傳輸保障的。6.—個進程應(yīng)當可以檢測另一個進程中的故障,并可以知道發(fā)生故障的原因。值得注意的是,COPL提供的并發(fā)性一定是真正的并發(fā)性,因此以進程的形式存在的對象都是真正并發(fā)的,進程間的消息傳遞也是真正的異步消息,而不像許多面向?qū)ο笳Z言中一樣是通過遠程過程調(diào)用(remoteprocedurecall)來冒充。還應(yīng)當注意,故障的原因并不總是正確的。例如,在一個分布式系統(tǒng)中,我們可能收到進程已經(jīng)死亡的通知消息,然而事實上是發(fā)生了一個網(wǎng)絡(luò)錯誤。2.4.3進程隔離對理解COP和創(chuàng)建可容錯軟件來說,一個核心的概念就是進程隔離(isolation),同一臺計算機上運行的兩個進程,應(yīng)當如同分別獨立運行在物理上分離的兩臺計算機上一樣。理想的架構(gòu)當然是面向并發(fā)的程序的每一個進程都給分配一個專用的處理器。但是在理想成為現(xiàn)實之前,我們不得不面對的事實是多個進程要運行在同一臺計算機上。然而我們?nèi)匀粦?yīng)當認為所有的進程都運行在物理上獨立的計算機上。進程隔離有著許多好處1.進程具有"不共享任何資源"的語意。這一點很明顯,因為進程被認為是運行在物理上獨立的計算機上的。2.消息傳遞是進程之間傳遞數(shù)據(jù)的唯一方式。因為進程之間沒有任何共享資源,進程間交互數(shù)據(jù)只能采用這種方式。3.進程隔離意味著消息傳遞必須是異步的。如果進程通信采用同步方式,那么當消息的接收者偶然發(fā)生一個軟件錯誤時,就會永久阻塞住消息的發(fā)送者,破壞了隔離的特性。4.沒有共享資源,所以進行分布式計算所需的任何數(shù)據(jù)都必須通過拷貝。因為沒有共享資源,進程間的交互只能通過消息傳遞,所以我們也不會知道消息什么時候到達接收者(我們說過消息傳遞是天生不可靠的)。知道消息是否被正確送達的唯一方法就是發(fā)送一個確認消息回來。乍一看,要編寫一個滿足上述規(guī)定的多進程系統(tǒng)是很困難的——畢竟在針對大多數(shù)順序化編程語言所做的并發(fā)擴展中,幾乎提供了完全相反的功能,諸如鎖、信號量、共享數(shù)據(jù)保護以及可靠消息傳遞。幸運的是,我們這種相反的做法被證明是正確的——編寫一個這樣的系統(tǒng)簡單得出奇,并且所編寫的程序不費吹灰之力就可以變得可伸縮,變得可容錯。因為所有的進程都要求完全獨立,所以增加新的進程不會對原系統(tǒng)產(chǎn)生影響。因為整個軟件就是一組獨立的進程的集合,因此無需對應(yīng)用軟件作大的更改就容納更多的處理器。因為沒有對消息傳遞的可靠性加以任何假設(shè),所以我們寫的應(yīng)用程序在消息傳遞并不可靠的時候必須一樣可以工作,在消息傳遞發(fā)生錯誤的時候也一樣要能夠工作。我們這樣做了以后,當我們需要向上伸縮我們的系統(tǒng)的時候,就會得到回報。2.4.4進程的名字我們要求所有進程的名字都是不可仿造的。這就意味著不可能猜測一個進程的名字,從而與之交互。我們假設(shè)所有的進程都知道它們自己的名字,以及由它們所創(chuàng)建的其他進程的名字。也就是說,父進程知道其子進程的名字。要想使用C0PL進行編程,我們就需要一種機制來找到相關(guān)進程的名字。一旦我們知道了一個進程的名字,我們就可以給它發(fā)消息。系統(tǒng)的安全性與進程名的獲取方法是密切相連的。如果別人不知道進程的名字,就沒有任何方法可以與之交互,這個系統(tǒng)也就是安全的了。一旦進程的名字廣為外界所知,這個系統(tǒng)的安全性就削弱了。我們把以受控的方式向其他進程透露名字的過程稱為名字散布問題(n謙distributionproblem)-系統(tǒng)安全性的關(guān)鍵就在于名字散布問題。當我們把一個Pid透露給另外一個進程,我們就說我們公布了該進程的名字。如果一個進程的名字從未被公布過,就不會存在安全性問題了。因此,獲取進程的名字是安全性的關(guān)鍵因素。因為進程名是不可仿造的,所以只要我們能夠?qū)㈥P(guān)于進程名字的知識限制在可信進程的范圍內(nèi),我們的系統(tǒng)就肯定是安全的。在許多古老的宗教信仰中,人們都相信人類可以通過靈魂的真名來支配靈魂,以獲得超越靈魂的力量。一旦獲知了靈魂的真名,就可以獲得超越它的力量,并且可以用這個真名來驅(qū)使靈魂去做很多事。C0PL采用的是相同的思想。2.4.5消息傳遞消息傳遞須遵循如下規(guī)則1.消息傳遞當是原子化的(atomic),意思是一個消息要么整個兒被傳遞,要么根本就不傳遞。2.—對進程之間的消息傳遞是有序的,意思是當在任何一對進程之間進行消息序列的收發(fā)時,消息被接收的順序與對方發(fā)送的順序相同。3.消息不能包含指向進程中的數(shù)據(jù)結(jié)構(gòu)的指針——它們只能夠包含常量和(或)Pid。注意,第2點只是一個設(shè)計決策,并沒有對用來傳送消息的網(wǎng)絡(luò)的基礎(chǔ)語意作任何反映。下層的傳輸網(wǎng)絡(luò)可能將消息重新排序,但是對于任一對進程來講,這些消息在被交付前會被進行緩存和重組,以使它們形成正確的順序。比起硬要允許消息按任意順序傳遞來,這種假設(shè)可以使得編寫消息傳遞的應(yīng)用程序要容易得多。我們說這種消息傳遞具有發(fā)送并祈禱(sendandpray)之義。我們發(fā)送一條消息以后,就祈禱它能夠到達對方。一旦收到對方發(fā)送回來的確認消息(有時候也叫做往返確認),就可以確認消息已經(jīng)送達對方。消息傳遞還可以用于同步(synchronisation)。假設(shè)我們希望同步兩個進程A和B。如果A向B發(fā)送了一條消息,那么B只能在A發(fā)送了這個消息之后的某個時間點才收到該消息。這一點就是分布式系統(tǒng)理論里的因果次序(casualordering)。在COPL中,所有的進程間同步都是基于這一簡單的思想。2.4.6協(xié)議部件之間隔離,采用消息傳遞的交互方式,這在架構(gòu)上對于保護系統(tǒng)免受錯誤影響來說是足夠了。但是對于說明系統(tǒng)的行為來說,是不夠的,對于在發(fā)生了某種錯誤時判斷到底是哪個部件出了錯也是不夠的。到目前為止,我們都只是假設(shè)了單個部件出錯,單個部件要么就正常運行,要么死了就死了。然而,實際會發(fā)生的情況是可能沒有觀察到有部件死掉,而系統(tǒng)卻已沒有如期地工作。為了完善我們的模型,我們添加了一些新的東西。我們不僅需要部件的完全獨立性,部件之間只通過消息傳遞進程交互,我們也需要制定部件之間相互通信所采用的協(xié)議。通過制定出通信協(xié)議,如果遵循該協(xié)議進行通信的兩個部件中一旦有誰違犯了協(xié)議,我們就可以很容易地識別出來。我們可以通過對程序的靜態(tài)分析——如果可能的話,還可以把運行時檢查編譯到生成碼中,以便當靜態(tài)分析失效時也報告錯誤——來保證協(xié)議被貫徹了。2.4.7C0P與程序員團隊構(gòu)建大的軟件系統(tǒng)需要許多程序員的共同努力,有時候甚至達到好幾百人。為了把這么多人的工作都協(xié)調(diào)起來,通常是把程序員組織成小一些的開發(fā)小組或團隊,每個小組負責系統(tǒng)中的一個或多個邏輯部件。日復(fù)一日,各個小組之間通過消息傳遞(如email或電話)來進行交流,而不必頻繁地見面。在某些情況下,開發(fā)小組分布在不同的國家,從來都不見面。我們發(fā)現(xiàn)不僅僅是軟件系統(tǒng)因各種原因需要被組織成獨立的部件,各部件以純消息傳遞的方式進行通信,而且這也是大型軟件開發(fā)群體的組織方式。2.5系統(tǒng)需求為了支持面向并發(fā)的編程風格,為了構(gòu)建滿足金融系統(tǒng)需求的軟件,我們對系統(tǒng)的根本特性提出了一組需求。這些需求對于系統(tǒng)來說,是一個整體——我并不關(guān)心這些需求是由編程語言來滿足,還是由語言所附帶的庫或創(chuàng)建方法來滿足。我們對下層的操作系統(tǒng)和編程語言有6條根本需求。Rl.并發(fā)性——我們的系統(tǒng)必須支持并發(fā)性。創(chuàng)建或銷毀一個并發(fā)進程的計算開銷一定要非常小,即使創(chuàng)建大量的并發(fā)進程,也不應(yīng)當帶來災(zāi)難。R2.錯誤封裝——一個進程中發(fā)生的錯誤一定不能破壞系統(tǒng)中其他的進程。R3.故障檢測——一定要可以檢測到本地異常(本地進程中發(fā)生的異常)和遠程異常(非本地進程中發(fā)生的異常)。R4.故障識別——我們要能夠識別出異常產(chǎn)生的原因。R5.代碼升級——要有某種機制來替換執(zhí)行中的代碼,而不必停下系統(tǒng)。R6.持久存儲——我們需要把數(shù)據(jù)按某種策略存儲下來,以便恢復(fù)一個已經(jīng)崩潰的系統(tǒng)。還有一點非常重要,即為了滿足上述需求所采用的實現(xiàn)方式一定要高效——如果不能夠可靠地創(chuàng)建幾十萬個進程,那么并發(fā)性就沒什么大用;如果故障報告中沒有包含足夠的信息使得隨后可以糾正故障,那么故障識別也就沒有什么大用。上述需求的實現(xiàn)方式可以是多種多樣。譬如并發(fā)性,既能夠由語言原語來提供,也能夠由操作系統(tǒng)來提供(例如Unix)。像C和Java之類的語言本身并不是面向并發(fā)的,但是可以利用操作系統(tǒng)的那些讓人覺得可以達到并發(fā)性的原語來獲得并發(fā)性。確實,并發(fā)程序可以由本身并不具備并發(fā)性的語言來編寫。2.6語言需求用來編寫并行系統(tǒng)的編程語言必須包括封裝原語——語言必須有多種手段來限制錯誤的蔓延。應(yīng)當可以把一個進程隔離起來,免得它會破壞其他進程。并發(fā)性——語言必須提供一種輕量化的機制來創(chuàng)建并行進程,以及在進程間發(fā)送消息。進程的上下文切換、消息傳遞必須非常高效。并行進程還必須以一種合理的方式來分享CPU時間,以便當前使用CPU的進程不至于壟斷CPU,而其他的進程處于"準備好"狀態(tài)而得不到處理。錯誤檢測原語——語言應(yīng)當允許一個進程監(jiān)控另一個進程,從而檢測被監(jiān)控進程是否因任何原因而終止。位置透明——如果我們知道了一個進程的Pid,我們就應(yīng)該可以向它發(fā)送消息,無論它是本地還是遠程的。動態(tài)代碼升級——應(yīng)該可以動態(tài)替換運行時系統(tǒng)中的代碼。注意,因為許多進程可能同時按照同一份代碼在運行,所以我們需要一種機制,來允許現(xiàn)有的進程按照"老"的代碼運行,而同時"新"進程按照修改后的代碼運行。上述對于編程語言的需求不僅要被滿足,而且要以一種合理有效的方式被滿足。當我們編程的時候,不希望我們的表達自由受到諸如進程數(shù)目之類的限制,我們也不希望擔心當一個進程試圖壟斷CPU時會發(fā)生什么事情。系統(tǒng)中進程個數(shù)的上限應(yīng)該足夠大,以便我們編程時不用把進程的個數(shù)作為一個限制因素來考慮。例如,為了構(gòu)建一個處理1萬個并行用戶會話的支付系統(tǒng),我們可能需要創(chuàng)建多達10萬個進程3。上述6條特性對于簡化應(yīng)用程序的編寫是必要的。如果我們能夠?qū)栴}的并發(fā)結(jié)構(gòu)以l:1的方式映射到解決該問題的應(yīng)用程序的進程結(jié)構(gòu)上的話,我們把語義上一組分布式的交互部件映射到程序中的過程就會極大地簡化。2.7庫需求語言并不是無所不能的——許多東西是由我們開發(fā)的系統(tǒng)庫提供的。程序庫必須提供持久存儲——由它存儲用于故障恢復(fù)的信息。設(shè)備驅(qū)動程序——這些程序提供了一種與外界交互的機制。代碼升級——它允許我們升級運行系統(tǒng)中的代碼。運行基礎(chǔ)——它解決系統(tǒng)的啟動、停止和錯誤報告問題。觀察一下我們的程序庫,不難看出它們雖然是用C編寫的,但是它們提供的服務(wù)都是本來可以由操作系統(tǒng)很方便地提供的服務(wù)。因為S0DBS的進程是彼此隔離的,只以消息傳遞的方式彼此通信,所以它們的行為就非常像操作系統(tǒng)的進程,后者是通過管道(Pipe)和套接字(socket)進行通信。本來可以很方便就由操作系統(tǒng)提供的許多特性被移到了編程語言中,于是操作系統(tǒng)就只需要提供設(shè)備驅(qū)動的一組原語就夠了。2.8應(yīng)用程序庫持久化存儲等特性并不是作為Sodbsng的語言原語來提供的,而是由基本SODBS庫來提供。這個基本庫是構(gòu)建一個復(fù)雜的應(yīng)用軟件的前提條件。更復(fù)雜的應(yīng)用需要比持久化存儲等層次更高的抽象。為了構(gòu)建這樣的應(yīng)用程序,我們需要一些現(xiàn)成的軟件實體來輔助我們編寫諸如客戶-服務(wù)器式(client-server)的程序。SODBS庫就給我們提供了用來構(gòu)建可容錯系統(tǒng)的一個完整的設(shè)計模式(我們稱之為behaviour)庫。此文檔中我會介紹behaviour庫的一個最小集,可以用它們來構(gòu)建可容錯的應(yīng)用軟件,它們是supervisor-—個監(jiān)督模型behaviour。sodbs_server——一種用于實現(xiàn)客戶_服務(wù)器式應(yīng)用程序的behaviour。sodbs_event——一種用于實現(xiàn)事件處理式應(yīng)用程序的behaviour。sodbs_fsm——一種用于實現(xiàn)有限狀態(tài)機的behaviour。這些庫程序當中,用于編寫可容錯應(yīng)用軟件的核心部件就是那個監(jiān)督模型。2.9相關(guān)的工作各個軟件部件不能很好地彼此隔離,是許多流行的編程語言不能夠用來構(gòu)建健壯的軟件的主要原因。安全性的本質(zhì),在于要能夠?qū)⒒ゲ恍湃蔚某绦蚋綦x起來,在于要保護基本平臺不受這些程序的破壞。隔離在面向?qū)ο笙到y(tǒng)中是相當困難的,因為對象很容易被別名化(aliased)。對象的別名化是很難纏的,而且在實際編程中不可能被檢測到,建議使用保護域(protectiondomains)(類似于操作系統(tǒng)的進程)來解決這一問題。在同一臺計算機上執(zhí)行多個用Java編寫的應(yīng)用程序的唯一安全的方式,是給每一個應(yīng)用程序開一個JVM,并且每一個JVM運行在一個單獨的OS進程中。這樣又會造成資源利用的效率方面的大大下降,會引起性能、伸縮性、程序啟動時間等方面的惡化。這樣一來,Java語言所提供的好處就只剩下可移植性和提升程序員的生產(chǎn)力了。這些固然重要,但是語言提供的所有潛在安全性并沒有完全被實現(xiàn)。事實是,在"語言安全性"與"真實安全性"之間存在著離奇的差異。把JVM變成一個類似于0S的執(zhí)行環(huán)境。尤其是現(xiàn)代0S所提供的進程抽象,也就是基于特性的角色模型;計算之間的相互隔離;資源的審計和控制以及資源的終止和回收。為了達到這一點任務(wù)不得直接共享對象,任務(wù)之間通信的唯一方式是使用標準的、拷貝式的通信機制與硬件系統(tǒng)一樣,軟件的容錯性關(guān)鍵在于把大的系統(tǒng)逐級分解成模塊,每一個模塊既是提供服務(wù)的最小單位,也是發(fā)生故障的最小單位,一個模塊的故障不會傳播到模塊之外。進程要想達到容錯性,就不能與其他進程有共享狀態(tài);它與其他進程的唯一聯(lián)系就是由內(nèi)核消息系統(tǒng)傳遞的消息。在RIG6的實現(xiàn)過程中的基本設(shè)計決策就是采用了一種沒有共享數(shù)據(jù)結(jié)構(gòu)的嚴格的消息規(guī)范。用戶與服務(wù)器之間所有的通信消息都是通過Al印h內(nèi)核來路由的。這種消息規(guī)范被證明是非常靈活、可靠的?!獣簳r撇開語言不管,讓我們想想一個單獨的進程應(yīng)該具備哪些性質(zhì)呢?—個硬件系統(tǒng)如果要適合在其上締造可容錯系統(tǒng)應(yīng)該具備3條性質(zhì)。這些性質(zhì)稱作1.故障即停(Haltonfailure)——當一個處理器出錯時,應(yīng)當立即停止下來,而不是繼續(xù)執(zhí)行可能不正確的操作。2.故障曝光性質(zhì)(Failurestatusproperty)——當一個處理器發(fā)生故障時,系統(tǒng)中的其他處理器應(yīng)該得到通知,故障的原因必須交代清楚。3.持久存儲性質(zhì)(Stablestorageproperty)——處理器的存儲器應(yīng)當分為持久存儲器(stablestorage,處理器崩掉時依然存在)和臨時存儲器(volatilestorage,處理器崩掉就沒了)。具備這些性質(zhì)的處理器稱為錯即停處理器(fail-stopprocessor)。其思想就是一旦錯誤發(fā)生,就沒有必要繼續(xù)運行了。出錯的處理應(yīng)該停下來,以免繼續(xù)執(zhí)行會引起更大的破壞。在一個錯即停處理器中,狀態(tài)存儲在臨時或持久處理器里。當處理器崩潰時,臨時存儲器中的所有數(shù)據(jù)將丟失掉,而持久存儲器中的所有數(shù)據(jù)在崩潰后仍然可以使用。用進程的方法達到故障隔離的思想提倡每個進程都是速錯的,要么它就正確地運行著,要么它就應(yīng)該檢測到錯誤,報告錯誤并停止運行。進程以防護性編程(defensiveprogramming)的方式達到"速錯"。它們對其所有的輸入?yún)?shù)、中間結(jié)果和數(shù)據(jù)結(jié)構(gòu)進行例行檢查。一旦檢測到錯誤,就立即報告該錯誤并停止運行。即速錯軟件具有很短的檢測潛伏期(detectionlatency)。這兩個思想本質(zhì)是一樣的;只不過一個說的是硬件,一個說的是軟件,但是其根本原則如出一轍。當進程發(fā)生不可校正的錯誤時應(yīng)該盡快停下來這一點非常重要—個軟件系統(tǒng)中的一個錯誤可能會引起一個或更多其他錯誤。從故障發(fā)生到其被檢測到的間隔時間——即潛伏時間——越長,代價就會越大,因為這樣會增加對故障進行回退分析的復(fù)雜性……為了有效地處理錯誤,我們應(yīng)該盡早地檢測到錯誤并停下來。綜合以上這些意見和我們的原始需求,我們規(guī)劃了本系統(tǒng)應(yīng)該具備如下一些性質(zhì)1.以進程作為錯誤封裝的單位——即一個進程中發(fā)生的錯誤不會影響到系統(tǒng)中其他的進程。我們稱這一性質(zhì)為強隔離(strongisolation)。2.進程要么就規(guī)規(guī)矩矩地運行,要么就痛痛快快地停掉。3.故障和故障原因應(yīng)該可以被其他進程檢測到。4.進程之間沒有共享狀態(tài),唯以消息傳遞的方式通信。要想一個編程語言或平臺具有如上這些性質(zhì),并可以用來構(gòu)建可容錯的軟件系統(tǒng),還需要具備一些必要的前提條件。我們將看到這些性質(zhì)是如何在SODBS及其編程庫中被滿足的。盡管編譯器檢查和由編程語言提供的異常處理確實有用,但是從歷史上看,人們似乎更偏向于用運行時檢查加進程的方式來達到故障封閉的目標。因為這種方式具有簡單性這一優(yōu)勢——一旦一個進程或它的處理器出錯,只管停下它!這種方式中進程就充當了一種干凈的模塊單位、服務(wù)單位、容錯單位、出錯單位的角色。故障被限制在速錯的軟件模塊之內(nèi)。進程因為與其他進程沒有任何共享狀態(tài)所以具有容錯性;進程與其他進程聯(lián)系的唯一方式就是通過內(nèi)核消息系統(tǒng)發(fā)送的消息。如果我們將這些觀點與我們現(xiàn)在的S0DBS系統(tǒng)比較我們會發(fā)現(xiàn)許多驚人的相似。當然也有些不同之處——在S0DBS中并不建議使用"防護性編程"的風格,因為編譯器增加了一些必要的檢測,使得這種編程風格并無必要。"事務(wù)機制"由sodbS_db數(shù)據(jù)庫來提供。而錯誤限制和處理則由S0DBS庫中的"監(jiān)督樹"behaviour來完成。"'速錯'模塊"的思想對應(yīng)于我們的編程指導方針,在我們的編程指導方針里,我們說進程應(yīng)該嚴格按照我們期望的方式運行,否則就應(yīng)該停掉。我們的系統(tǒng)中的監(jiān)督層次結(jié)構(gòu)對應(yīng)于模塊的層次結(jié)構(gòu)。應(yīng)當允許軟件部件崩掉然后重啟它,這樣會簡化故障模型,并有利于保證代碼的可靠性?,F(xiàn)代面向?qū)ο笙到y(tǒng)方面的工作也越來越認識到使軟件部件彼此隔離的重要性。3S0DBS這里簡單介紹一下SODBS設(shè)計思想。S0DBS的開發(fā)工具屬于面向消息的語言(message-orientedlanguage)—類——面向消息的語言都是通過并行進程的方式提供并發(fā)性的。在面向消息的語言中,沒有任何共享的對象,取而代之的是進程之間以收發(fā)消息來達到交互。3.l概覽S0DBS的世界觀可以歸納為如下的一些觀念—切皆進程。進程強隔離。進程的生成和銷毀都是輕量的操作。消息傳遞是進程交互的唯一方式。每個進程有其獨有的名字。你若知道進程的名字,你就可以給它發(fā)消息。進程之間不共享資源。錯誤處理非本地化。進程要么好好跑,要么迅速停止。把進程作為抽象的基本單位,緣于期望設(shè)計出一種適合編寫大型的可容錯的軟件系統(tǒng)的語言。編寫這類軟件要解決的一個基本問題就是要限制錯誤的傳播——進程的抽象正好提供了一種阻止錯誤傳播的抽象邊界。例如,Java就對于限制錯誤傳播無能為力的,所以Java不適合用來編寫"安全的"應(yīng)用程序。如果進程真正是隔離的(必須做到對錯誤傳播的限制),那么進程的其他性質(zhì)——例如只能以消息傳遞的方式進行交互——就順理成章地成為這種隔離性的結(jié)果。關(guān)于錯誤處理的觀點似乎并不明顯。當我們構(gòu)建一個可容錯系統(tǒng)時,我們需要至少兩臺物理上獨立的計算機。只用一臺計算機是不成的,一旦它崩潰了,就什么東西都沒有了。我們能夠想象的最簡單的可容錯系統(tǒng)也由兩臺計算機組成,如果一臺崩潰了,另外一臺就可以接過第一臺的所有工作。在這種最簡單的情形下,也要求故障恢復(fù)軟件做到非本地化;故障發(fā)生在第一臺計算機上,而由運行在第二臺計算機上的軟件來糾正該錯誤。S0DBS的世界觀就是"萬物皆進程",當我們把真實的計算機也模擬成進程時,我們就得到了錯誤處理應(yīng)該非本地化這一思想。其實,這是一個修正后的事實,遠程錯誤處理只有在本地嘗試修復(fù)錯誤失敗的情況下才會發(fā)生。如果有異常發(fā)生,一個本地進程應(yīng)該可以檢測到它并糾正它所造成的故障,在這種情況下對于系統(tǒng)中所有其他進程來說,根本感覺不到異常的發(fā)生。如果把SODBS看作是一種并發(fā)語言,它是非常簡單的。因為沒有共享數(shù)據(jù)結(jié)構(gòu),沒有監(jiān)視(monitor)或同步方法,所以需要學習的東西很少。語言的主體部分,或許也是最平淡無奇的部分,就是這種語言的順序化(sequential)子集。這個順序化子集可以用一種動態(tài)類型、嚴格的函數(shù)式編程來刻畫,而函數(shù)式編程是完全沒有副作用的。在這個順序化子集里,有少數(shù)操作是有一些副作用的,但是事實上這些操作不是必需的。3.2并發(fā)(concurrent)編程在SODBS中,可以通過調(diào)用spawn原語來創(chuàng)建并行進程,表達式如Pid=spawn(F)這里F是一個參數(shù)個數(shù)為0的函數(shù),該表達式創(chuàng)建了一個對F求值的并行進程。spawn返回一個進程標識符(Pid),通過Pid我們可以訪問該進程。語句"Pid!Msg"表示將一個消息Msg發(fā)送給進程Pid。消息可以用receive原語來接收,語法形式如下receiveMsgl[whenGuardl]_>Expr—seql;Msg2[whenGuard2]_>Expr—seq2;MsgN[whenGuardN]-Expr—seqN;endMsgl…MsgN都是模式,模式也可能帶有保護式。當向一個進程發(fā)送一個消息時,該消息就被放進一個屬于該進程的郵箱(mailbox)中。下次進程對receive語句進行求值的時候,系統(tǒng)就會查看一下郵箱,并且試圖拿郵箱中的第l條消息與當前receive語句中的所有模式進行匹配。如果郵箱中收到的消息沒有與任何模式匹配成功,則該消息就被轉(zhuǎn)移到一個臨時的"保管"隊列中,進程被掛起,等待下一條消息。如果消息匹配成功,且與之匹配的模式所帶的保護式也取真的話,該模式后面的語句系列就會依次被求值。同時,所有被臨時保管的消息也被放回到進程的郵箱中。receive語句可以有一個可選擇的超時值。如果在超時期限內(nèi)沒有收到可配的消息的話,超時條件下面的表達式就會被求值。3.4注冊進程名當我們想向一個進程發(fā)送消息的時候,我們需要知道該進程的名字。這是很安全的,但是當我們要向一個給定的進程發(fā)送消息時必須設(shè)法獲取該進程的名字,這一點某種程度上會帶來一些不便。如下表達式register(Name,Pid)會創(chuàng)建一個全局進程,并把原子Name與繼承標識符Pid關(guān)聯(lián)起來。這樣就可以通過調(diào)用"Name!Msg"來給進程Pid發(fā)送消息。3.5錯誤處理在SODBS里求取一個函數(shù)的值一定只有兩種結(jié)果要么函數(shù)就返回一個值,要么它就產(chǎn)生一個異常。異常可以隱式地產(chǎn)生(即由SODBS運行時系統(tǒng)產(chǎn)生),也可以通過調(diào)用exit(X)原語來顯式地產(chǎn)生。下面是一個隱式地產(chǎn)生異常的一個例子,假設(shè)我們寫一個函數(shù)如factorial(0)_>1;factorial(N)_>r#factorial(N_l).求值factorial(10)將返回一個值3628800,但是如果求取factorial(abc)的值,則將產(chǎn)生一個異常{'EXIT',仏adarith,…K異常會引起程序停下正在執(zhí)行的操作轉(zhuǎn)而去做其他的事情——這就是它們被稱作異常的原因。3.5.1異常異常是為S0DBS運行時系統(tǒng)所檢測到的一種非正常狀態(tài)。S0DBS程序是被編譯成虛擬機指令并且由一個虛擬機仿真器來執(zhí)行的。而虛擬機仿真器是S0DBS運行時系統(tǒng)的一部分。一旦仿真器檢測到某種不知所措的狀態(tài),它就會產(chǎn)生一個異常。一共有6種類型的異常1.值錯誤(valueerror)——就是諸如"被0除"之類的錯誤。這種情況下傳給函數(shù)的參數(shù)的類型是正確的,但是值錯了。2.類型錯誤(typeerror)——這類錯誤是指調(diào)用S0DBS的內(nèi)置函數(shù)的時候所填的參數(shù)類型不正確。例如,有一個內(nèi)置函數(shù)為atom—to—list(A),是將原子A轉(zhuǎn)換成其ASCI1碼的一個整數(shù)列表。如果變量A并不是一個原子,運行時系統(tǒng)就會產(chǎn)生一個異常。3.模式匹配錯誤(pattern-matchingerror)——這類錯誤是指試圖將一個數(shù)據(jù)結(jié)構(gòu)與一些模式進行匹配,卻找不到匹配成功的模式的錯誤。這種錯誤會在函數(shù)頭匹配時產(chǎn)生,或者在諸如case,receive或if語句中進行匹配時產(chǎn)生。4.顯式調(diào)用exit(explicitexits)——這類錯誤是在顯式調(diào)用表達式exit(Why)時產(chǎn)生的,該調(diào)用會產(chǎn)生一個Why異常。5.錯誤傳播(errorpropagation)——如果一個進程收到一個exit信號,它可以選擇停掉自己并把該exit信號傳播給所有它連接著的進程。6.系統(tǒng)異常(systemexc印tion)——運行時系統(tǒng)也許會因為內(nèi)存耗盡或檢測到一個內(nèi)部表不一致時終結(jié)掉一個進程。這類錯誤不在程序員的控制范圍之內(nèi)。3.5.2進程連接與監(jiān)視者—旦一個進程死掉,我們希望其他的進程得到通知?;叵胍幌挛覀冋f過我們需要這一點來編寫一個可容錯系統(tǒng)。有兩種方式可以做到這一點,我們可以用進程連接或進程監(jiān)視者。進程連接是將一組進程聚合在一起的一種方式,在進程連接中,任意一個進程中發(fā)生了錯誤,其他所有的進程都將連帶被停掉。進程監(jiān)視者是用一個單獨的進程來監(jiān)視系統(tǒng)中的所有其他的進程。進程連接catch原語用于截獲一個進程中發(fā)生的錯誤。那我們現(xiàn)在來問一問,如果程序的頂層catch都不設(shè)法修正一個它所檢測到的錯誤的話,會發(fā)生什么事情呢?答案是該進程將終止。出錯的原因只是異常的一個參數(shù)。當一個進程出錯時,出錯的原因?qū)⒈粡V播給它所歸屬的一個所謂"連接集"(linkset)的所有其他進程。進程A可以通過調(diào)用內(nèi)置函數(shù)link(B)將B加入到它的連接集中。進程之間的連接是對稱的,也就是說,如果A連接到了B,那么B也連接到了A。連接也可以在進程被創(chuàng)建的時候創(chuàng)建。如果A通過下面的調(diào)用方式來創(chuàng)建進程B:B=spawn_link(f皿()_>...end),那么進程B在創(chuàng)建的時候就連接到了進程A。這種調(diào)用方法在語義上等價于先調(diào)用spawn緊接著調(diào)用link,只不過這兩個表達式是一起執(zhí)行的,不是分步的。spawn—link原語的引入,是為了規(guī)避進程在創(chuàng)建的過程中還沒有來得及執(zhí)行l(wèi)ink語句就死掉這種罕見的編程錯誤9。如果進程P死掉的時候產(chǎn)生了一個{'EXIT',Why}的未捕獲異常,那么退出信號{'EXIT',P,Why}就會被發(fā)送給進程P的連接集中的所有進程。我剛剛提到"信號"。信號是進程終止的時候在進程之間傳遞的一種東西。信號是一個{'EXIT',P,Why}形式的元組,這里P是終止的進程的Pid,而Why是一個描述終止原因的項式。任何收到Why不為normal(正常)的退出信號的進程都將死掉。對于這一規(guī)則有一個例外如果接收進程是一個系統(tǒng)進程,那么該進程不會死掉,而是將退出信號轉(zhuǎn)換成一個正常的進程間消息,并被添加到該進程的郵箱中??梢哉{(diào)用內(nèi)置函數(shù)process—flag(tr即—exit,true)來將一個一般進程變成一個系統(tǒng)進程。系統(tǒng)進程處理其他進程的故障的典型代碼片斷如下start()_>spawn(fimgo/0).go()_>process_flag(tr即—exit,true),loop().20loop()-〉receive{,EXIT,,P,Why卜〉...handletheerror...end另外一個原語exit/2將完成這個拼圖。exit(Pid,Why)將給進程Pid發(fā)送一個原因為Why的退出信號。調(diào)用exit/2的進程本身不會終止,因此這種消息能夠用來"偽裝"一個進程的死亡。不過對于"系統(tǒng)進程將會把所有信號都轉(zhuǎn)換成消息"這一點來說,也存在一個例外如果調(diào)用exit(P,kill),將向P發(fā)送一個不可阻擋的退出信號(unsto卯a(chǎn)bleexit),收到該信號后進程P將不顧一切后果地終結(jié)掉。exit/2的這種用法在客客氣氣地請求一個進程自覺終結(jié)而遭到拒絕的時候就有用。進程連接對于建立進程群組(group)是有用的,進程群組中的一個進程出錯,所有進程都將死掉。通常我們把屬于一個應(yīng)用的進程連接起來,并且讓其中的一個進程充當"監(jiān)視者"的角色。監(jiān)視者被設(shè)定來捕獲退出信號。如果進程群組中有任何一個進程出錯了,群組中除了監(jiān)視者以外的其它所有進程都將死掉,而由監(jiān)視者來接收群組中的進程的出錯消息,這些出錯消息描述了故障原因。進程監(jiān)視者進程連接對于整個進程群組來說是有用的,但是對于非對稱的進程對的監(jiān)視來說沒什么用。在典型的客戶-服務(wù)器模型中,客戶與服務(wù)器的關(guān)系在考慮到錯誤處理的時候就是非對稱的。假設(shè)一個服務(wù)器處理著大量不同客戶的大量長時間會話(long-livedsession),那么當服務(wù)器崩掉的時候我們可能會殺死所有的客戶,但是當某一個客戶崩掉的時候我們并不希望殺掉服務(wù)器。SODBS:monitor/2原語就是用來設(shè)置一個監(jiān)視者的。如果進程A有求值Ref=SODBS:monitor(process,B)那么當B因為原因Why死掉的時候,就會向A發(fā)送一條如下格式的消息{,D0WN,,Ref,process,B,Why}監(jiān)視消息的發(fā)送者A和接收者B都不必是系統(tǒng)進程。[O306]3.6分布式(distributed)處理SODBS程序能夠很輕易地從一個單處理器平臺移植到多處理器平臺。每一個完整的自包含的(self-contained)SODBS系統(tǒng)被稱為一個節(jié)點(node)。一個宿主操作系統(tǒng)上面可以跑一個或多個SODBS節(jié)點。多個SODBS節(jié)點可以運行在同一個操作系統(tǒng)上這一點簡化了分布式應(yīng)用的測試??梢酝ㄟ^讓所有的節(jié)點運行在同一個處理器上,來進行一個分布式應(yīng)用程序的開發(fā)和測試。當應(yīng)用投入使用時,可以將在同一處理器上工作的不同節(jié)點變成分布式網(wǎng)絡(luò)處理器上的不同節(jié)點。除了定時操作(timing)以外,所有操作的工作方式都應(yīng)該與在同一個節(jié)點嚴格相同。分布式處理需要如下兩個原語spawn(Node,Fun)——在一個遠端節(jié)點Node上產(chǎn)生一個處理函數(shù)是Fun的進程。monitor(Node)——用來監(jiān)視整個節(jié)點的行為。這里的monitor類似于link,不同之處在于被控制的對象是一整個節(jié)點而不是某個進程的行為。3.7端口(ports)端口給SODBS程序與外界的通信提供了一種機制。端口可以通過調(diào)用內(nèi)置函數(shù)open_p0rt/2來創(chuàng)建。每個端口都有一個與之相關(guān)聯(lián)的"控制進程"(controlling-process)。我們稱控制進程擁有(own)該端口。從該端口收到的所有的消息都被發(fā)送給其控制進程,且只有其控制進程才可以向該端口發(fā)送消息。端口的控制進程被初始化為創(chuàng)建該端口的進程,但是這個進程可以被改變。如果P是一個端口,而Con是其控制進程的pid,那么可用調(diào)用如下的表達式來讓端口做某些事小蟲l冃P!{Con,Command}這里的Command變量可以取如下三種可能的值{command,Data}——把數(shù)據(jù)Data通過端口發(fā)送給外部對象。Data必須要是一個io表。io表是扁平化的,表中所有的數(shù)據(jù)元素都被發(fā)送給外部的應(yīng)用程序。close——關(guān)閉一個端口。被關(guān)閉的端口必須向控制進程回復(fù)一個{P,closed}的消息。{ccmnect,Pidl}——將端口的控制進程變?yōu)镻idl。該端口必須要給原來的控制進程回應(yīng)一個{Port,connected}的消息,此后該端口收到的所有新消息都經(jīng)發(fā)送給新的控制進程。通過端口收到的所有外部應(yīng)用程序的數(shù)據(jù)都將以{Port,{data,D}}的消息格式發(fā)送給其控制進程。消息的確切格式以及該消息是如何組幀的,則取決于端口是如何被創(chuàng)建的。3.8動態(tài)代碼替換SODBS支持一種簡單的動態(tài)代碼替換機制。在一個運行時的SODBS節(jié)點上,所有的進程都共享同一份代碼。因此我們必須要考慮如果我們替換了一個運行時系統(tǒng)的代碼,會發(fā)生什么事情?在順序化編程語言里只有一個控制線(threadofcontrol),所以如果我們期望動態(tài)替換代碼,我們只需要考慮對該唯一控制線的影響。在一個順序化系統(tǒng)里,如果我們期望改變代碼,我們實際上通常的做法是停止該系統(tǒng),替換代碼,然后重新啟動程序。然而在一個實時控制系統(tǒng)中,我們通常并不希望停下該系統(tǒng)來替換代碼。在某些特定的實時控制系統(tǒng)中,我們也決不允許關(guān)掉系統(tǒng)來替換代碼,所以這些系統(tǒng)需要被設(shè)計成不停止系統(tǒng)而支持代碼替換。這種系統(tǒng)的一個例子就是NASA設(shè)計的X2000衛(wèi)星控制系統(tǒng)。SODBS系統(tǒng)的每個模塊的代碼允許存在兩個版本。如果一個模塊的代碼被加載進來,那么調(diào)用該模塊代碼的所有新啟動的進程就會動態(tài)地連接到該模塊的最新版本上。如果一個模塊后來被替換了,那么原來執(zhí)行該模塊代碼的進程就既可以選擇繼續(xù)執(zhí)行老的代碼,也可以選擇執(zhí)行新加載的代碼。這種選擇決定于該代碼是如何被調(diào)用的。如果代碼是通過全修飾名被調(diào)用的,即以"ModuleName:FuncName"的方式調(diào)用的,那么就總是調(diào)用該模塊的最新版本,否則就調(diào)用該模塊的當前版本。舉個例子,假設(shè)我們寫了下面的一個服務(wù)循環(huán)-module(m)....loop(Data,F)_>receive{From,Q}_>(R印ly,Datal}=F(Q,Data),m:loop(datal,F(xiàn))end.在模塊m第一次被調(diào)用到的時候,該模塊就被加載了進來,譬如從外部調(diào)用m:loop函數(shù)的時候。因為這時候m模塊只有一個版本,所以調(diào)用的是當前模塊的loop函數(shù)。假設(shè)我們現(xiàn)在修改了模塊m的代碼,重新編譯并加載了該模塊。那么當我們在最后的receive語句中調(diào)用m:loop函數(shù)時,新版本的m模塊中的代碼就會被調(diào)用。注意,所調(diào)用的新代碼與老代碼的兼容性由程序員來保障。強烈建議把所有的代碼替換調(diào)用都做成尾調(diào)用的,這樣的話一個尾調(diào)用就不必返回到老代碼中,因此在一個尾調(diào)用以后,一個模塊的所有的老代碼就可以被安全地刪除了。如果我們希望繼續(xù)執(zhí)行當前模塊(老版本)的代碼,而不切換到新模塊的代碼中,那么我們就可以用非全修飾名調(diào)用的方式寫該loop循環(huán),艮卩-module(m)....loop(Data,F)_>receive{From,Q}->(R印ly,Datal}=F(Q,Data),loop(datal,F(xiàn))end.在這種情況下,模塊的新版本的代碼就不會被調(diào)用。靈活地運用這種機制使得進程可以同時執(zhí)行不同模塊的新、老代碼版本。需要注意,代碼存在兩個版本有一個局限性。如果第三次試著重新載入一個模塊,則正在執(zhí)行第一個模塊的所有進程將被全部殺掉。除了以上調(diào)用約定以外,還有許多內(nèi)置函數(shù)用來達到代碼替換的目的。3.9—種類型符號(typenotation)我們在構(gòu)建一個軟件模塊的時候,是怎么描述該模塊的用法的呢?通常,我們都會說通過對一組API(ApplicationProgrammingInterface)的調(diào)用來使用它。這組API就是模塊提供可供外部調(diào)用的一組函數(shù),以及這些函數(shù)的輸入值的類型的要求和返回值的類型的描述。下面的例子說明了如何用S0DBS的類型符號來指定一些函數(shù)的類型+typefile:open(fileName(),read|write)_>{ok,fileHandle0)I{error,stringO).+typefile:read_line(fileHandle())->{ok,string()}Ieof+typefile:close(fileHandle())_>true.+deftypefileName()=[int()]+deftypestring()=[int()].+deftypefileHandle()=pid().每一種SODBS的原始數(shù)據(jù)類型都有它的類型。這些原始類型是int()——是整數(shù)類型。atom()——是原子類型。pid()——是Pid類型。ref()——是引用類型。float()-是SODBS的浮點數(shù)類型。port()——是端口類型。bin()——是二進制類型。列表類型、元組類型以及選擇(alternation)類型是如下遞歸式地定義的如果T1,T2,…,Tn都是類型的話,那么{Tl,T2,,Tn}就是元組類型(tupletype)。此時如果{X1,X2,…,Xn}中的XI是Tl類型,X2是T2類型,……Xn是Tn類型,我們就說{Xl,X2,,Xn}是{Tl,T2,,Tn}類型。如果T是一個類型,那么[T]就是一個列表類型(listtype)。如果[X1,X2,,Xn]中的所有Xi都是T類型的話,那么我們就說[X1,X2,…,Xn]是[T]類型。注意,空表[]的類型也是[T],其中T是任意類型。如果Tl和T2都是類型,則Tl|T2就是選擇類型(alternationtype)。如果X的類型可能是Tl或者T2,我們就說X的類型是TlIT2??梢酝ㄟ^如下的符號來引入新的類型+deftypenamel()=name2()=...=Type.這里namel、name2......等名字應(yīng)遵循SODBS的原子(atom)的語法。Type是類型變量,需按照SODBS的變量的語法來書寫。例如我們可以定義+deftypebool()=true|false.+deftypeweekday()=monday|tuesday|Wednesday|thursday|fri(ky.+deftypeweekend()=Saturday()|sunday().+deftypeday()=weekday()Iweekend().函數(shù)類型按如下書寫+typef皿ctionName(Tl,T2,".,Tn)_>T.這里所有的Ti都是類型。如果在一個類型的定義中某個類型變量出現(xiàn)了不止一次,那么該類型的實例中與其定義對應(yīng)的位置的所有變量都必須具有相同的類型。下面是一些例子+deftypestring()=[int()].+deftypeday()=number()=int().+deftypetown()=street()=string().+typefactorial(int())_>int().+typeday2int(day())_>int()+typeaddress(person())_>{town(),street(),number()}.最后,還有匿名函數(shù)的類型如下書寫+typef皿(Tl,T2,…,Tn)_>Tend因此,map/2的類型就應(yīng)該如下書寫+typemap(fun(X)->Yend,[幻)-〉[Y].這里的類型符號是Wadler&Marlow[49]所開發(fā)的類型符號的一種極其簡化的版本。3.10討論這里介紹了SODBS很重要的一個子集,至少足以用來理解此文檔中的所有例子。但是我還沒有回答"S0DBS是可容錯系統(tǒng)嗎?"我們確信答案是"正是。"我們此前曾經(jīng)說過,可容錯系統(tǒng)的軟件系統(tǒng)一定要滿足某些特征。我現(xiàn)在就來印證一下,S0DBS確實是滿足了這些特征的,理由如下進程是S0DBS的基礎(chǔ),所以Rl滿足。因為S0DBS中的進程就是錯誤封裝單元,所以R2滿足。如果一個進程因為軟件原因終止的話,同一個SODBS節(jié)點中的其它進程將不會受到影響(當然,除非有進程被連接到了將會終止的進程上,這種情況下進程間的影響是有意的)。如果進程中的函數(shù)用了錯誤的參數(shù)來調(diào)用,或者系統(tǒng)的BIF用了錯誤的參數(shù)來調(diào)用,那么該進程就立即終止。即刻終止符合速錯進程(fail-fastprocess)的概念,也符合Schneider的錯即停處理器(fail-stopprocessor)的概念,還符合Renzel關(guān)于我們必須檢測錯誤,并盡量早地停下來的觀點。當一個進程出錯時,出錯的原因會被廣播給該進程的當前連接集,因此滿足R3和R4。R5由一種代碼升級機制來滿足。R6在S0DBS里沒有被滿足,但是在S0DBS的庫里得到了滿足。持久存儲可以用dets或sodbs—db來實現(xiàn)。dets是一個單機的基于磁盤的存儲系統(tǒng)。如果一個進程或者一個節(jié)點崩潰了,存儲在dets中的數(shù)據(jù)卻得以幸存。為了達到更好地保護數(shù)據(jù)的目的,數(shù)據(jù)應(yīng)該被存儲在物理上獨立的兩個節(jié)點上,這時候可以用sodbS_db數(shù)據(jù)庫,它是S0DBS的一個應(yīng)用程序。我還要指出,Schneider的"出錯即停止"(haltonfailure)、"錯誤狀態(tài)屬性,,(Failurestatusproperty)、"禾急定存儲屬性,,(Stablestorageproperty)等觀點,也由S0DBS自己或S0DBS的庫直接或間接地滿足了。4實現(xiàn)技術(shù)抽象出并發(fā)——某種意義上講,并發(fā)程序比順序化程序要難得多。為了避免在同一個模塊里既有并發(fā)的代碼又有順序化的代碼,我們展示了如何將代碼組織到兩個模塊里,其中一個全部是并發(fā)代碼,另一個則只有純的順序化代碼。抱持S0DBS的世界觀——在SODBS的世界里,萬事萬物都是進程。為了幫助我們抱持這種觀點,我介紹了一種協(xié)議轉(zhuǎn)換器(protocolconverter)的思想,它有助于程序員建立任何事物都是SODBS進程這一觀念。SODBS的錯誤觀——SODBS的錯誤處理方式與其他語言有本質(zhì)的區(qū)別。我將展示在SODBS中該如何編寫出錯情況下的程序。顯意編程——這是一種程序員能夠輕易就從源代碼中看出編程者的意圖的編程風格,而不是通過對代碼進行表面的分析來猜測編程者的意圖。4.1.1—個可容錯的客戶_服務(wù)器模型我現(xiàn)在來擴展我們的服務(wù)器程序,增加錯誤恢復(fù)的代碼,一旦函數(shù)F/2發(fā)生錯誤,原先的服務(wù)器程序就會崩潰掉。"容錯"一詞通常是說硬件的,但是在這里我們的意思是包容用以參數(shù)化服務(wù)器的函數(shù)F/2中的錯誤。函數(shù)F/2在一個catch語句內(nèi)進行求值,如果一個RPC請求會導致服務(wù)器崩潰,就將發(fā)起該RPC的客戶殺死掉。比較一下新的服務(wù)器代碼,我們發(fā)現(xiàn)跟老的代碼相比有兩點小小的變化rpc代碼被改成了rpc(Name,Query)_>Name!{self(),Query},receive{Name,crash}->exit(rpc);{Name,ok,R印ly)-〉R印lyend.并且loop/3內(nèi)receive語句的一段改成了case(catchF(Query,State))of{,EXIT,,Why}_>log—error(Name,Query,Why),F(xiàn)rom!{Name,crash},loop(Name,F,State);(R印ly,Statel}_>From!{Name,ok,Reply},loop(Name,F,Statel)end讓我們再仔細看看這些變化的細節(jié),我們會發(fā)現(xiàn),如果在服務(wù)器的loop函數(shù)里對F/2的求值發(fā)生了異常,則會發(fā)生三件事1.會報告該異常——在我們的程序中,我們只是將該異常打印了出來,但要是在更成熟的系統(tǒng)中,我們會將該異常記錄到一個穩(wěn)定存儲器中。2.向客戶發(fā)送一個crash消息——當客戶收到該crash消息時,會在客戶代碼中產(chǎn)生一個異常。因為這時候客戶程序再運行下去很可能已經(jīng)沒有意義了,所以這正是期望的結(jié)果。3.服務(wù)器繼續(xù)對老的狀態(tài)變量進行操作。因此說RPC遵循了"事務(wù)語義"(transactionsemantics),也就是說,它要么操作完全成功,服務(wù)器的狀態(tài)被更新,要么操作失敗,服務(wù)器的狀態(tài)保持原樣不動。注意server2.sodbs只能保護發(fā)生在將服務(wù)器參數(shù)化的特征函數(shù)中的錯誤。如果服務(wù)器本身死掉了(這是可能的,例如被系統(tǒng)中的其他進程故意殺死),那么客戶的RPC樁就被無限掛起了,一直等待著一個永遠不會到來的回應(yīng)消息。如果我們還想保護這種可能性,那么我們可以像這樣來寫RPC函數(shù)rpc(Name,Query)_>Name!{self(),Query},receive{Name,crash}_>exit(rpc);{Name,ok,R印ly卜〉R印lyafter10000—〉exit(timeout)end.這種解決方法解決了一個問題,但是卻帶來了另一個問題我們應(yīng)該把超時設(shè)置多長時間?一個更好的解決辦法是用到監(jiān)督樹,這個我在這里不展開講。服務(wù)器發(fā)生了故障,不應(yīng)該由客戶軟件來檢測它,而應(yīng)該由專門負責糾正服務(wù)器故障的特殊的監(jiān)督者進程來檢測它?,F(xiàn)在,我們可以來運行這個用含有故意錯誤的VSHLR版本(vshlr2)作為參數(shù)的服務(wù)器程序。該程序的一個執(zhí)行片斷如下所示〉vshlr2:start().true2>vshlr2:find(〃joe")error3>vshlr2:i_am_at(〃joe",〃sics〃).ok4>vshlr2:find(〃joe"){ok,〃sics"}5>vshlr2:find(〃robert〃).Servervshlrquery{find,〃robert"}causedexception(badarith,[(vshlr2,handle—event,2}]}氺氺exited:rpc氺氺6〉vshlr2:find(〃joe").{ok,〃sics"}異常中的信息足以用來幫助我們調(diào)試程序。我們最后對服務(wù)器程序所做的改進就是讓我們可以"在系統(tǒng)運行中"(on-the-fly)對服務(wù)器的程序進行修改。我可以對程序用vshlr3進行參數(shù)化,vshlr3沒有在這里貼出來,它跟vshlr2基本上是一樣的,只有一點不同第3行的server2改成server3。下面的執(zhí)行片斷展示了如何在"在系統(tǒng)運行中"修改服務(wù)器程序的代碼。第1-3行顯示服務(wù)器程序工作正常,server3可以處理1除以0這種錯誤而不會崩潰,例如第5行顯示運行正常。第6行,我們發(fā)送一條命令,將服務(wù)器程序的代碼改回到vshlrl中的版本。這條命令執(zhí)行完后,服務(wù)器程序會如第7行所示地正常工作。1〉vshlr3:start().true2>vshlr3:i_am_at(〃joe",〃sics〃).ok3>vshlr3:i_am_at("robert",〃FMV〃).ok4>vshlr3:find(〃robert").Servervshlrquery{find,〃robert"}causedexception(badarith,[(vshlr3,handle—event,2}]}氺氺exited:rpc氺氺5〉vshlr3:find(〃joe"){ok,〃sics"}6>server3:swap—code(vshlr,f皿(I,J)_>vshlrl:handle—event(I,J)end).ok7>vshlr3:find("robert").{ok,〃FMV"}編寫vshlr3的程序員完全不必知道server3的任何實現(xiàn)細節(jié),也不必知道服務(wù)器代碼可以在服務(wù)不停止的情況下被動態(tài)修改。在不停止服務(wù)器的情況下替換服務(wù)器程序代碼的能力部分滿足了第2.2節(jié)中的——即不停止系統(tǒng)而升級系統(tǒng)的軟件。"需求8程序。如果我們回顧一下server2的代碼和應(yīng)用程序中的vshlr2的代碼,我們會發(fā)現(xiàn)1.服務(wù)器程序中的代碼可以重復(fù)用來構(gòu)建許多不同的客戶-服務(wù)器模型的應(yīng)用2.應(yīng)用程序的代碼比服務(wù)器程序的代碼要簡單很多。3.要理解服務(wù)器程序的代碼,程序員就必須要理解S0DBS并發(fā)模型的所有細節(jié)。這就涉及到名字注冊、進程產(chǎn)生、向進程發(fā)送不可捕獲exit異常、發(fā)送和接收消息。對上報異常來說,程序員還必須理解異常的概念,對SODBS的異常處理機制相當熟悉。4.要編寫應(yīng)用程序的代碼,程序員就只需要理解一份簡單的順序化程序——他們不需要了解關(guān)于并發(fā)和錯誤處理的任何事情。5.我們可以想象,同一份應(yīng)用程序的代碼可以與越來越成熟的一系列服務(wù)器程序配合運行。我已經(jīng)展示過三個版本的服務(wù)器程序,并且我們還可以往服務(wù)器程序中添加越來越多的功能,而保持服務(wù)器程序/應(yīng)用程序(server/即plication)的接口不變。6.不同的服務(wù)器程序(server1、server2等等)滲透給應(yīng)用程序的是不同的非功能特性(non-functionalcharacteristics)。而所有服務(wù)器程序的功能特性(f皿ctionalcharacteristics)都是一樣的(即,輸入正確的參數(shù)程序最終產(chǎn)生的都是同一個結(jié)果);但是非功能特性卻不一樣。7.實現(xiàn)系統(tǒng)的非功能性需求(我們所說的非功能性需求是指在系統(tǒng)出現(xiàn)故障的時候系統(tǒng)的行為,函數(shù)求值需要多長時間等等)的部分代碼被限制在服務(wù)器程序之內(nèi),對編寫應(yīng)用程序的程序員來說是不可見的。8.遠程過程調(diào)用(remoteprocedurecall)如何實現(xiàn)的細節(jié)被隱藏在服務(wù)器程序模塊內(nèi)。這就意味著對于要使今后服務(wù)器程序的修改而不會影響到客戶程序,這一點是必須的。例如,我們可以修改rpc/2的實現(xiàn)細節(jié),而不必修改調(diào)用server2中的函數(shù)的客戶程序。將整個服務(wù)器的功能劃分成一個非功能性部件(none-functionalpart)和一個功能性部件(functionalpart)是一種好的編程實踐,可以給系統(tǒng)帶來許多可觀的好處,就如1.并發(fā)編程通常認為是比較難的。在一個大型的編程團隊中,程序員的技能層次往往不同,那么專家程序員應(yīng)該編寫專用服務(wù)器部分代碼,而經(jīng)驗尚淺的程序員應(yīng)該去編寫應(yīng)用部分代碼。2.形式化方法(formalmethod)可以應(yīng)用于(簡單一些的)應(yīng)用部分代碼之上。在對SODBS代碼進行形式化證明(formalverification)的時候,或設(shè)計類型系統(tǒng)進行類型推斷的時候,一遇到并發(fā)編程往往就會有問題。如果假設(shè)專用服務(wù)器程序已經(jīng)正確無誤這一假設(shè)成立,那么證明系統(tǒng)的性質(zhì)的問題就簡化為證明順序化程序的性質(zhì)的問題。3.在一個充滿大量客戶-服務(wù)器的系統(tǒng)中,所有的服務(wù)器程序就可以利用同一份專用服務(wù)器程序來編寫。這就使得程序員理解和維護起許多服務(wù)器程序來更簡單。4.專用服務(wù)器程序和應(yīng)用部分程序可以分別獨立進行測試。如果長時期內(nèi)接口保持恒定不變,那么這兩者可以獨立進行改進。5.應(yīng)用部分代碼能夠"插入到"許多不同的專用服務(wù)器程序中,不同的專用服務(wù)器具有不同的非功能特性。在具有相同的接口的情況下,有的服務(wù)器可以提供加強的調(diào)試環(huán)境,而有的服務(wù)器可以提供集群化、熱切換等特性。這一點已經(jīng)在很多項目中實行過,例如Eddie服務(wù)器程序提供了集群化能力,Blue-tail郵件加固器提供了一個具有熱切換功能的服務(wù)器。4.2抱持S0DBS的世界觀S0DBS的世界觀就是一切皆進程,進程之間只能通過交換消息來進行交互。當我們的SODBS程序需要跟外界軟件交互時,一般都是寫一個接口程序來完成交互,這個接口程序體現(xiàn)出"一切皆進程"的精神,而且很便利。舉個例子我們考慮一下如何實現(xiàn)一個電子支付服務(wù)器的web服務(wù)端?電子支付服務(wù)器是通過RFC2616建議中定義的HTTP協(xié)議與客戶通信的。從一個S0DBS程序員的角度來看,電子支付服務(wù)器內(nèi)部的循環(huán)會給每一個連接產(chǎn)生一個進程,接受來自客戶的請求,并作出適當?shù)捻憫?yīng)。程序代碼可能如下serve(Client)_>receive{Client,Request}_>Response=generate—response(Request)Client!{self(),Response}end.這里Request和Response是SODBS的項式(term),表示HTTP協(xié)議的請求和HTTP協(xié)議的響應(yīng)。上面的服務(wù)器程序非常簡單,它期望來一個單獨的請求,作出一個單獨的響應(yīng),然后就終止了連接?!獋€更成熟的服務(wù)器程序,還要支持HTTP/1.1規(guī)定的持久連接,支持這種持久連接的代碼也是非常簡單的serve(Client)_>receive{Client,close}—>true;{Client,Request}_>Response=generate—response(Request)Client!{self(),Response},server(Client);after10000—〉Client!{self(),close}end.這個11行的函數(shù)就從本質(zhì)上完成了一個簡單的支持持續(xù)連接的web服務(wù)端的功能。web服務(wù)端并不直接跟產(chǎn)生HTTP請求的客戶交互,因為那樣的話一些無關(guān)的細節(jié)將會嚴重地干擾web-server的實現(xiàn),并且使得程序結(jié)構(gòu)難以理解。這里我們用了一個"中間人"進程。"中間人"進程(一個HTTP驅(qū)動器)完成HTTP請求、應(yīng)答和表示這些請求、應(yīng)答的對應(yīng)SODBS項式之間的互換。HTTP驅(qū)動器程序的全部代碼如下relay(Socket,Server,State)_>receive{tcp,Socket,Data}_>caseparse—request(State,Data)of{completed,Request,Statel}_>Server!(self(),{request,Request}},relay(Socket,Server,Statel);{more,Statel}_>relay(Socket,Server,Statel)end;{tcp_closed,Socket}_>Server!{self(),close};30{Server,close}_>sodbs_tcp:close(Socket);{Server,Response}_>Data=format—response(Response),sodbstcp:send(Socket,Data),relay(Socket,Server,State);{'EXIT',Server,_}->sodbs_tcp:close(Socket)end.如果通過一個TCPsocket從客戶收到一個包,這個包就通過調(diào)用parse_request/2進行解析。當響應(yīng)已經(jīng)完成,一個表示該請求的SODBS項式就被發(fā)送給服務(wù)器。如果收到一個服務(wù)器的響應(yīng),則該響應(yīng)被轉(zhuǎn)換格式(reformat)并被發(fā)送給客戶。如果有任何一端終止連接,或者服務(wù)器發(fā)生一個錯誤,這個連接就會被關(guān)掉。如果該進程因任何原因終止,則所有的連接也會被自動關(guān)掉。變量State是一個狀態(tài)變量,用來表示可重入解析器的狀態(tài),該解析器解析收到的HTTP請求的。4.3錯誤處理哲學SODBS的錯誤處理與其他大多數(shù)編程語言中的錯誤處理有著根本的不同。SODBS關(guān)于錯誤處理的哲學可以用如下幾條標語來表達讓其它進程來修復(fù)錯誤。工作者不成功,便成仁。任它崩潰。杜絕防御式編程。4.3.1讓其它進程來修復(fù)錯誤在分布式系統(tǒng)中,我們?nèi)绾蝸硖幚礤e誤呢?為了處理硬件錯誤,我們需要備份;而為了處理整臺計算機的錯誤,我們需要兩臺計算機。如果計算機1發(fā)生故障,那么計算機2會發(fā)現(xiàn)故障并改正錯誤如果第1臺計算機崩潰了,第2臺計算機會檢測到該故障,并試圖修復(fù)該故障引起的錯誤。在SODBS中,我們就是使用的這種辦法,只不過我們把計算機與進程等價起來。如果進程1發(fā)生故障,那么進程2會發(fā)現(xiàn)故障并改正錯誤如果Pidl出錯且Pidl和Pid2是連接在一起的,且Pid2被設(shè)置為捕獲(tr即)錯誤,那么當Pidl出錯時,一條{'EXIT',Pidl,Why}格式的消息就被發(fā)送給Pid2。Why描述了出錯的原因。注意,如果運行Pidl的計算機死掉了,也會有一條退出消息{'EXIT',Pidl,machine—died}發(fā)送給Pid2。該消息貌似是來自Pidl,但是實際上來自運行著Pid2的節(jié)點的實時系統(tǒng)。非要使一個硬件錯誤看起來像一個軟件錯誤的原因是,我們不想用兩種方法來處理錯誤,一種處理軟件錯誤而另一種處理硬件錯誤。為了概念上的完整性,我們期望用統(tǒng)一的機制。再綜合考慮硬件錯誤的極端情形——即整個處理器發(fā)生故障,就產(chǎn)生了我們的錯誤處理思想即不在出錯的地方,而在系統(tǒng)的其他地方來進行處理。因此在任何情況下,包括硬件發(fā)生故障,都是由Pid2來糾正錯誤。這就是為什么我說"讓其他進程修復(fù)錯誤"。這種哲學與順序化編程語言是完全不同的,在順序化編程語言中,除了試圖在發(fā)生錯誤的控制線程中處理所有的錯誤,沒有其他選擇。在提供有異常處理的順序化編程語言中,程序員將任何可能發(fā)生故障的代碼用一個異常處理結(jié)構(gòu)包含起來,試圖包住該結(jié)構(gòu)中所有可能發(fā)生的錯誤。遠程錯誤處理有許多好處1.錯誤處理代碼和出錯代碼運行在不同的控制線程中。2.解決問題的代碼不會被處理異常的代碼擾亂。3.該方法可以用于分布式系統(tǒng),所以一個單節(jié)點系統(tǒng)的代碼移植到一個分布式系統(tǒng)中只需對錯誤處理代碼做很少的修改。4.系統(tǒng)可以在單一節(jié)點系統(tǒng)上構(gòu)建與測試,然后無需進行大的修改就可以部署到多節(jié)點系統(tǒng)上。4.3.2工作者與監(jiān)督者為了將執(zhí)行正常工作的進程與處理錯誤的進程更清楚地區(qū)別開來,我們經(jīng)常會談至lj王作者(worker)禾口監(jiān)督者(supervisor)?!獋€進程,即工作者進程,負責執(zhí)行正常的工作。另一個進程,即監(jiān)督者進程,來檢測工作者。如果工作者中發(fā)生了一個錯誤,監(jiān)督者會采取措施來糾正該錯誤。這種方式的妙處在于1.職責劃分很清晰。負責做事的進程(工作者)不用擔心錯誤處理。2.我們可以用特別的進程專門來負責錯誤處理。3.我們可以在物理上獨立的計算機上運行工作者進程和監(jiān)督者進程。4.往往會發(fā)現(xiàn)錯誤糾正代碼是有通用性的(generic),即對許多應(yīng)用程序都普遍適用,而工作者進程則更多因應(yīng)用而異。第三點是至關(guān)重要的——使得SODBS滿足了R3和R4,從而可以把工作者和監(jiān)督者運行在物理上獨立的計算機上,因此可以構(gòu)建可以包容引起所有進程出錯的硬件錯誤的系統(tǒng)。4.4任它崩潰我們的錯誤處理哲學如何適用于我們的編程實際呢?當程序員發(fā)現(xiàn)一個錯誤的時候,他該編寫什么代碼?我們的哲學是讓其它進程來修復(fù)錯誤,但是這對編碼者來說,意味著什么?答案是任它崩潰。我的意思是,當發(fā)生一個錯誤的時候,就讓程序崩潰好了。什么算是錯誤?就編程而言,我所說的錯誤即那些運行時系統(tǒng)也不知道該如何處理的異常。那些程序員也不知道如何處理的錯誤。如果一個異常是由運行時系統(tǒng)產(chǎn)生的,但是程序員之前就預(yù)見到了該異常,并知道如何糾正引起異常的條件,那么這就不是一個錯誤。例如,打開一個不存在的文件會產(chǎn)生一個異常,但是程序員可以不把它當作錯誤。程序員可以寫代碼報告這個異常,并進行必要的糾正。有些錯誤發(fā)生時連程序員也不知道該如何處理。程序員應(yīng)該遵照規(guī)格說明書來編程,但是往往規(guī)格說明書也沒有說該怎么辦,所以程序員也就不知道該怎么辦。這里有一個例子假設(shè)我們現(xiàn)在寫一個程序來為一個微處理器生成操作碼,規(guī)格說明書說一個load操作應(yīng)該返回操作碼1,一個store操作應(yīng)該返回操作碼2。程序員就把該規(guī)格說明寫成如下代碼了asm(load)-〉1;asm(store)_>2.現(xiàn)在假設(shè)系統(tǒng)試圖求值asm(jump)——該怎么處理呢?假設(shè)你是該程序員,并且你已經(jīng)習慣于編寫防御式(defensive)代碼,那么你可能會寫asm(load)-〉1;asm(store)_>2;asm(X)-〉??????但是??????該是什么呢?你會在該處寫什么樣的代碼?你現(xiàn)在碰到的情形就如同運行時系統(tǒng)遇到被0除一樣的情形,你寫不出有意義的代碼,你所能做的只有終止程序,于是你寫道asm(load)-〉1;asm(store)_>2;asm(X)->exit({oops,i,did,it,again,in,asm,X}).在S0DBS編譯器編譯asm(load)_>l;asm(store)_>2.的時候,就如同你已經(jīng)寫了asm(load)-〉1;asm(store)_>2;asm(X)_>exit({bad_arg,asm,X}).防御式編碼會破壞了代碼的純凈性,使代碼的閱讀者容易產(chǎn)生混淆。而且防御式編碼的診斷信息也未必比編譯器自動提供的診斷信息好。4.5顯意(intentional)代碼"顯意代碼"是我們給一種編程風格所起的名字,這種編程風格使得程序的閱讀者能夠很輕易地看到程序員編寫一段代碼的意圖。代碼的意圖應(yīng)該從它所調(diào)用的函數(shù)的名字上顯而易見,而不應(yīng)該需要通過對代碼的結(jié)構(gòu)分析來推斷。下面的例子很好地說明了這一點在早期的S0DBS的庫模塊dict中,導出了一個lookup/2的函數(shù),接口如下lookup(Key,Diet)_>{ok,Value}|notfo皿d在這種定義下lookup被用在了三種不同的上下文中1.用于數(shù)據(jù)獲取(dataretrieval)——程序員可能會寫lookup(Key,Diet)_>{ok,Value}|notfoimd這里lookup用一個已知的鍵(key)從字典(dictionary)中提取一個條目。Key應(yīng)該在字典中,否則就是一個編程錯誤,所以如果鍵沒有找到將會產(chǎn)生一個異常。2.用于搜索(searching)——如下代碼段caselookup(Key,Diet)of{ok,Val}_>...dosomethingwithVal...not_foimd_>...dosomethingelse...end.是搜索一個字典,我們并不知道Key是否存在——如果鍵不在字典中,將不會是一個編程錯誤。3.用于測試一個鍵的存在性——代碼段caselookup(Key,Diet)of{ok,_}_>...dosomething...not_foimd_>….dosomethingelse...end.是測試一個指定的鍵Key是否在字典中。在讀過數(shù)千行這種代碼后,我們開始擔心代碼的意圖了——我們問我們自己一個問題"程序員編寫這一行代碼的意圖到底是什么?"——在分析了上述三種用法后,我們的答案即數(shù)據(jù)獲取,搜索和測試。在很多不同的上下文中,我們都需要在一個字典里查找鍵。在某種情況下,程序員知道一個指定的鍵應(yīng)該存在于字典中,如果該鍵不在該字典中,則應(yīng)該是一個編程錯誤,程序應(yīng)該終止。另一種情況下,程序員不知道該鍵對應(yīng)的條目是否在字典中,他們的程序應(yīng)該能處理鍵在字典中和不在字典中兩種情況。拋棄對程序員的意圖的猜測,分析一下代碼,一組更好的庫函數(shù)是diet:fetch(Key,Diet)=ValI'EXIT'diet:search(Key,Diet)={found,Val}|not—found.diet:is—key(Key,Diet)=Boolean這就簡潔地表達了程序員的意圖——不需要對程序進行分析和猜測,我們清晰地看到了程序的意圖。顯然大家都知道fetch可以用search來實現(xiàn),search也可以用fetch來實現(xiàn)。但是如果fetch是原子性的,則我們也可以寫search(Key,Diet)_>case(catchfetch(Key,Diet))of{,EXIT,,」->not—found;Value-〉{found,Value}end.不過這并不是什么好代碼,因為我們先是產(chǎn)生了一個異常(該異常本應(yīng)說明程序已錯),后來卻修改了錯誤。更好的用法應(yīng)當如find(Key,Diet)_>casesearch(Key,Diet)of{ok,Value}_>Value;notfoimd_>exit({find,Key})end.這樣正好產(chǎn)生一個異常代表發(fā)生了一個錯誤。4.6討論軟件系統(tǒng)設(shè)計是一項嚴格的活動。編寫結(jié)構(gòu)清晰、意圖明顯的代碼是困難的。困難一部分源于要選擇正確的抽象。為了對付復(fù)雜的情況,我們使用了"分而治之"(divideandconquer)的方法,我們把復(fù)雜的問題分解成簡單一些的子問題,然后解決這些子問題。本章闡述了如何把許多復(fù)雜的問題分解成更簡單的子問題。在談到錯誤處理的時候,我闡釋了如何"抽象出"錯誤,并表明了程序應(yīng)該將"純凈"的代碼與"修復(fù)錯誤"的代碼劃分開的觀點。在編寫一個服務(wù)器程序的時候,我展示了如何抽象出服務(wù)器程序的兩個非功能特性。我展示了如何編寫一個當特征函數(shù)(特征函數(shù)定義了服務(wù)器的行為)中發(fā)生一個錯誤時不會導致服務(wù)器崩潰的服務(wù)器程序,我還展示了在不停下服務(wù)器的情況下如何修改服務(wù)器的行為。錯誤恢復(fù)、運行時修改系統(tǒng)的代碼是許多真實系統(tǒng)需要的兩項典型的非功能特性。通常的編程語言和系統(tǒng)對編寫已經(jīng)定義好的功能行為的代碼提供了強力的支持,但是對程序的非功能性部分的支持卻很貧乏。在大多數(shù)的編程語言中,編寫純的函數(shù)(其值確定地依賴于函數(shù)的輸入)是容易的,但是要做到修改運行時系統(tǒng)的代碼,或以一種通用的方式處理錯誤,或保護我們的代碼不受系統(tǒng)部分發(fā)生的故障的影響這一類事情,卻要困難得多,有時甚至是不可能的。因此,程序員運用了操作系統(tǒng)提供的服務(wù)——操作系統(tǒng)通常以進程的面貌提供了保護區(qū)域、并發(fā)機制等等。從某種意義上講,操作系統(tǒng)提供了"被編程語言設(shè)計者遺忘了的東西"。但是在S0DBS這樣的軟件平臺中,操作系統(tǒng)是幾乎不需要的。OS真正提供給S0DBS的只是一些設(shè)備驅(qū)動程序,而OS提供的諸如進程、消息傳遞、調(diào)度、內(nèi)存管理等等機制都不需要。用0S的機制來彌補編程語言的不足所帶來的問題是,操作系統(tǒng)的低層機制不能夠輕易地被改變。例如操作系統(tǒng)中關(guān)于什么是進程的概念以及進程間調(diào)度的策略都不能修改。通過給程序員提供輕量級的進程和關(guān)于錯誤檢測和處理的基本機制,應(yīng)用程序的編寫者就很容易地設(shè)計和實現(xiàn)他們自己的應(yīng)用操作系統(tǒng),這種應(yīng)用操作系統(tǒng)是專為他們的特定的問題的特征而特別設(shè)計的。S0DBS系統(tǒng)——用C編寫的一個應(yīng)用程序組——便是此中一例。5可容錯系統(tǒng)金融設(shè)備的設(shè)計者們在軟件設(shè)計中花了一半的精力在錯誤的檢測和糾正上。什么是可容錯系統(tǒng)?如何編寫可容錯系統(tǒng)?這個問題是此文檔的重點所在,也是我們理解如何構(gòu)建可容錯系統(tǒng)的關(guān)鍵。在本章中,我們定義了我們所說的"容錯"的含義,并提出了用來編寫可容錯系統(tǒng)的一種特殊方法。我們以兩條引述來開始本章如果一個系統(tǒng)的程序在出現(xiàn)邏輯錯誤的時候仍然能夠正確地執(zhí)行,我們就說該系統(tǒng)是可容錯的。要想設(shè)計并構(gòu)造一個可容錯系統(tǒng),你必須要明白系統(tǒng)在什么情況下應(yīng)該正常工作,在什么情況下該失效,可能會發(fā)生什么類型的錯誤。錯誤檢測是容錯系統(tǒng)的一個基本部件。也就是說,如果你知道發(fā)生了一個錯誤,你可能用替換掉出錯部件的方法、采用另一種計算方式的方法或上報一個異常的方法來達到包容該錯誤的目的。然而,你希望避免為了達到可容錯性而給系統(tǒng)增添不必要的復(fù)雜性,因為這些復(fù)雜性可能會導致系統(tǒng)可靠性的降低。我們將說明當檢測到一個反常情況發(fā)生時會發(fā)生什么事情,以及來建造一個軟件機制來檢測和糾正錯誤。此處余下部分我們講述—種可容錯編程的策略——該策略簡而言之就是當你不能糾正一個錯誤的時候,馬上放棄,只去做你可以做到的簡單一些的事情。監(jiān)督層級(supervisionhierarchies)-就是對任務(wù)的層次化組織。乖函數(shù)(well-behavedfunction)-就是那些應(yīng)該正確地工作的函數(shù)。乖函數(shù)產(chǎn)生的異常我們把它解釋成故障。5.1可容錯系統(tǒng)簡述為了使得系統(tǒng)可容錯,我們把軟件組織成一系列層次化的待執(zhí)行的任務(wù)(task)。最高層的任務(wù)按照某個規(guī)格說明來執(zhí)行著應(yīng)用邏輯。如果這個任務(wù)不能夠執(zhí)行,系統(tǒng)將會試圖執(zhí)行某個更簡單的任務(wù)。如果這個更簡單的任務(wù)仍然無法執(zhí)行,則系統(tǒng)將會嘗試執(zhí)行一個更更簡單的任務(wù),如此類推。如果系統(tǒng)中最底層的任務(wù)都無法執(zhí)行,那么就當系統(tǒng)發(fā)生了故障。這個方法直觀上感覺是很有吸引力的。它的意思是,如果我們不能做到我們想做的,那就做一些更容易做到的。我們還試圖組織一下我們的軟件,使得更簡單的任務(wù)由更簡單的軟件來執(zhí)行,這樣的話當任務(wù)變得更簡單時,成功的可能性就越高。當任務(wù)變得更簡單時,操作的側(cè)重點也發(fā)生了變化——相比提供完全的服務(wù),我們變得更關(guān)注于保護系統(tǒng)免受摧毀。雖然隨著任務(wù)層次的降低我們變得更保守,但是在所有的層次,我們的目標都是要提供一個可接受級的服務(wù)。當故障發(fā)生的時候,我們更關(guān)注于保護系統(tǒng),并且報告故障的確切原因——以便我們接下來可以對故障做點什么。這就意味著我們需要某種可以不受系統(tǒng)崩潰影響的持久化錯誤日志。在異常環(huán)境下,我們的系統(tǒng)會發(fā)生故障,但是當發(fā)生故障的時候我們絕不應(yīng)當丟失有關(guān)系統(tǒng)為什么會發(fā)生故障的信息。為了實現(xiàn)我們的任務(wù)層級,我們需要對"故障"(failure)這個詞有一個準確的認識。在SODBS中,對一個函數(shù)進行求值可能會導致異常(exc印tion)。但是異常不等于錯誤(error),而且不是所有的錯誤都將造成故障(failure)。所以我們需要討論一下異常、錯誤和故障之間的區(qū)別。異常、錯誤和故障之間最大的區(qū)別在于是在系統(tǒng)的哪個部分檢測到的非正常事件,該事件被如何處理,被如何解釋。我們來跟蹤一下當我們的系統(tǒng)中發(fā)生了一次異常情況時會發(fā)生什么事情——這里的描述是"自底向上"的,即從最初檢測到錯誤發(fā)生的點開始。在系統(tǒng)的最底層,SODBS虛擬機檢測到了一個內(nèi)部錯誤——它檢測到了一個被0除的情況,或者一個模式匹配錯誤或其他的情況。重要的是在檢測到這些情況時,進程對發(fā)生錯誤的地方后續(xù)的求值已經(jīng)變得沒有意義了。所以虛擬機仿真器無法繼續(xù),它做了唯一能做的事情,就是拋出一個異常。在它的相鄰層,該異??赡軙部赡懿粫徊东@。捕獲異常的程序段可能能夠也可能不能夠糾正異常所引起的錯誤。如果錯誤能夠成功被糾正,那么就不會造成什么傷害,進程會恢復(fù)到正常。如果該錯誤被捕獲了,但是糾正不了,那么可能會產(chǎn)生另一個異常,產(chǎn)生該異常的進程可以捕獲也可以不捕獲該異常。如果一個異常產(chǎn)生了但是沒有"捕捉處理者"(catchhandler),那么該進程將會發(fā)生故障。故障的原因?qū)粋鞑ソo當前與之相連的所有進程。收到這種故障信號的所有進程像對待正常的進程間消息一樣,可能會也可能不會截取并處理這些信號?,F(xiàn)在我們看到了當虛擬機仿真器中發(fā)生的一個非正常情況,是如何在系統(tǒng)中從下往上傳播的。在錯誤向上傳播的過程中,在每個點上都將嘗試著去糾正它。這種嘗試可能成功或失敗,所以我們就可以自如地決定在哪里、如何處理該錯誤。一個"被糾正了的"錯誤不會再被看作是一個故障,但是這要求該錯誤情形要能夠事先被預(yù)見到,并且針對該錯誤的糾正代碼要成功地執(zhí)行。至此,我么已經(jīng)看到了一個非正常情形是如何產(chǎn)生的,如何導致異常,該異常是如何被捕獲的,未被被捕獲的異常如何導致進程故障,進程故障如何被系統(tǒng)的其他進程檢測到。這些正是我們賴以實現(xiàn)我們的"任務(wù)層級"的一些可用的機制。5.2監(jiān)督層級回想一下,我們在本章的開始說到過"任務(wù)層級"的思想,其基本思想是[OS92]1.盡力執(zhí)行一個任務(wù)。2.如果你不能夠執(zhí)行一個任務(wù),則去執(zhí)行一個簡單一些的任務(wù)。我們將每個任務(wù)關(guān)聯(lián)上一個監(jiān)督者進程(supervisorprocess)——監(jiān)督者將會被賦予一個工作者(worker)來試圖達到該任務(wù)規(guī)定的目標。如果該工作者進程失敗并發(fā)出一個非正常的退出信號,則監(jiān)督者就會假定該任務(wù)已經(jīng)失敗,并發(fā)起某種錯誤恢復(fù)程序。錯誤恢復(fù)程序可能會重啟工作者,或者如果重啟失敗則轉(zhuǎn)而去做一些更簡單的事情。監(jiān)督者和工作者被按照如下規(guī)則安排成層次化的樹型關(guān)系1.監(jiān)督樹是監(jiān)督者組成的樹。2.監(jiān)督者監(jiān)視工作者和監(jiān)督者。3.工作者是behaviour的實例。4.behaviour用乖函數(shù)(well-behavedfunction)來參數(shù)化。5.乖函數(shù)在發(fā)生錯誤時會產(chǎn)生異常。在這里監(jiān)督樹是監(jiān)督者形成的層次化樹。樹中的每一個節(jié)點負責監(jiān)視它的子節(jié)點中發(fā)生的錯誤。監(jiān)督者是系統(tǒng)中監(jiān)視其他進程的進程。被監(jiān)督的對象是監(jiān)督者或工作者。監(jiān)督者必須能夠檢測到被監(jiān)視對象所產(chǎn)生的異常,能夠啟動、停止或重啟被監(jiān)督對象。工作者是執(zhí)行任務(wù)的進程。如果一個工作者進程以一個非正常退出信號(參見3.5.6節(jié))而終結(jié),那么監(jiān)督者就會認為已經(jīng)發(fā)生了一個錯誤,就會采取措施來修復(fù)該錯誤。在我們的模型中,工作者并不是任意的進程,而是為數(shù)不多的專用進程的實例(稱之為behaviour)。behaviour是其操作被一些回調(diào)函數(shù)完全特征化的專用進程。這些回調(diào)函數(shù)一定要是乖函數(shù)?!獋€關(guān)于behaviour的例子是sodbs——server,該behaviour用于編寫分布式的、可容錯的客戶_服務(wù)器程序。behaviour需要用一些WBF來進行參數(shù)化。所有程序員都應(yīng)該理解如何編寫WBF,才能編寫出可容錯的分布式客戶-服務(wù)器程序。sodbs—server這種behaviour為并發(fā)和分布式特性提供了一個可容錯的框架。程序員只需要關(guān)心的是編寫WBF來參數(shù)化該behaviour。為了簡便起見,我們這里考慮兩種監(jiān)督層次結(jié)構(gòu),分別為線性層次體系(linearhierarchies)和AND/0R層次樹(AND/ORhierarchytrees)。在接下來的章節(jié)里,我將對它們進行圖形化描述。5.2.l圖形表示法監(jiān)督者和工作者可以用圖2所示的符號來簡便地表示。監(jiān)督者記作方角矩形。在矩形的右上角用一個符號T來標明監(jiān)督者的類型。T的值要么為"0",代表"或(or)"型監(jiān)督,要么為"A",代表"與(and)"型監(jiān)督。關(guān)于監(jiān)督的類型稍后再詳述。監(jiān)督者能夠監(jiān)督任意個數(shù)的工作者或監(jiān)督者。對每一個被監(jiān)督的實體,監(jiān)督者都要知道如何來啟動、停止和重啟該實體。這種信息被保存在SSRS中,SSRS即"StartStopandRestartSpecification"(啟動停止重啟說明)。每個監(jiān)督者(監(jiān)督層次體系中的頂層監(jiān)督者除外)都有且僅有一個監(jiān)督者直接在它的上方,我們稱直接上層監(jiān)督者為直接下層的監(jiān)督者的父親(parent)。相反地,在監(jiān)督層次體系中某個監(jiān)督者直接下方的監(jiān)督者為該監(jiān)督者的孩子(children)。工作者被記作圓角矩形。工作者由乖函數(shù)來參數(shù)化。5.2.2線性監(jiān)督我先說線性層次結(jié)構(gòu)。圖3顯示了一個由三個監(jiān)督者組成的線性層次結(jié)構(gòu)。每個監(jiān)督者針對其每一個孩子都有一個SSRS,遵守下面的規(guī)則如果一個監(jiān)督者被其父親停止,那么該監(jiān)督者將停止其所有的孩子。如果一個監(jiān)督者的任何一個孩子崩潰,那么該監(jiān)督者將重啟該孩子。系統(tǒng)通過最頂層的監(jiān)督者啟動而啟動。最頂層的監(jiān)督者第一次啟動時,需要用到SSRS1。頂層監(jiān)督者有兩個孩子,即一個工作者和一個監(jiān)督者。頂層監(jiān)督者啟動一個工作者(為一個通過用乖函數(shù)WBF1進行參數(shù)化的behaviour),同時啟動一個子監(jiān)督者。層次體系中的下層監(jiān)督者也是按照類似的方式啟動起來,整個系統(tǒng)就跑起來了。5.2.3與/或監(jiān)督層級我們可以把我們的簡單的監(jiān)督層次體系擴展成一個含有與節(jié)點或或節(jié)點的樹型結(jié)構(gòu)。帶記號"A"表示一個"與"監(jiān)督者,帶記號"O"表示一個"或"監(jiān)督者。在一個與/或樹中的監(jiān)督者應(yīng)遵循如下規(guī)則如果一個監(jiān)督者被其父親停止,那么該監(jiān)督者將停止其所有的孩子。如果一個監(jiān)督者的一個孩子崩潰了,而自己是一個"與"監(jiān)督者,那么該監(jiān)督者將停止所有的孩子,然后重啟所有的孩子。如果一個監(jiān)督者的一個孩子崩潰了,而自己是一個"或"監(jiān)督者,那么該監(jiān)督者將重啟該孩子。"與"型監(jiān)督用于依賴性(d印endent)或關(guān)聯(lián)性(co-ordinate)的進程。在"與"型樹中,系統(tǒng)運行的成功依賴于所有孩子的成功——因此,當有任何一個孩子崩潰時,就應(yīng)該停止所有孩子并重啟它們。"或"型監(jiān)督可以用來協(xié)調(diào)獨立進程(ind印endentprocess)的行為。在"或"型樹中,孩子們的行為被認為是彼此獨立的,所以一個孩子不會影響到其它孩子,因此一個孩子出錯只需將該孩子進程重啟。落實到具體,我們的"任務(wù)層次體系"就是用一個"監(jiān)督層次體系"來表示的。在我們的系統(tǒng)中,我們把所有的任務(wù)等價于一系列目標,這些目標都具有一個不變量(invariant)——如果與目標相關(guān)聯(lián)的不變量為非假,我們就說達到了該目標。在大多數(shù)程序中,對不變量的取值的判斷通常對應(yīng)于一個特別指定的函數(shù)的求值語句是否產(chǎn)生了異常。Candea和Fox之前已經(jīng)做過相似的工作,他們曾做過一個基于"可遞歸重啟的(recursively-restartable)Java組件,,的系統(tǒng)。請注意,我們將錯誤區(qū)分成了兩類可糾正的(correctable)錯誤和無法糾正的(uncorrectable)錯誤??杉m正的錯誤是指那些在部件中可以被檢測到和糾正的錯誤。無法糾正的錯誤是指那些能夠被檢測到,但是沒有指定其糾正程序的錯誤。上面的討論都是相當模糊的,因為我們還沒有說到底什么算是錯誤,也沒有說在實踐中我們?nèi)绾螀^(qū)分可糾正的錯誤與無法糾正的錯誤。再加上實際情況是絕大多數(shù)規(guī)格說明書只說明了當系統(tǒng)中的每個部件都根據(jù)計劃運轉(zhuǎn)時該怎么做,而很少說明當某個特定的錯誤發(fā)生時該怎么做——這就使得情況更加復(fù)雜了。的確,如果一個規(guī)格說明書嚴格說明了當一個特定錯誤發(fā)生時該做什么,那或許就有很多人會說這種情況根本就不是錯誤,而是系統(tǒng)的一個預(yù)期特性。這就使得"錯誤"一詞的含意更加模糊。5.3什么是錯誤?當我們的程序運行的時候,運行時系統(tǒng)根本不知道該把什么當作是錯誤——它只管按照代碼來執(zhí)行。判斷運行出現(xiàn)錯誤的唯一跡象就是產(chǎn)生的異常。當運行時系統(tǒng)不能夠決定該怎么做時,就會自動產(chǎn)生一個異常。例如,當執(zhí)行一個除法操作時,運行時系統(tǒng)就可能會去檢測一種"被0除"的情況,當出現(xiàn)該情況時,就產(chǎn)生一個異常,因為運行時系統(tǒng)不知道該如何處理。一個異常并不總是對應(yīng)于錯誤。例如,如果一39個程序員已經(jīng)編寫了代碼來正確地應(yīng)對"被O除"這一異常,那么出現(xiàn)這個異常就不必再當成是錯誤了?!獋€異常是否對應(yīng)于一個錯誤完全有程序員來決定——在我們的系統(tǒng)中,程序員必須明確地說明系統(tǒng)中那些函數(shù)絕對不能產(chǎn)生異常。—旦一個組件的行為與它的規(guī)格說明不再一致,就說該組件發(fā)生了故障。為了我們特定的目的,我們將一個錯誤定義為觀察到的系統(tǒng)行為與期望的系統(tǒng)行為之間的背離。這里期望的行為是指"規(guī)格說明中說明的系統(tǒng)應(yīng)該具有的行為"。程序員必須確保一旦系統(tǒng)的行為方式與規(guī)格說明發(fā)生背離,就能夠啟動某種錯誤恢復(fù)程序,并且這種情況的記錄能夠被某種持久的錯誤日志記錄下來,以便日后改正。在構(gòu)建真實的系統(tǒng)時,情況會因為我們并沒有一個完整的規(guī)格說明而變得復(fù)雜。在這種情況下,程序員應(yīng)當對什么應(yīng)該當成錯誤,什么不應(yīng)當作錯誤有一些通用的概念。在缺少顯式的規(guī)格說明的情況下,我們需要一個隱式的機制,來符合我們的直覺的想法,即一個錯誤是"導致程序崩潰的事件"。在S0DBS系統(tǒng)中,我們制作了若干乖函數(shù)(Well-behavedfunction,WBF)-乖函數(shù)是用來參數(shù)化S0DBS的behaviour的。這些函數(shù)由SODBS的behaviour中的代碼來調(diào)用。如果對參數(shù)化函數(shù)的調(diào)用產(chǎn)生了一個異常,那么這就被定義為一個錯誤,一條錯誤診斷就會被添加到錯誤日志中。loop(Name,F,State)_>receive{From,Query}_>case(catchF(Query,State))of{,EXIT,,Why}_>log—error(Name,Query,Why),F(xiàn)rom!{Name,crash},loop(Name,F,State);(R印ly,Statel}_>From!{Name,ok,Reply},loop(Name,F,Statel)endend.回調(diào)函數(shù)F是在一個catch語句中被調(diào)用的。如果產(chǎn)生了一個異常Why,則該異常被當作是一個錯誤,一條錯誤消息就被添加到錯誤日志中。這只是一個非常簡單的例子,但是已經(jīng)闡明了SODBS的behaviour中錯誤處理的基本原理。例如,在S0DBS的sodbs_server這種behaviour中,我們編寫了一個用來參數(shù)化服務(wù)器的回調(diào)模塊M。這個模塊M除了其他事情之外,還必須導出回調(diào)函數(shù)handl—ca11/2。5.3.1乖函數(shù)(Well-behavedfunctions)乖函數(shù)(WBF)是指正常情況下不應(yīng)該發(fā)生異常的函數(shù)。如果一個WBF發(fā)生了一個異常,那么這個異常將被解釋成一個錯誤。如果在對一個WBF進行求值的時候產(chǎn)生了一個異常,那么該WBF應(yīng)該盡力扭轉(zhuǎn)產(chǎn)生異常的環(huán)境。如果WBF中產(chǎn)生了一個不能糾正的異常,那么程序員應(yīng)該用一個顯式的退出(exit)語句來結(jié)束該函數(shù)。乖函數(shù)的編寫應(yīng)該遵循如下規(guī)則規(guī)則1——程序應(yīng)該與規(guī)格說明同構(gòu)(isomorphic)。程序應(yīng)該忠實地遵循規(guī)格說明。規(guī)格說明書讓做什么,程序就應(yīng)該做什么,哪怕是愚蠢的事情。程序必須忠實地再生規(guī)格說明書中的錯誤。規(guī)則2——如果規(guī)格說明沒有說明該做什么,就產(chǎn)生一個異常。這一點是非常重要的。規(guī)格說明通常會說明當發(fā)生某種情況時該做什么,而忽略了如果其他情況時該做什么。那么答案就是"產(chǎn)生一個異常"。不幸的是許多程序員都在這時候充分發(fā)揮了他們的創(chuàng)造性的猜想力(guessiork),試圖猜測設(shè)計者當時應(yīng)該會是怎么樣的意圖。如果按照這樣來編寫系統(tǒng),那么觀測到的異常就會反映出規(guī)格說明中的錯誤。規(guī)則3——如果發(fā)生的異常沒有包含足夠的信息使得可以將該錯誤隔離,那么就在異常中加一些額外的有用信息。在程序員編寫代碼的時候,他們應(yīng)該自問一下,在一個錯誤發(fā)生時,應(yīng)該往錯誤日志中寫入些什么信息呢?如果出錯信息對調(diào)試來說不充分,那么他們就應(yīng)該往異常中添加足夠的信息,以使得程序在下一步能夠被調(diào)試。規(guī)則4——把非功能性需求變成可在運行時進行檢查的斷言(assertion)(不變量)。如果斷言失敗,就產(chǎn)生一個異常。這種情況的一個例子就是關(guān)于循環(huán)的終止——一個編程錯誤可能會致使一個函數(shù)進入一個無限循環(huán),從而導致函數(shù)不能退出。像這樣的錯誤應(yīng)該通過請求某個函數(shù)在一個規(guī)定的時間內(nèi)終結(jié)來檢測到。通過時間檢測,如果一個函數(shù)在規(guī)定的時間內(nèi)沒有終止,就產(chǎn)生一個異常,從而結(jié)束該函數(shù)。6構(gòu)建應(yīng)用前面各章分別介紹了編寫可容錯系統(tǒng)的一個一般模型,介紹了用以監(jiān)視系統(tǒng)的行為的"監(jiān)督樹"的思想。本章將從一般的理論方面轉(zhuǎn)移到監(jiān)督者在SODBS系統(tǒng)中的特定實現(xiàn)。為了闡明監(jiān)督原理,我構(gòu)建了一個簡單的SODBS應(yīng)用(即plication)。該應(yīng)用包含有一個監(jiān)督者進程,來管理三個工作者進程,這三個工作者進程是sodbs—server,Sodbs_event禾口sodbs_fsm這三禾中behaviour的實例。6.lbehaviour庫使用了SODBS平臺軟件的應(yīng)用都是由許多的"behaviour"構(gòu)建的。behaviour是對一些公共編程模式的抽象,在用SODBS語言來實現(xiàn)一個系統(tǒng)時可以作為構(gòu)建塊(buildingblocks)來使用。本章的余下部分將要討論的behaviour如下所列sodbs—server——這種behaviour用來構(gòu)建在客戶-服務(wù)器模型中使用的服務(wù)器程序。sodbS_eVent——這種behaviour用來構(gòu)建事件處理器程序。事件處理器程序是指像錯誤日志記錄器一樣之類的程序。一個事件處理器是響應(yīng)一個事件流的程序,它不必對向事件處理器發(fā)送事件的進程作出應(yīng)答。41[O780]sodbs_fsm——這種behaviour用來實現(xiàn)有限狀態(tài)機。supervisor-這禾中behaviour用來實現(xiàn)監(jiān)督樹。即plication-這種behaviour用作打包整個應(yīng)用程序的容器。對于每種behaviour,我會介紹其一般原理,還會介紹它的編程API的一些特殊細節(jié),并且會給出一個如何創(chuàng)建該behaviour的實例的一個完整的例子。使用S0DBS平臺構(gòu)建的系統(tǒng)遵循如下層次化的方式發(fā)布(releases)——發(fā)布處于層級的頂端。一個發(fā)布包含有構(gòu)建和運行一的所有必要信息。一個發(fā)布由一個軟件檔案(archive)(以某種形式打包)和一組發(fā)布的規(guī)程組成。由于發(fā)布升級必須在不停止目標系統(tǒng)的情況下安裝,因此安裝一的過程非常復(fù)雜。一個SODBS發(fā)布將這種復(fù)雜性打包到一個單獨的抽象單元中。在布內(nèi)部,包含零個或多個應(yīng)用。應(yīng)用(即plications)——應(yīng)用比發(fā)布要簡單,它包含所有的代碼和運行一個單獨應(yīng)用所需要的所有操作規(guī)程,但并不是整個系統(tǒng)。當一個發(fā)布包含多個應(yīng)用時,系統(tǒng)就應(yīng)該按照這種方式來組織要么確保每個不同的應(yīng)用之間充分獨立,要么不同的應(yīng)用都有著嚴格的層次化依賴關(guān)系。監(jiān)督者一S0DBS的應(yīng)用一般都是由一些監(jiān)督者的實例構(gòu)成。工作者——S0DBS的監(jiān)督者監(jiān)督工作者節(jié)點。工作者節(jié)點通常是sodbs—server、sodbs—event或sodbs_fsm等behaviour的實例。我們要特別解釋一下應(yīng)用。應(yīng)用是從工作者節(jié)點開始自底向上(bottom-up)構(gòu)建的。我會創(chuàng)建三個工作者節(jié)點(sodbs—server、sodbs—event和sodbs_fsm的實例各一個)。工作者節(jié)點由一個簡單的監(jiān)督樹來管理,監(jiān)督樹被打包成一個應(yīng)用。我就從工作者節(jié)點說起。6.1.lbehaviour庫是怎么寫成的S0DBS的behaviour都是用類似第4.1節(jié)的例子中的編程風格來編寫的。只有一個主要的不同,我們不是通過任意的函數(shù)來參數(shù)化behaviour,而是通過模塊的名字來參數(shù)化一個behaviour。該模塊必須導出一些指定的預(yù)定義的(pre-defined)函數(shù)。具體哪些函數(shù)需要被導出,依賴于behaviour的定義方式。每個behaviour的完整的API在其使用手冊中有詳細的文檔。舉個例子,假設(shè)xyz是sodbs—server這禾中behaviour的——個實例,那么xyz.sodbs就必須包含如下代碼-module(xyz).-behaviour(sodbs—server).-export([init/1,handle_call/3,handle_cast/2,handle_info/2,terminate/2,change_code/3])....xyz.sodbs必須導出如上所示的init/l…等六個函數(shù)。要創(chuàng)建一個sodbs_server的實例,我們就要調(diào)用sodbs—server:start(ServerName,Mod,Args,Options)這里ServerName給服務(wù)器命名,Mod填寫原子xyz,Args是傳遞給xyz:init/1的統(tǒng)該布發(fā)系裝發(fā)個個安個一參數(shù),Options是用來控制服務(wù)器自身的行為的參數(shù)。Options不會作為參數(shù)傳遞給模塊,0第4章中給出的例子中對behaviour的參數(shù)化的方法在某種程度上比S0DBS所采用的方法要更通用。造成這種差異主要是由于歷史原因,最初的behaviour是在fun這種方法增加到SODBS中之前編寫的。6.2專用服務(wù)器(PrivateServer)的原理第4章中我們介紹了專用服務(wù)器的思想。專用服務(wù)器首先提供了一個"空的"服務(wù)器,即一個可以被實例化為服務(wù)器的框架。它清晰地闡明了制作一個專用服務(wù)器的相關(guān)原理。在SODBS系統(tǒng)中,SODBS模塊sodbs—server用來構(gòu)造客戶-服務(wù)器的服務(wù)器模塊。sodbs—server可以通過許多不同的途徑被參數(shù)化成許多不同類型的服務(wù)器。6.2.1專用服務(wù)器的API為了便于理解sodbs—server的API,我們來看看服務(wù)器程序與應(yīng)用之間的控制流。我會描述一下sodbs—server的API中在本章的例子中將會用到的一個子集。sodbs—server:start(Namel,Mod,Arg,Options)_>Result在此Namel=服務(wù)器的名字(見注解1)。Mod=回調(diào)模塊的名字(見注解3)。Arg=傳遞給Mod:init/1的參數(shù)(見注解4)。Options=控制服務(wù)器工作方式的一組選項。Result=通過求值Mod:init/1而獲得的值(見注解4)。sodbs—server:call(Name2,Term)_>Resuit在此Name2=服務(wù)器的名字(見注解2)。Term=傳遞給Mod:handle_call/3的參數(shù)(見注解4)。Result=通過求值Mod:handle_call/l而獲得的值(見注解4)。sodbs—server:cast(Name2,Term)_>ok在此Name2=服務(wù)器的名字(見注解2)。Term=傳遞給Mod:handle_cast/3的參數(shù)(見注解4)。注解1.Namel應(yīng)為如{local,Name2)或{global,Name2)般的項式。啟動一個本地服務(wù)器會在一個單節(jié)點上創(chuàng)建一個服務(wù)器。啟動一個全局服務(wù)器會在一個可為其它分布式SODBS節(jié)點透明地訪問的節(jié)點上創(chuàng)建一個服務(wù)器。2.Name2是一個原子。3.Mod應(yīng)當導出如下一些或全部函數(shù)init/1,handle_call/3,handle_cast/3,terminate/2。這些函數(shù)將會被sodbs—server調(diào)用。4.sodbs_server的某些函數(shù)的參數(shù)會原封不動地作為參數(shù)傳遞給Mod的某些函數(shù)。類似的,Mod的函數(shù)的返回值中包含的某些項式也會出現(xiàn)在sodbs—server的某些函數(shù)的返回值中。Mod所提供的回調(diào)函數(shù)應(yīng)遵循如下規(guī)格Mod:init(Arg)_>{ok,State}|{stop,Reason}此函數(shù)試圖啟動服務(wù)器Arg是提供給sodbs_server:start/4的第3個參數(shù)。{ok,State}意思是服務(wù)器成功啟動了。服務(wù)器的內(nèi)部狀態(tài)變成了狀態(tài)State,說明此時對sodbs—server:start的原始調(diào)用返回了{ok,Pid},這里Pid是服務(wù)器的標識符。{stop,Reason}意思是服務(wù)器啟動失敗了,這種情況下對sodbs—server:start的調(diào)用會返回{error,Reason}。Mod:handle_call(Term,From,State)_>(r印ly,R,SI}此函數(shù)在用戶調(diào)用sodbs_server:call(Name,Term)的時候被調(diào)用Term是任意的一個項式(譯注該項式為用戶自定義,用于標識具體的調(diào)用請求)。From標識客戶。State是服務(wù)器當前的狀態(tài)。(r印ly,R,SI}使sodbs_server:call/2的返回值為R,而服務(wù)器的新狀態(tài)變?yōu)镾I。Mod:handle_cast(Term,State)->(nor印ly,SI}|{stop,R,SI}此函數(shù)在用戶調(diào)用sodbs_server:cast(Name,Term)的時候被調(diào)用Term是任意的一個項式。State是服務(wù)器當前的狀態(tài)。(nor印ly,SI}使服務(wù)器的狀態(tài)變?yōu)镾l。{stop,R,SI}使服務(wù)器停止。服務(wù)器停止時要調(diào)用Mod:terminate(R,SI)。Mod:terminate(R,S)_>void此函數(shù)在服務(wù)器停止的時候被調(diào)用,返回值被忽略R是服務(wù)器終止的原因。State是服務(wù)器當前的狀態(tài)。6.2.2專用服務(wù)器的例子這里舉一個用sodbs_server實現(xiàn)簡單的鍵_值(Key-Value)服務(wù)器的例子。本鍵_值服務(wù)器用一個叫kvl的回調(diào)模塊來實現(xiàn)的。kv的第2行告訴編譯器本模塊時sodbs—server這種behaviour的回調(diào)模塊。那么如果本模塊沒有導出sodbs—server所需要的正確的回調(diào)函數(shù)集,編譯器就會產(chǎn)生告警??蛻艉瘮?shù)可以在系統(tǒng)內(nèi)部任何地方調(diào)用。回調(diào)函數(shù)只會在sodbs—server模塊內(nèi)部被調(diào)用。kv:start()通過調(diào)用sodbs_server:start_link/4來啟動月艮務(wù)器。傳給sodbs_server:start_link/4的第1個參數(shù)為服務(wù)器的位置。在我們的例子中,位置為{local,kv},意思是服務(wù)器是一個本地注冊的進程,名字為kv。關(guān)于位置的參數(shù),還可以填寫許多其他的值。包括(global,Nameh這種值標明用一個全局名字(而不是本地名字)來注冊服務(wù)器。用一個全局名字將允許服務(wù)器可以被一個分布式SODBS系統(tǒng)中的其他任何節(jié)點訪問。sodbs_server:start_link/4的其余參數(shù)為回調(diào)模塊名字(kv)、初始化參數(shù)(argl)、和一組控制和調(diào)試選項參數(shù)([])。如果把控制和調(diào)試選項參數(shù)設(shè)置成[{debug,[trace,log]}]那么將會開啟調(diào)試器,并把調(diào)試信息寫入到一個日志記錄(log)文件。44當調(diào)用sodbs—server:start—link/4時,sodbs—server會調(diào)用kv:init(Arg)來對其內(nèi)部數(shù)據(jù)結(jié)構(gòu)進行初始化,這里Arg為提供給sodbs_server:start_link/4的第3個參數(shù)?!銇碚f,init/1應(yīng)該返回一個{ok,State}式的元組。kv導出的客戶數(shù)store/2禾卩l(xiāng)ookup/1通過調(diào)用sodbs_server:call/2來實現(xiàn)。在內(nèi)部,遠程過程調(diào)用(remoteprocedurecall)的實現(xiàn)是通過調(diào)用回調(diào)函數(shù)handle—call/2來實現(xiàn)的。第23-29行實現(xiàn)了服務(wù)器側(cè)的遠程過程調(diào)用所需要的回調(diào)函數(shù)。handle_call的第1個參數(shù)是一個模式,必須要與調(diào)用sodbs_server:call/2時使用的第2個參數(shù)匹配。第3個參數(shù)為服務(wù)器的狀態(tài)。在一般情況下,handle—call應(yīng)該返回一個(r印ly,R,Statel},這里R是遠程過程調(diào)用的返回值(該值也會成為sodbs_server:call/2的返回值,最終返回給客戶),Statel將變成服務(wù)器的新的狀態(tài)值。在第12行stop/0中調(diào)用的sodbs—server:cast(kv,stop)用來停止月艮務(wù)器。sodbs—server:cast(kv,stop)的第2個參數(shù)stop作為了31行中handle_cast/2的第1個參數(shù),handle_cast/2的第1個參數(shù)為服務(wù)器的狀態(tài)。handle—cast返回的{stop,Reason,State}將迫使專用服務(wù)器去調(diào)用kv:terminate(Reason,State)。這種處理給了服務(wù)器一個機會去執(zhí)行任何希望在退出之前執(zhí)行的臨終操作。當termintate/2返回時,專用服務(wù)器會停止下來,其所有已注冊的名字也被移除。在本例中,我們只是展示了一個使用專用服務(wù)器的簡單的例子。sodbs—server的手冊將給出傳給sodbs—server的回調(diào)函數(shù)和控制函數(shù)的參數(shù)能夠接受的值的所有選擇。專用服務(wù)器可以用許多種不同的方式來參數(shù)化,以便簡化作為本地服務(wù)器或分布式S0DBS節(jié)點網(wǎng)絡(luò)上的全局服務(wù)器的運行。專用服務(wù)器還有許多內(nèi)置的調(diào)試幫助手段,可以方便程序員使用。用sodbs—server構(gòu)建的服務(wù)器的內(nèi)部發(fā)生一個錯誤時,關(guān)于哪里發(fā)生了錯誤的一個完整的調(diào)用軌跡會被自動添加到系統(tǒng)的錯誤日志中。該信息對于服務(wù)器的死因調(diào)查通常是很有意義的。[OSS1]6.3專用事件管理器(EventManager)的原理事件管理器behaVioursodbs_event提供了構(gòu)建特定于應(yīng)用的事件處理函數(shù)的一種專用框架。事件管理器可以完成如下任務(wù)錯誤處理。告警管理。調(diào)試。設(shè)備管理。事件管理器可以提供命名對象,事件可以發(fā)送給這些命名對象。在l個事件管理器中,可以安裝O個或多個事件處理器(eventhandler)。當一個事件達到一個事件管理器時,它將會被該事件管理器內(nèi)部安裝的所有事件處理器進行處理。事件管理器可以在運行時被操縱,特別是我們可以在運行時安裝一個事件處理器,去掉一個事件處理器或用另一個處理器來代替一個處理器。我們先來看一些定義事件(Event)——發(fā)生的某件事情。事件管理器(EventManager)——一個對某一類事件的處理進行協(xié)調(diào)的程序。事件管理器提供一個命名對象,事件可以發(fā)送給它。通知(Notification)——向一個事件管理器發(fā)送一個事件的動作。事件處理器(EventHandler)——一個可以處理事件的函數(shù)。事件處理器必須是類型如下的函數(shù)StatexEvent_>State,事件管理器維護一個{M,S}形式的"模塊X\u29366X態(tài)"二元組的列表。我們稱這樣的列表為模塊-狀態(tài)(MS)列表。假設(shè)事件管理器的內(nèi)部狀態(tài)可以用如下MS列表來表示[{Ml,SI},{M2,S2},]當事件管理器接收到一個事件E的時候,如上的列表將變?yōu)閇{Ml,SINew},{M2,S2New},...]。這里應(yīng)該有{ok,SiNew}=Mi:handle_event(E,Si)。事件管理器可以被當作是一個一般的常規(guī)有限狀態(tài)機,只不過不是維護一個狀態(tài),我們維護的是一"組"狀態(tài)和一組狀態(tài)遷移函數(shù)。如同我們可能預(yù)期的那樣,sodbs—event的API中也有許多接口函數(shù),是用來操縱服務(wù)器中的{Module,State}對的。sodbs—event比我們在這里的一點簡單介紹要強大得多??梢酝ㄟ^閱讀S0DBS文檔中關(guān)于事件處理方面的手冊來了解的所有的細節(jié)。6.3.1專用事件管理器的API事件管理器(sodbs_event)導出了下列函數(shù)sodbs—event:start(Namel)_>{ok,Pid}|{error,Why}創(chuàng)建一個事件管理器。Namel是事件管理器的名字(見注解1)。{ok,Pid}意味著事件管理器開啟成功。Pid就是事件管理器的進程PID。{error,Why}是在事件管理器開啟失敗時的返回值。sodbs—event:add—handler(Name2,Mod,Args)_>ok|Error添加一個新的處理器到事件管理器中。如果事件管理器的原有狀態(tài)是L,那么當此操作成功時,事件管理器的狀態(tài)將變成[{Mod,S}lL],這里S是調(diào)用Mod:init(Args)獲得的值。Name2是事件管理器的名字(見注解1)。Mod是回調(diào)模塊的名字(見注解2)。Arg是傳遞給Mod:init/1的參數(shù)。sodbs—event:notify(Name2,E)_>ok發(fā)送一個事件E給事件管理器。如果事件管理器的狀態(tài)是一個{Mi,Si}的集合集且收到一個事件E,那么事件管理器的狀態(tài)將編程{Mi,SiNew}的集合,而{ok,SiNew}=Mi:handle_event(E,Si)。sodbs—event:call(Name2,Mod,Args)_>Reply執(zhí)行事件管理器中的某個事件處理器上的某個操作。如果事件管理器的狀態(tài)列表包含一個元組{Mod,S},那么將會調(diào)用Mod:handle—ca11(Args,S)。R印ly就是源自該調(diào)用的返回值。注解1.事件管理器遵循與專用服務(wù)器相同的命名約定。2.—個事件處理器必須導出下列中的一些或全部函數(shù)init/1,handle_event/2,handle_call/3,terminate/2。—個事件處理器模塊應(yīng)該具有下列API:Mod:init(Args)->{ok,State}這里Args來自sodbs_event:add_handler/3的第3個參數(shù)。State是本事件處理器的初始狀態(tài)值。Mod:handle_event(E,S)->{ok,SI}這里E來自sodbs_event:notify/2的第2個參數(shù)。S是本事件處理器的原有狀態(tài)值。SI本事件處理器的新的狀態(tài)值。Mod:handle_call(Args,State)->{ok,R印ly,Statel}這里Args來自sodbs_event:call/2的第2個參數(shù)。State是本事件處理器的原有狀態(tài)值。R印ly將成為sodbs_event:call/2的返回值。Statel是本事件處理器的新的狀態(tài)值?!獋€簡單的錯誤記錄器Mod:terminate(Reason,State)_>void這里Reason標明事件管理器為什么被停止。State是本事件處理器的當前狀態(tài)值。6.3.2專用事件管理器的例子我們用sodbs—event來構(gòu)建了一個簡單的錯誤記錄器。該錯誤記錄器會跟蹤最近的5個錯誤消息,還可以在收到r印ort事件時顯示最近的5個錯誤消息。注意,simple_logger.sodbs中的代碼是純順序化的。在此我們會注意到傳遞給sodbs—server的參數(shù)的形式與傳遞給sodbs—event的參數(shù)的形式的相似之處。一般而言,傳遞給不同behaviour模塊中諸如start,stop,handle_call等等函數(shù)的參數(shù),我們會設(shè)計得盡量的相似。6.4專用有限狀態(tài)機(FiniteStateMachine)的原理許多應(yīng)用(例如協(xié)議棧)可以用有限狀態(tài)機(FSM)來建模。FSM可以用有限狀態(tài)機behaviour,艮卩sodbs_fsm來編寫。—個FSM可以用如下形式的一組規(guī)則來描述State(S)xEvent(E)_>Actions(A)xState(S')...這個規(guī)則的意思是如果我們處于狀態(tài)S,發(fā)生了一個事件E,那么我們應(yīng)該執(zhí)行操作A,并把狀態(tài)遷移到S,。如果我們選擇用sodbs_fsm這種behaviour來編寫一個FSM,那么上面的狀態(tài)遷移規(guī)則就應(yīng)該被寫作一些遵循如下約定的SODBS函數(shù)StateName(Event,StateData)_>..codeforactionshere...{next_state,StateName,,StateData,}6.4.1專用有限狀態(tài)機的API有限狀態(tài)機behaviour(sodbs_fsm)導出了下列函數(shù)sodbs_fsm:start(Namel,Mod,Arg,Options)_>Result該函數(shù)的功能跟先前討論過的sodbs_server:start/4—樣。sodbs_fsm:send—event(Namel,Event)_>ok發(fā)送一個事件給標識符為Name1的FSM。回調(diào)模塊Mod必須導出下列函數(shù)Mod:init(Arg)_>{ok,StateName,StateData}當一個FSM啟動的時候,它會調(diào)用init/l,Mod:init/1應(yīng)該返回一個初始狀態(tài)StateName,和一些該狀態(tài)的相關(guān)數(shù)據(jù)StateData。接下來調(diào)用sodbs_fsm:send—event(…,Event)時,F(xiàn)SM會調(diào)用Mod:StateName(Event,StateData)。Mod:StateName(Event,SData)_>(證tstate,SNamel,SDatal}在FSM運轉(zhuǎn)時,StateName、Event和SData表示FSM的當前狀態(tài)。而FSM的下一個狀態(tài)應(yīng)為SNamel,下一個狀態(tài)相關(guān)的數(shù)據(jù)應(yīng)該為SDatal。6.4.2專用有限狀態(tài)機的例子為了描述一個典型FSM的應(yīng)用,我們利用sodbs—fsm寫了一個簡單的包聚合器(packetassembler)的程序。該包聚合器有2個狀態(tài)waiting禾口collecting。當它處于waiting狀態(tài)時,它期望收到包含有包長度的信息,此時它會進入collecting狀態(tài)。當它處于collecting狀態(tài)時,它期望收到許多小的數(shù)據(jù)包,這些小的數(shù)據(jù)包將會被聚合。當所有小數(shù)據(jù)包的長度等于總的包長度時,F(xiàn)SM會打印出聚合包,并重新進入waiting狀態(tài)。我們可以在SODBS的shell中下達一段命令來看看這個包聚合器的用法>packet_assembler:start().{ok,〈0.44.0>}>packet_assembler:send—header(9).ok>packet_assembler:send—data(〃Hello").ok>packet_assembler:send—data(〃〃).ok>packet_assembler:send—data(〃Joe").48Gotdata:HelloJoeok再次強調(diào),SOdbS_fsm比這里所描述的要有用得多。6.5專用監(jiān)督者(Supervisor)的原理到目前為止,我們所著重講到的都是為了解決典型應(yīng)用問題的一些基本behaviour,而編寫應(yīng)用中大部分問題也都可以用基本的客戶-服務(wù)器、事件處理、和FSM等behaviour來解決。這里要講的sodbs_sup這種behaviour是第一個元行為(mete-behaviour),艮卩用來將基本behaviour粘合成一個監(jiān)督體系的behaviour。6.5.l專用監(jiān)督者的API專用監(jiān)督者的API是極其簡單的supervisor:start—link(Namel,Mod,Arg)_>Result本函數(shù)開啟一個監(jiān)督者,其間調(diào)用Mod:init(Arg)函數(shù)?;卣{(diào)模塊Mod必須導出init/1函數(shù),規(guī)格如Mod:init(Arg)_>SupStrategySupStrategy是描述監(jiān)督樹的項式。SupStrategy是一個描述監(jiān)督樹中的工作者們?nèi)绾伪粏?、停止和重啟的項式。我不在這里詳細描述,接下來的一個簡單的監(jiān)督樹的例子會有比較詳盡的描述。關(guān)于專用監(jiān)督者的完整細節(jié)可以參見用戶手冊的相關(guān)部分。6.5.2專用監(jiān)督者的例子我們前面的例子是一個監(jiān)督者監(jiān)督了前面各節(jié)所介紹的三個工作者。我們現(xiàn)在來看看當運行時這些工作者發(fā)生錯誤時,會發(fā)生什么事情。Si,le_SUp.sodbs模塊定義了該監(jiān)督者的行為。開始在第7行調(diào)用了superivsor:start_link/3--這與系統(tǒng)中其它behaviour的調(diào)用習慣是一致的。?MODULE是一個宏,被展開為當前模塊的名字simple—sup。最后一個參數(shù)被設(shè)置為nil。監(jiān)督者開啟的時候會用start_link/3的第3個參數(shù)作為參數(shù)去調(diào)用指定的回調(diào)模塊中的init/1函數(shù)。init/1返回一個定義了監(jiān)督樹的形狀和所采用的策略的數(shù)據(jù)結(jié)構(gòu)。項式{one_fOr_One,5,1000}(第11行)告訴監(jiān)督者構(gòu)建一個"或"型監(jiān)督樹(參見5.2.3小節(jié))——這是因為它所監(jiān)督的三個工作者是彼此沒有關(guān)系的。數(shù)字5和1000指定了一個重啟頻率(restartfrequency)——如果監(jiān)督者在1000秒鐘內(nèi)重啟了被監(jiān)督者超過5次,則監(jiān)督者本身將會出錯?!獋€簡單的監(jiān)督者這里我們的監(jiān)督樹中有三個被監(jiān)督對象,但是我只描述包聚合器是如何添加到監(jiān)督樹中的。另外兩個工作者的添加方法依次類推。第13-15行指定了包聚合器這個工作者。第13行開始,元組中的第一個元素描述了包聚合器如何被監(jiān)督。原子packet是一個任意的名字(在是在本監(jiān)督者實例的內(nèi)部要保證是唯一的),可以用來指示監(jiān)督樹中的節(jié)點。因為被監(jiān)督者本身也是SODBS的behaviour的實例,所以把他們添加到監(jiān)督樹中會很容易。下一個參數(shù)(第14行)是一個3元組(M,F(xiàn),Ah被監(jiān)督者用來啟動指定的進程。如果監(jiān)督者要啟動一個被監(jiān)督的進程,它會去調(diào)用即ply(M,F(xiàn),A)。第15行的第一個參數(shù)permanent是說被監(jiān)督的進程是一個所謂的"永恒"進程。一個永恒進程在它出錯時將會被其監(jiān)督者自動重啟?!獋€被監(jiān)督進程不單要指明如何被啟動,還需要按照一定的方式來編寫。例如,它必須能夠在監(jiān)督者要求它終止時井然有序地終止。為了做到這一點,被監(jiān)督進程必須遵守所謂的"停止協(xié)議"(shutdownprotocol)。監(jiān)督者通過調(diào)用shutdown(P,How)來終止一個工作者進程,這里P是工作者的Pid,而How決定了工作者如何被停止。shutdown定義如下shutdown(Pid,brutal—kill)_>exit(Pid,kill);shutdown(Pid,infinity)_>exit(Pid,shutdown),receive{,EXIT,,Pid,shutdown}_>trueend;shutdown(Pid,Time)_>exit(Pid,shutdown),receive{,EXIT,,Pid,shutdown}->trueafterTime->exit(Pid,kill)end.如果How是brutal—kill,那么工作進程會被殺死(參見第3.5.6小節(jié))。如果How是infinity,那么一個shutdown的信號會被發(fā)送給工作者進程,而工作者進程應(yīng)當回以一條{'EXIT',Pid,shutdown}消息。如果How是一個整數(shù)T,那么工作者進程需要在給定的T毫秒事件內(nèi)終止,如果在T毫秒之內(nèi)沒有收到{'EXIT',Pid,shutdown}的消息,那么該進程會被無條件殺死。[1008]第15行的整數(shù)500是關(guān)停協(xié)議所需要的一個"關(guān)停時間"。著說明如果監(jiān)督者想要停止一個被監(jiān)督進程時,它被允許有最多500毫秒的時間來停止目前正在處理的事情。[1009]參數(shù)worker表示被監(jiān)督進程是一個工作者進程(在5.2節(jié)中我們說過一個被監(jiān)督者進程可以為一個工作者或監(jiān)督者進程),[packet—assembler]是本監(jiān)督者使用的所有模塊的列表(這個參數(shù)在同步代碼變更操作時要用到)?!┧械氖虑槎级x好了,我們就可以編譯運行該監(jiān)督者了。在接下來的的演示腳本中,我啟動了一個監(jiān)督者,并觸發(fā)了被監(jiān)督者中的幾個錯誤。被監(jiān)督者會死掉并被監(jiān)督者自動重啟。第一個例子是展示一下當包聚合器中發(fā)生一個錯誤時,會發(fā)生什么。我們啟動監(jiān)督者,并檢查一下包聚合器的Pid。50[1012:[1013:}okPacketassemblerstarting=ERRORREPORT====3-Jun_2007::12:38:07===**Statemachinemy_simple_packet_assemblerterminating氺禮asteventinwas〃oops〃氺稀henState==collecting**Data=={3,0,[]}林Reasonfortermination=**{if_clause,[{packet_assembler,collecting,2},{sodbs_fsm,handle_msg,7},{proc_lib,init_p,5}]}這個錯誤引起的打印相當多。首先是包聚合器崩潰了,看第一條錯誤輸出就知道。緊接著,監(jiān)督者檢測到了包聚合器崩潰的情況并重啟了它一該進程重啟的時候會打印"Packetassemblerstarting"消息。最后,有一條長長的、含有所期望的有用信息的出錯消息。該出錯消息包含了FSM在崩潰的時刻的狀態(tài)信息。它告訴我們,F(xiàn)SM當時所處的狀態(tài)是collecting,該狀態(tài)關(guān)聯(lián)的數(shù)據(jù)為一個3元組{3,0,[]},并且引起FSM崩潰的事件是"oops"。這些信息對于FSM的調(diào)試是相當有用的。在這里,錯誤日志被直接定向到了標準輸出。但是在實際產(chǎn)品系統(tǒng)中,錯誤日志被配置為定向到持久存儲設(shè)備并觸發(fā)報警裝置,報警裝置包括RS485接口的市售JCJ601型智能聲光報警器、標準市售串口GSMmodem(內(nèi)置中國移動sim卡),監(jiān)控程序定期(每秒鐘)檢查一次所有設(shè)備和進程,一旦出現(xiàn)任何問題,即通過串口發(fā)出告警信息,通過聲光報警器和GSMmodem發(fā)出聲光報警以及手機短信報警(內(nèi)含必要的診斷信息),在監(jiān)控室的監(jiān)控顯示器上也有相應(yīng)的出錯信息提示。對于系統(tǒng)可自行修復(fù)的警告性錯誤,只報警一次。對于系統(tǒng)無法自行修復(fù)的錯誤(如無法重啟報錯硬件)則每分鐘持續(xù)報警一次,直至技術(shù)人員排除故障為止。我們可以確認一下,監(jiān)督者已經(jīng)正確地重啟了包聚合器,求值一下whereis(my—simple_packet_assembler)就會返回新起來的包聚合器的Pid。1044]6>whereis(my_simple_packet_assembler).1045]〈0.40.0>1046]7>packet_assembler:send—header(6).1047]ok1048]8>packet_assembler:send—header(〃0know〃).1049]Gotdata:0know1050]ok1051]用類似的方法,我們可以觸發(fā)在Key-Value服務(wù)器中故意留下的那個錯誤1052]12>kv:store(a,1).1053]ack1054]13>kv:lookup(a).1055]{ok,l}1056]14>spawn(fun()_>kv:lookup(crash)end).1057]〈0.49.0>1058]K_Vserverterminating1059]Key-Valueserverstarting1060]15>1061]=ERRORREPORT====3-Jun-2007::12:54:10===1062]氺氺Genericserverkvterminating1063]氺氺Lastmessageinwas{lookup,crash}1064]氺氺WhenServerstate=={dict,l,1065]16,1066]16,1067]...manylinesremoved...1068]氺氺Reasonfortermination==1069]氺氺(badarith,[{kv,handle—call,3},{proc_lib,init—p,5}]}1070]15>kv:lookup(a).1071]error1072]請注意,kv:lookup(crash)必須通過一個沒有連接到shell進程(queryshell)的臨時進程來調(diào)用。這是因為監(jiān)督者是通過調(diào)用supervisor:start_link/4的方式來啟動的,所以監(jiān)督者被連接到了shell進程。在shell里直接調(diào)用kv:lookup(crash)會使監(jiān)督者進程也崩潰掉,這很可能不是我們所期望的。還請注意專用監(jiān)督者和預(yù)先定義的(pre-defined)behaviour是如何一起(together)工作的。專用監(jiān)督者與基本behaviour不是設(shè)計成各自孤立的,而是設(shè)計成相互補充的。還有,默認的做法是在錯誤日志中提供盡可能多的有用信息,并努力使系統(tǒng)處于一種安全的狀態(tài)。6.6專用應(yīng)用(Application)的原理我們迄今已經(jīng)構(gòu)建了三種基本behaviour,并把他們放進了一棵監(jiān)督樹中;剩下的事情就是把所有的東西都塞到一個應(yīng)用(application)里。—個應(yīng)用就是一個包含交付一個應(yīng)用程序時需要的一切事物容器。應(yīng)用的編寫方式跟先前討論的behaviour的編寫方式不一樣。之前的behaviour都要用到回調(diào)模塊,回調(diào)模塊導出一些預(yù)定義函數(shù)。應(yīng)用不使用回調(diào)函數(shù),而是表現(xiàn)為文件系統(tǒng)中的文件、目錄、子目錄的一種特殊的組織形式。一個應(yīng)用的最重要的部分包含在應(yīng)用描述子文件(a卯licationdescriptorfile)(—個擴展名為.即p的文件)中,該文件描述了一個應(yīng)用所需要的所有資源。[1080]6.6.1專用應(yīng)用的API應(yīng)用是用一個應(yīng)用描述子文件來描述的。一個應(yīng)用描述子文件的擴展名是.即p。在用戶手冊中,對于一個應(yīng)用的.app文件的結(jié)構(gòu)作了如下定義{application,Application,[{description,Description},{vsn,Vsn},{id,Id},{modules,[Modulel,..,ModuleN]},{maxT,MaxT},{registered,[Namel,..,NameN]},{applications,[Appll,,ApplN]},{included—applications,[Appll,..,ApplN]},[1098]{env,[{Pari,Vail},,{ParN,ValN}]},[1100]{mod,{Module,StartArgs}},[1102]{start—phases,[1103][{Phasel,PhaseArgsl},…,[1104](PhaseN,PhaseArgsN}]}]}.53[1105]應(yīng)用聯(lián)合清單(a卯licationassociationlist)中的所有鍵(key)都是可選的,如果被忽略,就會采用一個合理的默認值。1106]6.6.2專用應(yīng)用的例子1107]我們測試其中的一個服務(wù)器1108]l>application:start(simple,temporary).1109]Packetassemblerstarting1110]Key—Valueserverstarting1111]Loggerstarting1112]ok1113]2>packet_assembler:send—header(2).1114]ok1115]3>packet_assembler:send—data(〃hi").1116]ok1117]Gotdata:hi1118]現(xiàn)在我們可以停止該應(yīng)用1119]4>application:stop(simple).1120]=INFOREPORT====3-Jun_2007::14:33:26===1121]application:simple1122]exited:stopped1123]type:temporary1124]ok1125]在停止了應(yīng)用以后,應(yīng)用中運行著的所有進程都將依次關(guān)掉。1126]6.7系統(tǒng)與發(fā)布(release)1127]本章的鋪陳是"自底向上"的。我以簡單的東西開始,將它們組合成更大的更復(fù)雜的單元。我們是以幾個基本behaviour如sodbs_server、sodbs_event禾口sodbs_fsm開始的,然后把這些基本專用模式組織到了一個監(jiān)督層次體系中,然后把這個監(jiān)督層次體系構(gòu)建到了一個應(yīng)用包中。最后一步是將應(yīng)用包構(gòu)建到一個發(fā)布中。一個發(fā)布可以將多個不同的應(yīng)用打包成一個單一概念單元。結(jié)果就是可以移植到目標環(huán)境的少數(shù)幾個文件。構(gòu)建一個完整的發(fā)布是一個復(fù)雜的過程——一個發(fā)布不僅要描述系統(tǒng)的當前狀態(tài),而且還要知道系統(tǒng)的之前的版本。發(fā)布不但要包含軟件當前版本的信息,而且還要包含軟件的之前的發(fā)布的信息。特別地,發(fā)布應(yīng)該包含將系統(tǒng)從早先版本的軟件升級到當前版本的軟件的規(guī)程。這種升級通常需要在不停下系統(tǒng)的情況下進行。一個發(fā)布還必須能夠處理新軟件因某些原因出現(xiàn)安裝失敗的情況。如果一個新發(fā)布出錯,系統(tǒng)還應(yīng)該能夠回退到之前的某個穩(wěn)定狀態(tài)。所有的這些都由SODBS系統(tǒng)的發(fā)布管理組件來處理。[1131]6.7.l軟件升級我們做了一套自己的發(fā)布管理系統(tǒng),是對SODBS系統(tǒng)中的專用發(fā)布包的一個擴展。這是一個天生的分布式系統(tǒng)。我們希望分布式系統(tǒng)的軟件升級有一個"事務(wù)級"(transaction)的語義,即要么系統(tǒng)的所有節(jié)點整體進行軟件升級,要么升級失敗任何節(jié)點上的軟件都沒有改變。在本系統(tǒng)中,整個系統(tǒng)的軟件同時有兩個版本共存,一個老版本和一個新版本。增加一個新版本軟件的時候,當前版本就關(guān)成了老版本,而新添加的版本變成了新版本。[1134]為了做到這一點,所有的BMR軟件升級包都是以一種可逆(reversible)的方式編寫的。即不但可以將老版本軟件動態(tài)地升級到新版本,而且可以從一個新版本變回到老版本。升級所有節(jié)點的軟件是按四個步驟完成的。1.在第一階段,新版本的軟件被分發(fā)到每個節(jié)點——這通常會成功。2.在第二階段,所有節(jié)點上的軟件都從老版本變到新版本。如果有任何節(jié)點上的轉(zhuǎn)換失敗,則所有運行新版本軟件的節(jié)點都退回到運行老版本的軟件。3.在第三階段,系統(tǒng)中所有節(jié)點都運行著新版本的軟件,但是如果發(fā)生任何錯誤,則所有節(jié)點都回退去運行老版本的軟件。這時候系統(tǒng)尚未確認只運行新版本軟件。[1139]4.在新系統(tǒng)已經(jīng)成功運行了一段很長時間以后,操作人員可以"確認"(commit)軟件的改變。系統(tǒng)確認(即第四階段)將改變系統(tǒng)的行為。在確認以后如果再發(fā)生錯誤,那么系統(tǒng)將用新版本軟件重新啟動,而不是回退到老版本。該機制與NASA開發(fā)的深太空應(yīng)用系統(tǒng)X2000上的機制相仿,他們的軟件也需要在不停止系統(tǒng)運行的前提下實現(xiàn)升級。需要補充一下的是,本系統(tǒng)考慮到了在軟件正在進行升級的時候分布式系統(tǒng)中有節(jié)點處于"掉線"(out-of-service)狀態(tài)的情況。在這種情況下,當該節(jié)點重新加入到系統(tǒng)中時,它將學習一下它離線期間系統(tǒng)的變化,之后進行任何必要的軟件升級。[1142]6.7.2硬件在線更替與系統(tǒng)軟件升級相仿,硬件節(jié)點的增減也是在線進行的,并且通過軟件升級步驟來實現(xiàn)。每當系統(tǒng)操作人員向系統(tǒng)中增加或移除一個硬件節(jié)點時,都將提出一個新的軟件配置,系統(tǒng)將此新配置以軟件升級的形式發(fā)出,新的可用硬件資源將被所有軟件節(jié)點知悉并開始加以運用,將移除的硬件節(jié)點資源將不再有軟件去訪問它。當新的軟件配置生效后,系統(tǒng)將發(fā)出軟件系統(tǒng)升級完畢的通知,操作人員可據(jù)此確認新增硬件已投入運轉(zhuǎn),或關(guān)閉已在邏輯上移除的硬件節(jié)點并搬走它。[1144]6.8討論S0DBS系統(tǒng)中實現(xiàn)behaviour的專用模塊是有專家編寫的。這些模塊都是建立在多年的金融行業(yè)與技術(shù)經(jīng)驗的基礎(chǔ)上的,代表了編寫代碼來解決某些特殊問題的"最佳實踐"。使用S0DBS的behaviour來構(gòu)建的系統(tǒng)擁有非常有規(guī)則的結(jié)構(gòu),例如,所有的客戶-服務(wù)器和監(jiān)督樹都有著同樣的結(jié)構(gòu)。使用behaviour,就會迫使解決某一問題時采用公共的結(jié)構(gòu)。應(yīng)用程序員只需要提供定義他們的特殊問題的語義的代碼,而所有的基礎(chǔ)設(shè)施都由behaviour自動提供。對于加入已經(jīng)存在的團隊的一個新程序員來說,基于behaviour的解決問題的方式更容易理解。只要他們熟悉了behaviour,他們就能夠很輕易地識別出哪種情況下應(yīng)該用哪禾中behaviour。[1148]系統(tǒng)編程中大部分的"復(fù)雜問題"(tricky)都被隱蔽在了behaviour的實現(xiàn)中(這些復(fù)雜的問題實際上比我們這里描述的還要復(fù)雜得多)。如果你回頭看看客戶_服務(wù)器和事件處理器behaviour,你會發(fā)現(xiàn)所有處理并發(fā)、消息傳遞等等事務(wù)的代碼都被隔離在了behaviour的"專用"部分,而"問題相關(guān)"的代碼都是一些有著良好的類型定義的純順序化函數(shù)。這正是編程中人高度期望的一種境界——"困難"的并發(fā)程序被隔離成了系統(tǒng)中的一些定義良好的小的部分。系統(tǒng)中絕大部分代碼能夠用有著良好類型定義的順序化的程序來編寫。在我們的系統(tǒng)中,behaviour解決的都是正交的問題(orthogonalproblems)-例如,客戶-服務(wù)器與工作者-監(jiān)督者沒有任何關(guān)系。在構(gòu)建真實系統(tǒng)的時候,我們會挑選并混合使用behaviour,并把他們用不同的方式組合起來解決問題。為一個軟件設(shè)計者提供一個小的、混合的behaviour集有如下諸多好處它關(guān)注于一小組久經(jīng)考驗的技術(shù)。我們事先都都知道單個技術(shù)可以在實現(xiàn)中工作很好。如果對設(shè)計完全不加限制并且活動絕對自由,那么設(shè)計者就可能會受到誘惑制造出一些有著不必要的復(fù)雜性的東西,或者制造一些根本不能實現(xiàn)的東西。它允許設(shè)計者以一種精確的方式來構(gòu)造和討論設(shè)計。它提供了一個談?wù)摃r的共同詞匯。它完成了設(shè)計與實現(xiàn)之間的反饋環(huán)。這里所講的所有behaviour都有實用。[1155]7S0DBS介紹自組織分布式金融系統(tǒng)(Slef-Organized&DistributedBankingSystem)是為了構(gòu)建和運行安全、穩(wěn)健的金融系統(tǒng)而設(shè)計的一個軟件系統(tǒng)。SODBS系統(tǒng)是設(shè)計來運行在通常的操作系統(tǒng)之上的一個所謂的"中間件平臺"。SODBS的發(fā)布包含有如下一些部件1.SODBS的編譯器和開發(fā)工具。2.適應(yīng)多種不同目標環(huán)境的SODBS運行時系統(tǒng)。3.覆蓋廣泛的公共應(yīng)用的一些庫。4.實現(xiàn)公共行為模式的一組設(shè)計模式。5.用來學習如何使用該系統(tǒng)的一些教學資料。6.大量的文檔。SODBS已經(jīng)被移植到了許多不同的操作系統(tǒng)上,包括所有的Unix類的系統(tǒng)(SCOopenserver、Li皿x、FreeBSD、Solaris、0S-X…),大多數(shù)的Windows操作系統(tǒng)(Windows2000、Windows2003…)。SODBS運行時系統(tǒng)是一個用來運行由C的BEAM編譯器產(chǎn)生的中間碼(intermediatecode)的虛擬機。它同時也為SODBS編譯器產(chǎn)生的本地碼(nativecode)提供運行時支撐服務(wù)。SODBS運行時系統(tǒng)提供了許多傳統(tǒng)上由操作系統(tǒng)提供的服務(wù),所以,SODBS運行時系統(tǒng)遠不僅僅提供純序列化語言的運行時支撐,而比這要復(fù)雜得多。所有得SODBS進程都由SODBS運行時進程來管理——即使在一個SODBS運行時系統(tǒng)控制著數(shù)以萬計的SODBS進程的時候,宿主操作系統(tǒng)也只會感到只有一個進程在運行,那就是SODBS運行時系統(tǒng)本身。[1167]另一方面,與其他語言相比,SODBS的編譯器又是相當簡單的。編譯通常只是一個從S0DBS代碼到一條合適的虛擬機原語的一個簡單翻譯。所以,例如SODBS中的spawn原語被翻譯成虛擬機中的一條單獨的操作碼(opcode)(即spawn原語的實現(xiàn)),然后付出很大的努力使得操作碼的實現(xiàn)盡量的高效。[1168]7.1庫SODBS的發(fā)布包包含由一個很大的庫集,為了發(fā)布的目的,其中所有的庫都作為SODBS應(yīng)用的實例。例如發(fā)布包VI.0就包含如下這些應(yīng)用即pmon——一個監(jiān)控和操縱監(jiān)督樹的一個圖形化工具。asnl——個按照ASN.1定義的一個編譯器和運行時編/解碼支持包。compiler-SODBS的編譯器。crypto——一個用于加密/解密數(shù)據(jù)和計算消息摘要(messagedigests)的函數(shù)集。debugger——一個SODBS源代碼調(diào)試器。sodbs_interface——一個用于與分布式SODBS節(jié)點通信的庫文件集。[1176]erts——SODBS運行時系統(tǒng)。et——一個事件跟蹤器和一些記錄事件數(shù)據(jù)并進行圖形化表示的工具。eva——負責"事件與告警"處理的應(yīng)用。gs——一個圖形系統(tǒng),一組用于構(gòu)建GUI的圖形函數(shù)。ic-SODBS的IDL編譯器inets-—個HTTP服務(wù)器和一個FTP客戶。jinterface——一個編寫Java與SODBS的接口的工具。kernel——系統(tǒng)得以運行所需要的兩個基本庫之一(另一個是stdlib)。本庫包含文件服務(wù)器、代碼服務(wù)器的實現(xiàn)。megaco-支持Megaco2/H248協(xié)議的庫集。m固osyne-—種用在sodbs_db上的數(shù)據(jù)庫查詢語言。sodbsdb——一個具有SODBS的軟實時特性的DBMS(數(shù)據(jù)庫管理系統(tǒng))。[1188]observer——一個用于跟蹤和觀測分布式系統(tǒng)的行為的工具集。[1189]odbc——一個用于SODBS訪問SQL數(shù)據(jù)庫的ODBC接口。orber——一個CORBA對象請求代理的SODBS實現(xiàn)。注意還有其它一些單獨的應(yīng)用,來提供對不同CORBA服務(wù)(如事件、通知、文件傳輸?shù)?的訪問。[1191]os_mon——個監(jiān)控外部操作系統(tǒng)的資源使用情況的工具。parsetool——解析SODBS的工具。包括yecc,即LALR(l)解析器生成器(parsergenerator)。pman——個查看系統(tǒng)狀態(tài)的圖形化工具。pman可以用來查看本地或遠端的SODBS節(jié)點。runtime_tools——運行時系統(tǒng)所需要的各種小函數(shù)。sasl-"SystemArchitectureSupportLibraries,,(系統(tǒng)結(jié)構(gòu)支持庫)的縮寫。本應(yīng)用包含對告警處理(alarmhandling)和發(fā)布管理(managingreleases)的支持。[1196]s,-簡單網(wǎng)絡(luò)管理協(xié)議(SimpleNetworkManagementProtocol)的SODBS實現(xiàn)。本應(yīng)用包含一個MIB編譯器和一些MIB編寫的工具。ssl-—個SODBS的安全套接字層(securesocketslayer)接口。stdlib——系統(tǒng)得以運行的"必備的"SODBS庫集。另一個必備的庫集是kernel。[1199]toolbar——一個可以從中開啟應(yīng)用的圖形化工具條。tools——一個由各種用于分析和監(jiān)測SODBS程序的獨立應(yīng)用組成的包。這些應(yīng)用即一些性能評估(profiling)、覆蓋率分析(coverageanalysis)、交叉引用分析(crossreferenceanalysis)的工具。tv——一個"表瀏覽器"(tableviewer)。本表瀏覽器是一個可以對sodbs_db數(shù)據(jù)庫種的表進行圖形化瀏覽的圖形化應(yīng)用。webtool——一個用于管理基于網(wǎng)頁的工具(如inets)的系統(tǒng)。[1203]postool——一個用于管理Pos機的工具系統(tǒng)。[1204]e-bank——一個用于電子支付的系統(tǒng)。SODBS庫集提供了一個高度成熟的工具集,然而,SODBS庫集是相當龐雜的。[1206]8案例研究文檔的本部分將展示一個系統(tǒng)的研究案例,這些系統(tǒng)均是用SODBS平臺部署的。這個系統(tǒng)是北京資和信擔保有限公司的S0DBS-1系統(tǒng)——S0DBS-1系統(tǒng)是一個大容量的金融支付系統(tǒng)。S0DBS-1廣泛應(yīng)用了SODBS庫集,因此它為SODBS庫集的功能性提供了一個很好的證明。8.1方法學在案例研究中,我所關(guān)注的是如下方面問題領(lǐng)域——問題領(lǐng)域是什么?這個問題屬于SODBS的設(shè)計所要解決的問題范圍嗎?代碼的量化特性——寫了多少行代碼?一共有多少個模塊?這些代碼是如何組織的?程序員都遵循了設(shè)計規(guī)范嗎?定下設(shè)計規(guī)范有用處嗎?哪些是好的?哪些是不好的?可容錯性的證據(jù)——系統(tǒng)可容錯嗎?SODBS的初衷就是為了構(gòu)建可容錯的系統(tǒng)。有證據(jù)證明確實發(fā)生了運行時錯誤并被成功地糾正了嗎?一個編程錯誤發(fā)生的時候產(chǎn)生的信息對于后續(xù)的程序糾正是不是足夠充分?系統(tǒng)的可理解性——系統(tǒng)的可理解性如何?便于維護嗎?我不是問一些關(guān)于系統(tǒng)屬性的籠統(tǒng)的問題,而是去尋找了一些明確的證據(jù),來證明系統(tǒng)確實在按照我們期望的方式運行。特別是1.是否有證據(jù)證明系統(tǒng)確實曾因為編程錯誤而崩潰過,并且該錯誤被糾正了,并且系統(tǒng)能夠從該錯誤中恢復(fù)過來,并且在錯誤被糾正后能以一種讓人滿意的方式運行?[1216]2.是否有證據(jù)證明系統(tǒng)已經(jīng)運行了很長時間,并且期間已經(jīng)發(fā)生過軟件錯誤而系統(tǒng)依然穩(wěn)固?3.是否有證據(jù)證明系統(tǒng)的代碼曾經(jīng)"在運行中"(onthefly)升級。4.是否有證據(jù)證明像垃圾回收這些機制起了作用(也就是我們已經(jīng)長時間運行了垃圾回收系統(tǒng)而沒有發(fā)生垃圾回收的錯誤)?5.是否有證據(jù)證明錯誤日志中的信息對于出錯后的錯誤定位很有意義?58[1220]6.是否有證據(jù)證明系統(tǒng)的所有代碼都以某種方式組織了起來,從而大多數(shù)的程序員不必關(guān)心系統(tǒng)中使用的并發(fā)模式的細節(jié)?7.是否有證據(jù)證明監(jiān)督樹如期所望地工作著?8.代碼是否按照"凈/臟"風格來組織?上面的第1、2、5條的存在是因為我們希望檢測我們關(guān)于編寫可容錯系統(tǒng)的思想在實踐中起到了作用。第4條檢驗的是對于必須長時間運行的系統(tǒng)來說,垃圾回收確實起了作用。[1225]第6條是對S0DBS的behaviour的抽象能力的一個衡量。有太多的原因使我們希望能夠"抽象出"很多情形下普遍存在的并發(fā)處理的細節(jié)。SODBS的behaviour集就是嘗試這么做。對于一個初涉我們程序的程序員來說,他在多大程度上忽略了并發(fā)處理是度量SODBS的behaviour是否適合開發(fā)系統(tǒng)軟件的一個重要的指標。我們可以通過觀察程序員在他們的代碼中多么頻繁地使用顯式消息傳遞和進程操作原語來評估并發(fā)處理可以被忽略的程度。第7條檢驗了監(jiān)督者策略是否如期望般地起效。第8條檢驗了我們是否可以按照附錄B中給出的編程規(guī)范來編程。特別是它的指導方針強調(diào)了按照"凈/臟"方式來組織系統(tǒng)的重要性。這里我們說的"凈"代碼是指沒有副作用的代碼,這樣的代碼比"臟"代碼更容易理解,而"臟"代碼是指有副作用的代碼。我們的整個系統(tǒng)跟硬件操作是息息相關(guān)的,而這種硬件操作就會帶來副作用。因此,我們關(guān)心的不是可否避免副作用,而是我們能夠在多大程度上把副作用限制在盡量少的模塊中。與其讓有副作用的代碼均勻地散布在整個系統(tǒng)中,不如希望能夠把大量的副作用限制在少數(shù)"臟"模塊中,而大多數(shù)模塊都以無副作用的方式編寫,與"臟"模塊組合起來成為整個系統(tǒng)。對代碼進行一下分析就可以揭示這種組織方式是否可行。當然"反例"也很重要。我們想知道我們的范型不適用的所有情形,以及這種不適用是否是一個大問題。8.2S0DBS-1S0DBS-1系統(tǒng)是北京資和信擔保有限公司試運行的一套應(yīng)用實例。整個系統(tǒng)由許多可伸縮的模塊組成——每個模塊提供10000在線用戶的支付操作容量,那么16個模塊加起來聯(lián)在一起就能形成一套支持160000人在線的金融支付系統(tǒng)。S0DBS-1是為支持"運營商級"不停機運轉(zhuǎn)而設(shè)計的。該系統(tǒng)由重復(fù)的硬件來提供硬件冗余,并且硬件可以在不打斷業(yè)務(wù)的情況下添加到系統(tǒng)中或從系統(tǒng)中移除。軟件必須要能夠應(yīng)付硬件和軟件故障。因為系統(tǒng)是為不停機運轉(zhuǎn)而設(shè)計的,所以它必須能夠在不干擾系統(tǒng)流量的前提下進行軟件修改。[1234]8.3軟件的量化特性下面展示了一個對SODBS軟件的簡單統(tǒng)計的分析結(jié)果。這份簡單統(tǒng)計顯示了該系統(tǒng)在2008年6月5日當時的狀態(tài)。這份分析報告只關(guān)注于系統(tǒng)的SODBS代碼的一些量化特性。系統(tǒng)的總體量化特性如下總的SODBS模塊的數(shù)量[1238]22481239]"凈"模塊數(shù)1240]14721241]"臟"模塊數(shù)1242]7761243]代碼行數(shù)1244]11361501245]總的S0DBS函數(shù)個數(shù)1246]574121247]"凈"函數(shù)的個數(shù)1248]533221249]"臟"函數(shù)的個數(shù)1250]40901251]"臟"函數(shù)個數(shù)/代碼行數(shù)之比率1252]0.359%1253]上表中只是粗淺地對每個模塊或函數(shù)是"凈"是"臟"進行區(qū)分而做的一個簡單的分析。如果一個模塊中有任何一個函數(shù)是"臟"的,我們就認為它是"臟"的,否則就認為它是"凈"的。為了簡化處理,如果一個函數(shù)進行了接收或發(fā)送數(shù)據(jù),或者調(diào)用了如下一些SODBS的BIF,我們就說它是臟的apply,cancel_timer,check_process_code,delete—module,[1255]demonitor,disconnect_node,erase,group_leader,halt,link,[1256]load—module,monitor_node,open_port,port_close,port_command,[1257]portcontrol,process_flag,processes,purge—module,put,register,[1258]registered,resume_process,send_nosuspend,spawn,spawn_link,[1259]spawn_opt,suspend_process,system—flag,trace,trace_info,[1260]trace_pattern,皿link,皿register,yield.之所以這樣區(qū)分,是因為調(diào)用了這些BIF的代碼段都會有潛在的危險性。請注意我們在這里給"臟模塊"下了一個特別簡化的定義。直覺上似乎應(yīng)該給"臟模塊"一個遞歸地定義,即如果一個模塊中有任何函數(shù)調(diào)用了"危險的"BIF或另一個模塊中的"臟函數(shù)",則該模塊為"臟模塊"。不幸的是,如果按照這個定義來判定,那么系統(tǒng)中幾乎所有的模塊都將被判為"臟模塊"。原因在于,如果你統(tǒng)計一下對某個模塊導出的所有函數(shù)的調(diào)用的傳遞閉包(transitiveclosure),你就會發(fā)現(xiàn)這個傳遞閉包實際上囊括了系統(tǒng)的幾乎所有模塊。這個傳遞閉包之所以這么大,應(yīng)歸咎于C語言庫中許多模塊都發(fā)生了"泄漏"(leakage)。[1263]我們簡單地認為,所有的模塊都寫得很好且經(jīng)過了測試,并且如果一個模塊確實包含有副作用,那么在編寫該模塊時也會注意這種副作用不會泄漏出該模塊,從而對調(diào)用該模塊的代碼造成影響。按照這種定義,65%的模塊就都是"凈模塊"。由于只要模塊中含有一個"臟函數(shù)"該模塊就被視為"臟模塊",那么看看"凈函數(shù)'V"臟函數(shù)"的比率或許更有意思。只要函數(shù)中發(fā)生了一次對不清白的BIF的調(diào)用,該函數(shù)就被視為"臟函數(shù)"。在函數(shù)級,我們可以看到6092%的函數(shù)都是以無副作用的方式編寫的。還應(yīng)注意,在所有代碼中,總共包含有3067個"臟函數(shù)",也就是每1000行代碼包含的臟函數(shù)個數(shù)不超過4個。從結(jié)果看,臟函數(shù)的分布情況既有值得褒獎的地方,也有亟待改進的地方。好消息是95%的臟函數(shù)都出現(xiàn)在極少數(shù)的1%的模塊中,壞消息是有大量的模塊中都包含有極少量的臟函數(shù)。例如,有200個模塊中只有l(wèi)個臟函數(shù),有156個模塊中包含2個臟函數(shù),等等。這些數(shù)據(jù)中有一點很有意思,就是我們并沒有系統(tǒng)性地為達到代碼的"干凈程度"而付出努力。因此這種編程的"原始風貌"正好迎合了一種編程風格,即少數(shù)模塊包含大量的副作用,而大量模塊包含極少的副作用。[1268]臟函數(shù)的分布SODBS編程規(guī)范也積極支持這種編程風格,意圖就是要讓更有經(jīng)驗的程序員編寫和測試那些包含副作用的代碼。基于對SODBS的代碼的觀察,我們有一個主意,那就是明確地定義哪些模塊是允許包含副作用的,作為一種質(zhì)量控制的強制要求。如果我們再深入一點看看對會引入副作用的原語的使用的次數(shù),我們會發(fā)現(xiàn)如下一個順序put(1743),apply(1532),send(1345),receive(634),erase(235),[1272]process—flag(262),spawn(304),unlink(132),register(172),[1273]spawn_link(134),link(121),證egister(27),open_port(16),[1274]demonitor(13),processes(ll),yield(lO),halt(8),registered(9),[1275]spawn—opt(5),port—command(4),trace(3),cancel_timer(3),[1276]monitor—node(2).用得最廣泛的原語就是put,一共被使用了1743次。從這個使用統(tǒng)計數(shù)據(jù)我們可以看出,我們的S0DBS原語"黑名單"上的許多原語從來就沒有被用到過。使用得最廣泛的會帶來有副作用的原語就是put——而是否真的會產(chǎn)生副作用則取決于對它的使用方法。其中一種廣泛的用法就是用put來斷言一個用于調(diào)試目的的進程的一個全局特性,而這種用法基本上是安全的,雖然沒有自動化的分析程序能夠證明這一事實。真正有危險的副作用是那些改變應(yīng)用程序的并發(fā)結(jié)構(gòu)的原語,因此那些使用了link、unlink、spawn、spawn_link等原語的模塊要仔細地檢查一下。更危險的代碼是調(diào)用了halt或processes原語的代碼——我確信這樣的代碼一定被非常小心地檢查過了。[1281]8.3.l系統(tǒng)結(jié)構(gòu)S0DBS的代碼是用S0DBS監(jiān)督樹來組織的,因此可以根據(jù)這些樹的形狀推斷出S0DBS的整個代碼組織結(jié)構(gòu)為一棵大的監(jiān)督樹。這棵監(jiān)督樹的內(nèi)部節(jié)點本身是一些監(jiān)督者節(jié)點,而葉子節(jié)點則都是S0DBS的behaviour的實例或?qū)iT的應(yīng)用相關(guān)的進程。[1283]S0DBS系統(tǒng)的監(jiān)督樹有141個節(jié)點,使用了S0DBS的behaviour的191個實例。每種behaviour的實例的個數(shù)如下sodbs—server(162),sodbs—event(56),supervisor(30),sodbs_fsm(17),[1285]application(8).可見sodbs_server的使用最多,一共有162個專用服務(wù)器的實例,而sodbs_event的實例個數(shù)居于其次。有意思的一點是,實際上所需要的共同行為是相當少的。[1287]客戶-服務(wù)器抽象(即sodbs—server)是如此有用,以至于整個系統(tǒng)中的一般對象有63%是客戶-服務(wù)器行為的實例。在SODBS庫集中,一個監(jiān)督者通過調(diào)用它所監(jiān)督的進程的所謂chilcLspec(子進程說明)信息中的一個函數(shù)來開啟一個應(yīng)用。"子進程說明"(childspecification)中包含有許多信息,其中{Mod,F皿c,Args}元組就使用來標明如何開啟一個被監(jiān)督的進程的。開啟一個被監(jiān)督進程的方法是完全專用的,因為監(jiān)督者可以根據(jù)任意一個函數(shù)開啟一個被監(jiān)督者。在SODBS的案例中,這種方法的專用性并沒有完全體現(xiàn)出來,而是在所有的監(jiān)督層次結(jié)構(gòu)中只用了三種開啟被監(jiān)督進程的方法中的一種。在這三種方法中,有一種完全占據(jù)統(tǒng)治地位,應(yīng)用到了除3棵監(jiān)督樹中的其他所有監(jiān)督樹中。SODBS的架構(gòu)師定義了一個主監(jiān)督者,它可以用許多標準化的方式進行參數(shù)化。SODBS的所有監(jiān)督者也被打包成為一個常見的SODBS應(yīng)用,它們的行為在一個所謂的.即p文件中進行描述。分析一下SODBS的所有即p文件,可以給我們一個關(guān)于SODBS的軟件靜態(tài)結(jié)構(gòu)的一個很好的總體認識。SODBS的軟件一共有172個.即p文件。這172個文件展示了11棵獨立的監(jiān)督樹。這些監(jiān)督樹中的大多數(shù)是非常平的,并沒有很復(fù)雜的結(jié)構(gòu)。展示這種結(jié)構(gòu)的一個簡單的方法就是用簡單的ASCII碼顯示來畫出該結(jié)構(gòu)的樹型圖。例如,這里就是上面所說的11棵頂層樹中的處理"Standby"業(yè)務(wù)的一棵樹的樹型圖。1292]__chStandbyStandbytopapplication1293]__stbAtsStandbypartsofATSSubsystem1294]__aini_sbProtocolterminationofAINI,...1295]__cc_sbCallControl.Standbyrole1296]__iisp_sbProtocolterminationofIISP...1297]__mdisp_sbMessageDispatcher,standbyrole1298]__pch_sbPermanentConnectionHandling...1299]__pnni_sbProtocolterminationofPNNI...1300]__reh_sbStandbyapplicationforREH1301]__saal_sbSAAL,standbyapplication.1302]__sbm_sbStandbyManager,startstandbyrole1303]__spvc_sbSoftPermanentConnection...1304]__uni_sbProtocolterminationofUNI,...1305]__stbSwsStandby即plications-SWS1306]__cecpSbCircuitEmulationforS0DBS,...1307]__cnh_sbConnectionHandlingonstandbynode1308]—tecSbTone&EchoforS0DBS,SWS,.1309]正如我們看到的一樣,這棵樹的結(jié)構(gòu)是相當簡單的,平而淺。樹中只有2個一級子節(jié)點,而它們下面的監(jiān)督者的結(jié)構(gòu)完全是扁平的。[1310]需要注意的是,這樣顯示的數(shù)據(jù)只是顯示了監(jiān)督者節(jié)點的組織。作為樹中葉子節(jié)點的子節(jié)點的實際的進程并沒有顯示出來,監(jiān)督類型(即"與"型監(jiān)督還是"或"型監(jiān)督)也沒有顯示出來。為什么把樹組織得扁平而不是多層?其原因反映了從實踐中獲取的一條經(jīng)驗,簡單來說,就是"多層的(cascading)重啟經(jīng)常失效"。SODBS的首席軟件架構(gòu)師關(guān)羽發(fā)現(xiàn),用同樣的參數(shù)去重啟一個出錯的進程經(jīng)常是可以的,但是當這個簡單的重啟過程失敗后,多層的重啟(即重啟它的上一層)卻往往不奏效。很有意思,我們觀察到了大多數(shù)的硬件故障都是瞬時的,可以通過重新初始化硬件的狀態(tài)然后重試操作來糾正。我們于是猜測這一點對于軟件來說也是一樣我猜想軟件中也存在類似的現(xiàn)象——很多產(chǎn)品故障都是很微妙的。如果程序狀態(tài)被重新初始化一下,然后重試先前失敗的操作,這個操作在第二次就往往不再失敗了。此文檔中提出的出錯處理模型的一般化方法——即在發(fā)生故障時采用一個盡量簡單的策略——只是被部分采用了。之所以是部分采用,與其說是有意設(shè)計,不如說是偶然湊成——S0DBS庫集本身提供的與文件系統(tǒng)的接口和以及與socket這樣系統(tǒng)級服務(wù)的接口在編寫之初就十分注意當發(fā)生故障的時候要保護系統(tǒng)的完整性。所以,如果一個文件或socket的控制進程因任何原因而終止時,這個文件或socket就會被自動關(guān)閉掉。由SODBS庫服務(wù)提供的保護級別將自動提供"更簡單的服務(wù)級別"(simplerlevelofservice),這也正是我們的容錯處理模型的用意。8.4討論在分布式系統(tǒng)中,甚至在我們的談?wù)撝?,關(guān)于故障的理解是需要修正的。我們再談?wù)撜麄€系統(tǒng)的故障已經(jīng)沒有什么意義了(因為這種事情極少發(fā)生了)——我們應(yīng)該談?wù)摰氖菍τ诜?wù)質(zhì)量降低的衡量。我們的案例研究中的軟件系統(tǒng)是如此可靠,以至于操作這些系統(tǒng)的人傾向于認為這些系統(tǒng)沒有錯誤。但事實不是這樣的,軟件錯誤在運行時確實發(fā)生過,但是這些錯誤很快就被糾正過來了,所以沒有人曾經(jīng)注意到錯誤的發(fā)生。為了得到關(guān)于長時間穩(wěn)定性的準確的統(tǒng)計數(shù)據(jù),我們就必須記錄下系統(tǒng)啟動和停止的次數(shù),以此來作為衡量系統(tǒng)的"健康度"的參數(shù)。如果在系統(tǒng)級沒有收集到任何這樣的統(tǒng)計數(shù)據(jù),就說明系統(tǒng)的表現(xiàn)是完全"健康"。[1320]絕大部分代碼都是干凈的,但是臟代碼的分布不是一個真正的"階梯"函數(shù)(st印f皿ction)(也就是沒有清晰的分界線來區(qū)分"這部分代碼不好,要小心對待"以及"這部分代碼是好的")而是一個發(fā)散的分布,即有少數(shù)的模塊有許多副作用(我們不擔心這一部分),而更讓人擔憂的則是那些數(shù)量眾多的僅含有一兩個帶副作用原語的模塊。由于對于代碼沒有一個更深入的理解,我在這里也無法斷定這就是問題的根源,也不能說這些具有潛在副作用的調(diào)用給系統(tǒng)帶來了問題,或者這些調(diào)用是有害的。無論如何,我想表達的東西是清楚的。光靠編程規(guī)范并不足以讓我們的系統(tǒng)都以一種特別的方式來編寫。如果有人希望把代碼強制性地分為干凈的代碼和骯臟的代碼,那么就一定要輔以工具的支持,還要有一些強制執(zhí)行的政策的支持。是否真的要這么做是有爭議的——也有可能最好的方式是允許程序員們違反編程規(guī)范,只是希望他們在違反編程規(guī)范的時候知道正在做什么。[1325]9API與協(xié)議在我們編寫一個軟件模塊的時候,我們需要描述如何使用它。有一種做法就是為模塊所有的導出函數(shù)定義一套編程語言API。為了做到這一點,我們可以用3.9節(jié)提到過的類型系統(tǒng)。定義API的方法其實是很普遍的。不同的語言之間,類型符號的細節(jié)有所不同,不同的系統(tǒng)之間,底層的語言實現(xiàn)對于類型系統(tǒng)的要求的強制性程度也不一樣。如果對類型系統(tǒng)有嚴格的強制要求,那么這種語言就被稱為是"強類型的"(stronglytyped),否則它就被稱為"弱類型的"(untyped)——這一點經(jīng)常會引起混淆,因為許多要求進行類型聲明的語言它的類型系統(tǒng)是很容易被違反的。1328]SODBS不要求類型聲明,但是是"類型安全"(typesafe)的,意思是不能以一種會破壞系統(tǒng)的方式違反底層類型系統(tǒng)。1329]即使我們的語言不是強類型的,但是類型聲明可以作為一種有價值的文檔,而且可以作為一個動態(tài)類型檢查器的輸入,動態(tài)類型檢查器能夠用來進行運行時類型檢查。1330]不幸的是,只按照慣常的方式寫出API的對于理解程序的行為是不夠的。1331]例如,看下面的代碼片斷1332]silly()_>1333]{ok,H}=file:open(〃foo.dat〃,read),1334]file:close(H),1335]file:read—line(H).1336]按照類型系統(tǒng)的要求和3.9節(jié)的例子中給出的API,這段程序是完全合法的。1337]但是它明顯是完全沒有意義的,因為我們不可能期望從一個已經(jīng)關(guān)閉了的文件中讀取東西。1338]為了改正上面的問題,我們可以添加一個額外的狀態(tài)參數(shù)。輔以一種相當明了的守號,關(guān)于文件操作的API可以這樣寫1339]+typestartxfile:open(fileName(),read|write)_〉1340]{ok,fileHandle(Mxready1341]I{error,string()}xstop.1342]+typereadyxfile:read_line(fileHandle())_〉1343]{ok,string()}xready1344]|eofxatEof.1345]+typeatEof|readyxfile:close(fileHandle())_〉1346]truexstop.1347]+typeatEof|readyxfile:rewind(fileHandle())-〉1348]truexready1349]這禾中API模型用了四種狀態(tài)變量start,ready,atEof禾卩stop。狀態(tài)start表示文件還沒有被打開。狀態(tài)ready表示文件已經(jīng)準備好被讀取,atEof表示到了文件的結(jié)尾。文件操作總是以start狀態(tài)開始,而以stop狀態(tài)終止?,F(xiàn)在API就可以這么解釋了,例如,當文件處于狀態(tài)ready是,進行file:reacLline的函數(shù)操作是合法的。它要么返回一個字符串,這時候它仍然處于ready狀態(tài);或者它返回eof,此時它處于atEof狀態(tài)。在atEof狀態(tài)的時候,我們可以關(guān)閉文件或回倒(rewind)文件,所有其他的操作都是非法的。如果我們選擇回倒文件,那么文件將重新回到ready狀態(tài),這時候read—line操作就又變得合法了。為API增加了狀態(tài)信息,就為我們提供了一種判定一系列操作是否與模塊的的設(shè)計相吻合的方法。9.l協(xié)議可見我們可以標定一套API的使用順序,其實同樣的思想也可以應(yīng)用到協(xié)議的定義上。假設(shè)有兩個部件使用純消息傳遞的方式進行通信,我們要能夠在某一個抽象層次說明一下這兩個部件之間流動的消息的協(xié)議。兩個部件A和B之間的協(xié)議P可以用一個非確定的有限狀態(tài)機(non_deterministicfinitestatemachine)來描述。假設(shè)進程B是一個文件服務(wù)器,而A是一個要使用這個文件服務(wù)器的客戶程序,進一步假設(shè)會話是面向連接的。那么文件服務(wù)器應(yīng)當遵循的協(xié)議可以按如下方式來說明+statestartx{open,fileName(),read|write}_>{ok,fileHandle())xreadyI{error,string0}xstop.+statereadyx{read_line,fileHandle()}_>{ok,string()}xreadyIeofxatEof.+stateready|atEofx{close,fileHandle()}_>[1366]truexstop.+stateready|atEofx{rewind,fileHandle())_>[1368]truexready這個協(xié)議描述的意思是,如果文件服務(wù)器處于start狀態(tài),那么它就可以接收{(diào)open,filen謙(),read|write}這種類型的消息,文件服務(wù)器的響應(yīng)要么是返回一個{ok,fileHandle()}類型的消息,并遷移到ready狀態(tài),要么是返回一個{error,string()}的消息,并遷移到stop狀態(tài)。如果一個協(xié)議用類似上面的方式來描述,那么就可能寫一個簡單的"協(xié)議檢查"程序,置于進行協(xié)議通信的兩個進程中間。圖5就展示了在進程X和Y之間放一個協(xié)議檢查器C的情形。圖5:兩個進程和一個協(xié)議檢查器當X向Y發(fā)送一個消息Q(Q是一個詢問)時,Y會以一個響應(yīng)R和一個新狀態(tài)S作為回應(yīng)。值對{R,S}就可以用協(xié)議描述中的規(guī)則進行類型檢查了。協(xié)議檢查器C位于X和Y之間,根據(jù)協(xié)議描述對X和Y之間來往的所有消息進行檢查。為了檢查協(xié)議規(guī)則,檢查器就需要訪問服務(wù)器的狀態(tài),這是因為協(xié)議描述可能還65有如下的條目1373]+stateSnxTl_>T2xS2|T2xS31374]在這種情況下,只觀察返回消息T2的類型并不足以區(qū)分服務(wù)器的下一個狀態(tài)是S2還是S3。1375]如果我們回憶一下前述的簡單的專用服務(wù)器的例子,我們程序的主循環(huán)就可以是這樣的1376]1377]1378]1379]1380]1381]1382]1383]1384]1385]1386]1387]1388]1389]1390]1391]loop(State,Fun)_>receive(R印lyTo,R印lyAs,Q}_>{Reply,Statel}=F皿(State,Q),Reply!(R印lyAs,Reply},loop(Statel,F(xiàn)un)end.這個主循環(huán)又可以很容易地改成loop(State,S,F(xiàn)un)_〉receive(R印lyTo,R印lyAs,Q}_〉(R印ly,Statel,SI}=Fun(State,S,Q),R印ly!OteplyAs,Sl,R印lyl,loop(Statel,Sl,F(xiàn)un)end.這里S和S1代表協(xié)議描述中的狀態(tài)變量。注意接口(即協(xié)議描述中使用的狀態(tài)變量的值)的狀態(tài)與服務(wù)器的狀態(tài)State是不同的。1392]進行了上面的修改后,專用服務(wù)器就徹底變成了一種允許安裝在客戶和服務(wù)器之間的動態(tài)協(xié)議檢查器了。1393]9.2API還是協(xié)議?1394]前面我們展示了如何用兩種本質(zhì)上相同的方式來做同樣的事情。我們可以在我們的編程語言上強加一個類型系統(tǒng),或者我們可以在用消息傳遞方式通信的兩個部件之間強加一個契約檢查機制。這兩個方法中,我更喜歡契約檢查器這種方法。第一個方面的原因跟我們系統(tǒng)的組織方式有關(guān)系。在我們的編程模型中,我們采用了獨立部件和純消息傳遞的方式。每個部件被當作是"黑盒子",從黑盒子的外面,完全看不到里面的計算是怎么進行的。唯一重要的事情就是黑盒子的行為是否遵循他的協(xié)議描述。在黑盒子的內(nèi)部,可能因為效率或其他編程方面的原因,我們需要使用一些晦澀的編程方法,甚至違背所有的常識規(guī)則和好的編程實踐。但是只要系統(tǒng)的外部行為遵守了協(xié)議描述,就沒有絲毫關(guān)系。只要簡單的擴展,協(xié)議說明就可以擴展成系統(tǒng)的非功能屬性。例如,我們可以向我們的協(xié)議描述語言中添加一個時間的概念,那么我們就可以這樣來表達[1398]+typeSix{operationl,Argl}_>[1399]valuel()xSjwithinTl[1400]|value2()xSkafterT2意思是operationl應(yīng)該在Tl時間內(nèi)返回一個valuel()類型的數(shù)據(jù)結(jié)構(gòu),或在T2時間后返回一個value2()類型的數(shù)據(jù)結(jié)構(gòu)。第二個方面的原因跟我們所做的工作在系統(tǒng)中的位置有關(guān)。在一個部件的外面放置一個契約檢查器決不干涉到該部件本身的構(gòu)造,并且還給我們的系統(tǒng)增加或刪除各種自我測試手段提供了一種靈活的途徑。使得我們的系統(tǒng)可以進行運行時檢查,并以能以更多的方式進行配置。9.3交互部件系統(tǒng)S0DBS系統(tǒng)如何與外界通信呢?——當我們想要構(gòu)建一個分布式系統(tǒng),而S0DBS只是許多交互部件中的一個時,這個問題就變得很有意思了。我們看到任何PLITS系統(tǒng)都是建立在模塊(module)和消息(message)這兩種基本構(gòu)件之上的。模塊是一種自包含(self-contained)的實體,就如同Simula或Smalltalk中的類、SAIL進程、CLU模塊一樣。模塊本身用什么編程語言來編碼并不重要,我們希望做到不同的機器上的不同模塊完全可以用不同的語言來編寫。為了做一個交互部件系統(tǒng),我們必須使得不同部件在許多方面達成一致,我們需要—種傳輸格式,和一種從語言實體到傳輸格式的映射方法。[1407]—套類型系統(tǒng),它建立在傳輸格式的實體之上。[1408]—種基于類型系統(tǒng)的議描述語言。關(guān)于這種傳輸格式,我們采用了一種叫做UBF(UniversalBinaryFormat的縮寫)的方法,這種方法方法是為快速解析而設(shè)計的。9.4討論我還是想回歸此文檔的核心問題——我們?nèi)绾卧诔霈F(xiàn)錯誤的時候構(gòu)建一個可靠的系統(tǒng)?跳出這個系統(tǒng),把它看作是一組相互通信的黑盒子,是一種有益的思路。[1412]如果我們可以形式化地描述兩個黑盒子之間通信通道所遵循的協(xié)議,那么我們就可以利用這一點,作為一種檢測和識別錯誤的手段。我們還可以準確地說出哪個部件出了錯。這種架構(gòu)恰好滿足2.5節(jié)的需求Rl-R4,因此按照我們的推理,這種架構(gòu)是可以用在編寫可容錯系統(tǒng)上的。這一點與第5.1節(jié)的"試圖做較容易做到的事"的說法也是相符的。如果一個黑盒子函數(shù)的實現(xiàn)發(fā)生故障,那么我們可以切換到黑盒子函數(shù)的另一個較簡單的實現(xiàn)。那么協(xié)議檢查機制就可以用在元層(meta-level)來決定該使用哪一個實現(xiàn),一旦發(fā)生錯誤,就選擇一個較簡單的實現(xiàn)。當部件處于物理上獨立的不同機器上時,做到強隔離也就是自然的要求了。[1415]10總結(jié)此文檔中所描述的工作,以及開發(fā)人員做的相關(guān)工作,已經(jīng)證明了許多不同尋常的東西,即SODBS及其相關(guān)技術(shù)是有效的,這是很有啟迪意義的一個結(jié)果。曾有人認為,像S0DBS這樣的系統(tǒng)是不可能全面勝任企業(yè)級金融應(yīng)用的。然而在北京資和信擔保有限公司諸多產(chǎn)品上成功地展示了SODBS對于這類產(chǎn)品來說是一種很合適的系統(tǒng)。不但這些產(chǎn)品成功了,而且在它們各自的激烈競爭的市場中成為領(lǐng)頭羊,這一事實非常有意義。應(yīng)用改進型Beowulf式(無固定的控制節(jié)點)服務(wù)器集群配合強隔離、輕量化進程和無共享內(nèi)存的系統(tǒng)在實踐中是確實可行的,這種系統(tǒng)自我檢測、自我修復(fù)能力強,而且可以用來設(shè)計復(fù)雜的大容量企業(yè)級金融系統(tǒng)。構(gòu)建在出現(xiàn)軟件和硬件錯誤的時候作出合理的行為的系統(tǒng)目標已經(jīng)達成了。68權(quán)利要求自組織分布式金融系統(tǒng)可用于持久處理pos機支付以及網(wǎng)上支付業(yè)務(wù),其特征是采用了分布式處理以及自組織的體系結(jié)構(gòu),達到了使系統(tǒng)高度穩(wěn)定、持久在線的效果。2.根據(jù)權(quán)利要求1所述的自組織分布式金融系統(tǒng),其特征是服務(wù)器硬件節(jié)點進行網(wǎng)狀連接,沒有特定的關(guān)鍵節(jié)點,實現(xiàn)了即使部分服務(wù)器硬件或軟件發(fā)生故障也不致引起系統(tǒng)整體癱瘓。3.根據(jù)權(quán)利要求1所述的自組織分布式金融系統(tǒng),其特征是系統(tǒng)的軟、硬件故障可以由系統(tǒng)自身自動檢測出來,并作自動智能處理(如重啟或初始化出錯節(jié)點),同時以聲光、手機短信等方式報警。4.根據(jù)權(quán)利要求1所述的自組織分布式金融系統(tǒng),其特征是軟件系統(tǒng)各進程間實行強隔離,不共享內(nèi)存,使得部分軟件模塊的錯誤不會擴散到系統(tǒng)中,保持整個系統(tǒng)的健壯性和穩(wěn)定性。5.根據(jù)權(quán)利要求1所述的自組織分布式金融系統(tǒng),其特征是硬件節(jié)點的增減及系統(tǒng)軟件升級或重新配置均可在線進行,無需重啟或暫停系統(tǒng)。全文摘要本發(fā)明涉及一種自組織分布式金融業(yè)務(wù)系統(tǒng),縮寫為SODBS。涉及領(lǐng)域為金融行業(yè)pos機支付和網(wǎng)上電子支付的業(yè)務(wù)處理。金融企業(yè)的業(yè)務(wù)系統(tǒng)比較復(fù)雜,大的維護(比如遇有設(shè)備故障檢測或軟件升級等情形)一般需要離線處理。本發(fā)明的目的是在保障基本金融業(yè)務(wù)功能的前提下使系統(tǒng)可在線維護。本發(fā)明采用跨平臺分布式處理方式,實現(xiàn)了即使部分軟件或硬件發(fā)生故障也不致引起系統(tǒng)整體癱瘓的情況發(fā)生。使用自組織的體系結(jié)構(gòu),軟、硬件故障可以自動檢測出來,對其作自動智能處理(如重啟或初始化出錯節(jié)點),同時以聲光、手機短信等方式報警。達到了軟、硬件故障可以自動檢測并做適當自動修復(fù),硬件節(jié)點的增減無需停機,軟件升級也無需重啟或暫停服務(wù)的有益效果。客戶將享受到持久在線的高質(zhì)量金融服務(wù)。文檔編號G06Q20/00GK101727629SQ20081022393公開日2010年6月9日申請日期2008年10月10日優(yōu)先權(quán)日2008年10月10日發(fā)明者不公告發(fā)明人申請人:北京資和信擔保有限公司
網(wǎng)友詢問留言 已有0條留言
  • 還沒有人留言評論。精彩留言會獲得點贊!
1
宕昌县| 拜泉县| 眉山市| 东乡族自治县| 民乐县| 靖安县| 久治县| 甘孜县| 永和县| 黔南| 双峰县| 阳信县| 泸溪县| 肃宁县| 阿荣旗| 东乌珠穆沁旗| 师宗县| 宁强县| 塘沽区| 公主岭市| 九龙坡区| 白沙| 镇赉县| 明水县| 思南县| 孙吴县| 五华县| 香河县| 馆陶县| 宣威市| 佛冈县| 徐汇区| 遂昌县| 图们市| 密山市| 阜南县| 密云县| 彭阳县| 安岳县| 定襄县| 衡南县|