智能服務契約帶來的巨大伸縮性

智能服務契約帶來的巨大伸縮性

作者 Udi Dahan 譯者 黃璜 發佈於 2008年5月13日 下午8時15分

社區
Architecture,
SOA
主題
ESB,
消息傳送,
性能和可伸縮性

那是2005年6月的一個晴天,看着爲之奮鬥兩年的新訂單系統在生產環境上線,我們精神無比振奮。我們的合作伙伴開始發送訂單,監視系統也告訴我們一切工作正常。一個小時之後,我們的COO給戰略合作伙伴發了一封郵件,告訴他們可以將訂單發送到新系統了。五分鐘之後,一臺服務器宕掉了,一分鐘後,又有兩臺服務器癱瘓。客戶開始打電話過來,那時我們明白,我們將有一段時間見不着太陽了。

“最佳實踐” 遠遠不夠

儘管我們設計系統時翻閱了衆多供應商所提供的最佳實踐文檔,使用了無狀態的請求處理邏輯、分層結構、分層部署、分離OLTP 和OLAP服務器,但是從沒有人告訴我們這個系統將要應對不同類型的伸縮性問題。在2003年,我們設計了和系統效率有關的關鍵部分。在2004年,我們經受住了負載和壓力測試的考驗。因此,我們都信心滿滿地以爲我們將各方面都覆蓋到了。

通過篩選審查服務器日誌和監視系統的事件,我們發現來自戰略伙伴的訂單和一般合作伙伴的訂單有着很大的不同。一般合作伙伴一次訂購幾百件物品,戰略伙伴一次發來的訂單卻有成千上萬行。請求的數據量甚至可以達到數百兆字節。我們的消息基礎設施和對象/關係映射代碼都從未承受過如此負載的測試。服務器核心爲了反序列化所有這些XML數據承受了前所未有的考驗,處理單個請求就能消耗掉半G內存。數據庫鎖的佔用時間達到了幾分鐘而不是毫秒級。當線程超時後,垃圾回收機制開始瘋狂地回收內存,這更加損害了系統的可用性。

我們做的第一件事就是在性能測試實驗室再現現實中的場景。每當我們一遍一遍的測試而系統一次又一次的崩潰時,我們面面相覷,都不敢相信。我不斷告訴自己:“我們做了書上說的每一件事,怎麼會是這樣?”

事實上,這是我工作中遇到的第一家真正給你時間和預算讓你一切都照着書本去做的公司。我們沒有任何的藉口。可當書本遠遠不夠解決問題時,你能做些什麼呢?

不同類型的伸縮性

最後發現,每秒的請求數僅僅是可伸縮性的一方面而已。我們經歷痛苦找到的其它方面還包括:

  1. 消息的大小
  2. 每個請求的CPU利用率
  3. 每個請求的內存利用率
  4. 每個請求的IO(和網絡)利用率
  5. 每個請求的總處理時間

消息的大小看似對其它的各個方面都有很大的影響。當消息增大時,它會佔用更多的CPU時間來反序列化,消耗更多的內存來保存結果數據,更多的網絡帶寬和IO來進行數據庫讀寫操作,所有這些加起來就會影響總的處理時間。然而,即使是像給一個合作伙伴的所有待處理訂單打折這樣的小請求,也會因所處理數據量不同而受到影響。

我們檢查了所有的東西,沒有一個可以把問題解決。除非我們使大消息變小,問題始終會存在。這是我們對話的片斷:

Dan: “二進制序列化或許對更少數量的戰略伙伴有用。”

Barry: “不好,他們之間總共有五種互不兼容的平臺。”

Sasha: “而且那也不會對內存和IO有多大的幫助。”

Me: “試試壓縮怎麼樣?那樣會減輕消息底層的負載。”

Dan: “那樣會使CPU的負擔更重。”

Sasha: “還要我再說一次內存和IO嗎?”

Barry: “請求/響應好像在這裏並不管用。”

Me: “你知道我多喜歡發佈/訂閱,但我也看不出來在這裏怎麼用得上。”

可是當我們深入探究消息模式的核心時,我們偶然發現瞭解決方案。

真實世界是面向消息的

最讓我們驚奇的是解決方案對於一般的合作伙伴和戰略合作伙伴都適用,而且都顯著提高了兩者的性能。不僅如此,它還加快了訂單的週轉時間從而提升了存貨管理的能力。這是連我們自己都沒有想到的。

事實上,解決方案相當直接——與之前的一條“創建訂單信息”不同,合作伙伴可以隨着時間動態地發送給我們多條“訂單信息”,關鍵字是:(合作伙伴id,採購訂單編號)。當該採購訂單編號的所有條目完成後,他們可以發來一個“完成”標誌爲真的“訂單信息”。這是有狀態的交互。

你知道,合作伙伴幾乎總有一個採購部門來發出訂單。這些訂單是隨着時間逐步添加,直到最後“完成”併發送給我們。我們的解決方案使合作伙伴的採購系統在生成訂單的同時發送給我們那些部分、非完的訂單信息。他們可以修改已發出的訂單信息或者取消掉訂單的某部分,無需瞭解我們系統中的訂單號(它由一個現有的ERP來管理)。事實上,在我們收到表示訂單已完成的信息之前,我們根本不會去調用ERP來處理訂單。

當我們收到“訂單信息”時,我們會返回一個“訂單狀態已改變”消息。如果他們系統在他們認定的合理時間段內沒有收到響應,他們可以再發一次之前的消息。換句話說,我們要保證消息是冪等的。這意味着,如果合作伙伴想對產品SKU(Stock Keeping Unit,庫存單元)作任何更改,都必須重新發送該SKU的所有行(包含各種各樣的選項和配置)——實際上沒有多大的數據。

冪等消息指的是這樣一種消息,無論其被系統處理多少次,效果也跟被系統處理一次一樣。

這給性能帶來了極大的影響——我們不再需要爲了使消息不丟失而對其進行持久化。不再總是向磁盤寫大量消息,我們的應用協議使合作伙伴的系統爲我們管理交互狀態——只需在他們的系統中稍稍增加一些複雜性。

模式變化帶來的伸縮性影響

在我們和戰略伙伴在新“版本”(沒人敢說系統重寫了)訂單系統上進行的合作過程中,我們發現“訂單消息”的預期大小在幾千行條目的數量級——和來自一般合作伙伴的訂單差不多。隨着消息大小減小,我們也看到了其它各方面的伸縮性得以提升——CPU、IO、還有內存利用率都相應下降。

一旦每個請求的資源利用率得以下降,延遲也得以明顯下降,但是吞吐率卻比我們預期的更高。這是因爲巨型消息“擾亂了”更小的請求使用的資源。隨着時間增加,數據庫連接池被處理大消息的線程佔據,效果上形成了對那些小消息服務線程的“拒絕服務”,導致這些線程超時。

系統中,仍有其它方面對我們造成影響,靠擴容數據和減小消息大小也無法解決。比如說,爲待處理的合作伙伴訂單打折,象這樣類似的請求必須尋求對象/關係設計之外的解決辦法加以實現。那種請求處理邏輯更易用基於集合的邏輯表達——SQL。與把所有數據讀進內存,循環並改變它,最後再把所有東西存回數據庫的方式不同,可以使用以下這樣的一個簡單SQL語句:

UPDATE PendingOrders SET Discount=@Discount WHERE PartnerId = @PartnerId

效果是驚人的——更快的響應時間和更好的穩定性。

顯式狀態管理分類

新舊版本系統設計之間最引人注意的區別就是消息處理邏輯是“有狀態的”,這正是所有供應商所警告的。Martin Fowler的一些文章使我們明白在數據庫中保存狀態並不會神奇的使得系統獲得伸縮性——這隻使得數據庫成爲了瓶頸,而讓數據庫供應商賣出了更多的許可證。我們的新設計顯式地處理了多消息處理的狀態;我們用“saga”來描述邏輯與狀態的結合。顯式地狀態管理使我們能選擇最合適的狀態存儲技術。

對於一般合作伙伴,我們要高速地讀寫許多小對象,但是這些對象的生命週期相對比較短,所以我們決定用內存中的、分佈式緩存產品以保證狀態的高可用性。對於戰略合作伙伴,狀態可能達到幾百兆字節,而且會按幾周甚至幾月爲週期緩慢地演變。我們最後使用了數據庫,但是它是映射到直接存取設備的開源數據庫,而不是使用SAN的OLTP數據庫。不用XML存儲的訂單數據在給我們能進行二進制序列化好處的同時又不用損失互操作性。

Saga —— 好處與挑戰

由於新消息契約使合作伙伴給我們發送很多帶有同一個採購訂單編號的消息,因此我們系統需要能區分哪些是針對已存在的訂單處理saga,哪些需要創建新的 saga。於是,我們需要一種按客戶id和採購訂單編號來查詢saga持久化機制的方法。這一需求使得一些流行的分佈式緩存技術出局,因爲它們只允許通過 id查詢,但是一些高端解決方案剛好滿足我們的需要。

“saga”這個術語於1987年由關係數據庫社區創造,用於描述一種處理長生命週期事務的風格。Saga放棄全局的原子性和隔離性,將事務過程處理爲多重的、小粒度的ACID事務的序列。

儘管一開始我們團隊裏有人對轉換到一種新的編程模式有些許不安,但他們很快就看到這與常規消息處理實質上是一樣的。當消息進來,它的數據被用於從存儲容器中查詢一些對象(saga),對象的方法被調用,對象的一些狀態被改變了,一些消息被髮出,最後對象再被存進存儲容器。唯一的區別在於saga所管理的不是存儲在主數據庫或者ERP系統裏的數據,而是系統交互時產生的臨時數據。當saga收到“完成”標記爲真的訂單消息時,它會調用ERP系統讀取所有它所積累的數據,併發消息告訴合作伙伴的系統它們的訂單狀態已由“收到(received)”改爲“接收(accept)”。

在開發過程中,我們逐漸意識到,在我們收到“訂單消息”時,除了合作伙伴系統需要收到迴應以外,公司內還有其它系統也有有興趣瞭解這個訂單,甚至在其完成之前。因此我們開始發佈“訂單狀態改變消息”給那些有興趣知道這些消息的系統。

發佈/訂閱提高了企業範圍的效率

第一個感興趣的部門是訂單執行部——特別是遇到那些“突擊任務”時。公司近來面臨的一大挑戰就是向提出緊急任務的客戶提供更好的服務,而問題最終歸於訂單的執行。你知道,即使能夠按時將所有產品準備好,但是準備數量剛好而又合適的交通工具幾乎從來不可能。一些產品需要被冷藏,一些需要泡沫包裹。現在你已經明白了我的意思,僅僅在手邊配備足夠的人手來處理事情都是個問題。

既然執行系統預先知道了訂單的出貨日期和需要哪些產品,他們可以提前作出計劃以配備足夠的人手,足夠的冷藏車,以及按時完成任務所需要的一切。

緊隨執行系統的是庫存管理——知道將接收訂單的具體產品使得他們可以提前準備好庫存,並能在需要時敦促供應商提前送貨。

負載測試的結果

當我們用之前失敗的測試所紀錄的消息來測試新系統的時候,我們發現了一些很有意思的事情。

首先,在我們第一次運行測試的時候,我們的測試工程師錯誤地設置了消息基礎設施使得消息可以被亂序處理。儘管我們對性能表現很滿意,但我們還是把日誌和數據檢查了個遍,看看到底是不是真的正確了。即使我們知道冪等消息可以被處理任意多次,但我們之前從未想過處理的次序問題。爲了確保萬無一失,我們又進行了一輪代碼審查,專門針對於消息的次序。最後,我們又做了更多的功能測試,直到我們確信可以放鬆次序約束條件。

其次,我們發現響應時間隨着一段時間的測試慢慢地下降,即使這些測試只是一遍又一遍的重複同樣的測試用例。當注意到後面的測試用例的CPU利用率跟之前的一樣之後,我們把關注的焦點轉向了數據庫的鎖機制。當我們中有人發現saga表中的記錄超過百萬行時,問題應刃而解。當時我們面面相覷,有人低聲咕噥:“我們有沒有說當saga完成後要把它們刪掉呢?”,這正是原因。稍加修改了saga持久化機制之後,響應時間平滑多了。

合作伙伴的觀點

當然,爲了使用新系統,合作方的系統也需要做一些修改。你可以想像,在我們的設計得以通過之前,很多合作伙伴都進行了技術層面和COO級別的諮詢。我們的一般合作伙伴對作出改變顯得有一點苦惱,但最終促成了“創建訂單消息”到“訂單消息”的轉變並保持了下去。

當戰略伙伴的技術團隊看過我們的示例代碼後,我們看到他們臉上流露出欣喜的表情。“你不會以爲我們喜歡發那些龐大的消息吧?這個方案同樣將會節省我們的內存和CPU-Bound的序列化。”他們甚至願意打開防火牆的一個端口來獲取訂單在我們公司所經歷的各個階段的狀態。

部屬到生產環境就顯得有一點麻煩了。當我們引入新系統的時候,合作伙伴必須使得新舊兩套系統並行上線工作。這可以理解,我們的COO在整個過程中一直處於緊張不安的狀態,然而,除了一些小問題,整個過程相當成功。

我們一直觀察之前的那些健康指標——CPU、內存、IO、數據鎖,足足幾個小時,大氣都不敢出。接下來的日子裏運營組和開發組的人員都一直監視這些統計數據以期找到末日將至的跡象,但什麼也沒有。響應時間比我們之前的觀察快了兩倍,三個月之後舊系統開始了退役的過程。

從2005年的夏天以來,已過去了兩年了。我們頭上的烏雲最終散去了。COO實際上並不在乎系統技術上那些令人驚奇的能力,他太過於專注從我們戰略伙伴那裏所帶來的利潤增長了。

經驗教訓

這個項目對現在的很多最佳實踐來說是一次真正考驗。我們盲目地寄希望於供應商的產品和技術,卻砸了自己的腳。儘管一直都緊記性能和伸縮性,但是我們從未考慮到大數據量對系統的深層次影響。以我們全部加起來的聰明頭腦,也只參透了“不成熟的優化是一切錯誤的源泉”這個真理的一部分。完整的闡述是:

“我們應該忘記那些小的效率,大概百分之九十七的時候都是這樣:不成熟的優化是一切錯誤的源泉。”
           ——Tony Hoare爵士

我們這裏沒有“小的效率”,也沒有隨之而來的重新設計(比如“優化”)。通過改變我們的服務契約並引入有狀態的交互,使得我們能夠管理那些和系統性能息息相關的狀態。我們從未想到能有這種級別的控制權,通過選擇特定的技術不僅促成了一個高性能系統,也獲得了一個有成本效益的解決方案。

我最大的收穫是:可伸縮性不是是或不是這樣一個簡單的問題。除了併發用戶數量和在線服務器的數量之外,可伸縮性還是一個多維的成本函數。對於某個響應時間的需求,峯值和均值請求的比率,消息大小,格式,每個請求的內存工作集大小,每個請求的CPU/IO利用率,一個解決方案需要花多大的代價?對於戰略伙伴有意義的技術選型對一般合作伙伴又不具成本效益。始終站在業務的角度來得出結論——當他們發現成本(前端和進行中)蓋過產生的回報的時候他們就可能改變性能需求。

參考閱讀

關於作者

Udi Dahan是軟件簡化主義者,獲得了微軟公司頒發的令人羨慕的解決方案架構最有價值專家大獎,該活動已運作了三年。Udi是一個互聯技術顧問,專注於微軟的WCF,WF,Oslo等技術。他爲全世界的客戶提供培訓,指導以及高端架構諮詢服務,特別是面向服務的,可伸展的安全的.NET架構設計。

Udi是國際.NET協會歐洲宣講局的成員,國際軟件架構組織的作者、培訓師以及其架構培訓委員會的創始人。同時他也是Dr.Dobb所贊助的web服務,SOA&XML專家。

Udi曾在諸如TechEd USA,SD Best Practices,DevTeach Canada,Prio Germany,Oredev Sweden,TechEd Barcelona,QCon London,以及TechEd Israel等一系列國際會議上發表演講,其主題覆蓋了持久性服務,持久化域模型,多線程偶爾互聯的客戶端等深入而任務關鍵的領域。

可以通過Udi的主頁www.UdiDahan.com來聯繫他。

查看英文原文Spectacular Scalability with Smart Service Contracts

譯者簡介:黃璜,畢業於重慶郵電大學計算機學院。現從事軟件開發工作,供職於成都ISSC,主要負責Java Web開發,熟悉struts,spring,ibatis,關注語義網,SOA,雲計算等領域。個人主頁:http://www.chinacomputing.org, 聯繫方式[email protected]。參與InfoQ中文站內容建設,請郵件至[email protected]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章