程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 事務:構建處理故障的可擴展系統,防止丟失數據

事務:構建處理故障的可擴展系統,防止丟失數據

編輯:關於.NET

本文將介紹以下內容:

穩定的消息傳送

事務和系統一致性

處理錯誤隊列問題

消息大小和計時

本文使用以下技術:

WCF、MSMQ

設計分布式系統一直都是個挑 戰。有了功能日益強大的 CLR、高效的 Visual Studio® 以及框架中建立的細粒度控制(如 Windows® Communication Foundation (WCF)),開發人員便擁有了構建可擴展系統所需的全部工具 。遺憾的是,這些還不夠。

在處理大型分布式系統時,我的團隊發現開發可處理故障且不丟失數據的可靠系統遠不是那麼簡單。 這並不是工具集失效,而是需要以極特殊的方式來使用這些工具才能實現既可擴展又可靠的系統。

HTTP 和消息丟失

團隊面對的第一個難題就是消息丟失。剛開始設計我們的系統時,我們 已決定使用 HTTP 傳輸基於 WCF 的服務。作為消息處理的一部分,我們的服務會經常向數據庫中寫入數 據。這裡沒有譴責的意思,但很多系統都是這樣設計的。

在系統進入配置環境之前,已經過了非 常嚴格的壓力測試 – 系統在繁重負荷下運行了一周時間。壓力測試的結果表明我們的系統發生了 消息丟失。

在分析事因的過程中,我們確定丟失的主要是那些包含訂單信息的消息 - 這並不令人 驚訝,因為系統中的大部分負荷都與訂單信息有關。但讓我們驚奇的是所有這些消息丟失都是在一個 10 分鐘的時間間隔內發生的。此後,系統又正常運行了。

在研究了多方面的日志文件後,我們發現 ,這一周中發布了一個關鍵的 Windows 補丁,而配置實驗室中的服務器自己在自動安裝了該補丁後重新 啟動。考慮到這個新發現蘊藏的信息 – 服務器會不時重新啟動 – 我們意識到不能忽略這一 點。系統將需要連續運行數年並需要經受住多次服務器升級和重新啟動的考驗。

通過 HTTP 處理 消息時,服務會打開一個針對數據庫的事務,嘗試寫入數據,然後提交該事務。正常情況下,此操作會成 功執行。如果消息處理服務器重新啟動了中間事務,數據庫會檢測到該事務超時,並回滾其更改,從而保 持其狀態一致。但當服務器再次啟動後,原始消息的數據既不會出現在內存中也不會出現在服務器的網絡 處理堆棧中 – 該消息將會丟失。

對於訂單處理系統,這就意味著經濟損失。任何人都很難 接受在空中交通控制系統中出現這樣噩夢般的情形 - “飛機 A 與飛機 B 正處於碰撞航向” 這一事件丟失。

穩定的消息傳送

決定從 HTTP 轉用穩定傳輸後,我們不得不在 Microsoft® Message Queue (MSMQ) 和 SQL Server® Service Broker 之間進行選擇。Service Broker 的優點是消息傳送一直在數據庫中進行,避免了消息傳送層與數據庫之間出現分布式事務,從而 可能提高性能。MSMQ 的優點是其可直接在 Windows 上使用(沒有任何開銷),可與 WCF 緊密集成以及 能夠在服務器之間進行對等通信而無需再加重數據庫的負荷。最終,我們決定在每台服務器上使用專用的 MSMQ 隊列。

請注意,使用 MSMQ 並不會使每條消息都自動寫入到磁盤中 – 這一點對性能 開銷而言倒是個好消息。為確保消息的穩定性,在使用 MSMQ 時,直接將 Message 類的 Recoverable 屬 性設置為 true 或使用 NetMsmqBinding,因為默認情況下其 Durable 屬性已設置為 true。

如在 整套解決方案中都使用 NetMsmqBinding,應確保在單獨的端點上傳輸不需要穩定傳送的消息。將這些端 點配置為非事務隊列,同時將 <netMsmqBinding> 中的 Durable 屬性設置為 false。

為了 使隊列在發生故障時回滾該消息,必須在一個事務內執行“接收”操作。將 OperationBehaviorAttribute 的 TransactionScopeRequired 屬性與排隊的傳輸配合使用即可輕松地完 成此操作,用於出列消息的事務被用來處理該消息。

系統一致性

對消息進行處理後,服務 將回復一條或多條消息並向其他系統發送任意數量的其他消息。由於可通過消息傳送在服務中的所有位置 任意交流代碼,使其具備了極高的價值。但利用 HTTP、TCP 或任何其他不識別事務的技術執行此操作是 很危險的。

由於數據庫死鎖可能會造成處理消息相關的事務發生回滾,因此一旦事務發生回滾, 我們發送的其他系統信息可能是不真實的。以圖 1 中所示方案為例。在數據庫中,如果多個線程同時處 理同一表中的數據就會發生死鎖。一個線程成功鎖定了表 1 並試圖鎖定表 2。與此同時,另一個線程成 功鎖定了表 2 後又試圖鎖定表 1。數據庫檢測到此死鎖後會選擇犧牲其中一個線程,終止其事務。

圖 1 事務遇到數據庫死鎖

這裡的問題是我們已通知系統 A 對表 1(已發生回滾)中的數據進行了更改 。這可能隨後導致系統 A 更改其內部數據並進一步傳播該誤報,最終導致全局沖突。

在訂單處理 中,我們觀察到的行為如下

采購訂單到達。

“采購訂單”表更新(狀態已接收 )。

倉庫系統通告有關未決訂單的信息。

訪問客戶關系管理 (CRM) 數據庫以更新客戶的 值。

檢測到 CRM 數據庫死鎖。

事務回滾。

重試該事務時,客戶數據表明沒有與該 客戶完成任何交易,因為其未履行付款義務。因此,訂單系統的狀態與 CRM 系統保持一致。

遺憾 的是,倉庫系統未得到有關其他數據庫回滾的通知 – 它會是什麼狀況呢?這導致從冷藏裝置中取 出產品並在裝卸台上等了一個多小時,直至某個人調用並檢查訂單的執行狀況。該產品不能在此時售出, 補充存貨所花費的時間將導致若干其他訂單延後幾個小時交付。

單這一個事故就會使該周的利潤 降低 30%,並讓這些令人沮喪的技術問題卷入了核心業務熱點。如轉為使用 MSMQ,這些問題會迎刃而解 ,且無需我們做任何開發工作,接下來我將解釋這一點。

事務性消息傳送

當使用 MSMQ 向 隊列發送消息時,如方法調用返回,則該消息將無法發送。這與 HTTP 和其他相關技術表現出來的行為截 然不同。使用 MSMQ 時,在消息發送之前,它會本地存儲在同一計算機上的外發隊列中。在事務的上下文 中發送消息時,只有提交事務後才能發布消息,這樣 MSMQ 才能實際地將其發送出去。

在上一示 例中,當訂單系統向倉庫系統發出一條消息通知其新訂單的相關信息時,該消息沒有立刻發送。只有當整 個消息處理事務提交後,MSMQ 才能向倉庫系統發送該消息。當數據庫發生死鎖或由於其他原因導致事務 中止時,發送給倉庫系統的消息會從 MSMQ 外發隊列中刪除。換言之,為防止系統出現全局沖突,我們需 要事務性消息傳送。

請注意,事務性消息傳送並不意味著消息的發送者和處理該消息的服務共享 一個事務。事實是一個消息到達處理服務中即表示發送者成功提交了一個事務。但這一事實並不代表接收 服務處理該消息時發生的行為。

有時,在消息到達服務後,對其進行處理時會產生異常。這種異 常有多種可能的根本原因,因而了解這些原因之間的差異以及如何處理它們非常重要。在處理消息的過程 中,有時某個異常僅發生一兩次;而有時服務每次嘗試處理該消息都會失敗。

屬於第二組的消息 稱為中毒消息,需要特殊處理。隨著我對各種根本原因的分析,您將看到一種能有效處理所有問題的簡單 解決方案。

瞬態條件

您已看到,格式正確的消息可能會由於數據庫死鎖或達到了數據庫連 接池的處理極限而導致異常。這些案例的共同之處是異常均由瞬態環境條件所致。

由於事務性消 息傳送中的默認行為是只要在事務中發生異常就全部回滾,因此原始消息將返回隊列中。此時,會有一個 線程(有可能是剛剛發生異常的那個線程)再次對其進行處理。由於回滾事務會花費一些時間,因此很有 可能之前的環境條件現在發生了改變,使得事務性消息處理可成功完成。

有一個消息傳送失敗案 例相當簡單,以致於常被人忽略,那就是服務所使用的數據庫不可用,因為並非所有數據庫的可用性都能 達到萬無一失。從使用數據庫的代碼的角度來看,這個問題是非瞬態的 – 我們並不知道該數據庫 將有多長時間無法使用。簡單地回滾消息並重新嘗試很有可能導致相同的異常。

該消息自身可能 是完全有效的,可能包含極具價值的數據,如您可能不希望丟失的百萬美元的采購訂單。使消息停留在處 理-回滾循環中是保存消息的一種方法,但這樣會引起其他問題。

假設服務邏輯使用多個數據庫或 使用了多個表。一個數據庫可能可用而其他的不可用。備份一個表的數據文件可能可用,而備份其他表的 文件可能位於某個已關閉的遠程服務器上。現在,假設處理 A 類消息僅需要表 A,而處理 B 類消息僅需 要表 B。那麼當表 B(或數據庫 B)不可用時會出現什麼情況?

將成功處理 A 類消息,但 B 類 消息將繼續回滾。此過程持續到服務輸入隊列中的 B 類消息數量多於線程處理消息數量為止。那時,A 類消息也無法使用該服務,直至所有資源在線復原。換言之,該服務的資源利用率最低,從系統可用性的 角度來看,這是一個令人不安的(高開銷)事務。

在消息處理連續失敗多次後,服務最智能的做 法是將該消息從輸入隊列移動到其他隊列中 – 我們暫時先稱它為錯誤隊列,它與其他隊列本質上 沒有任何不同之處。在這種情況下,如果一個資源離線,則 B 類消息將在回滾 n 次後移動到錯誤隊列中 ,而 A 類消息將繼續進行處理,直至成功。盡管資源離線可能會損害服務的延遲和吞吐量,但該服務將 繼續可用。

反序列化錯誤

當很多字節到達某個端點時,該服務的基礎技術嘗試將這些字節 轉換為正規的對象 – 消息。這一反序列化過程可能因多種原因導致失敗。例如 XML 通知該技術創 建的數據類型尚未在該端點部署。或部署了錯誤的數據類型版本。另一種更常見的情況是現有的客戶端向 某個最近已升級的服務端點發送消息,而新版本的數據類型與之前的版本不兼容。

問題在於盡管 該服務可能無法理解向其發送的消息,但該消息中可能會有重要的數據,您不想將其丟棄。也不想讓它阻 塞該服務的輸入隊列。

這種情況下的解決方案與數據庫不可用時所使用的解決方案極為相似:您 只需將消息移動到其他隊列中即可(甚至可以是與剛才相同的錯誤隊列)。細微的差別在於該情況中處理 此消息的結果是肯定的 – 它始終都會導致異常。在這種情況下,您無需等到該消息回滾 n 次即可 將其移動到錯誤隊列中。反序列化失敗的消息應直接移動到錯誤隊列中。

錯誤隊列中的消息

從之前介紹的一些案例中您可以看到,經常會有各種消息被標識為中毒消息。其中的某些消息可 能確實是需要丟棄的垃圾,但這必須由操作人員檢查這些消息,做出最後決定。

發生反序列化異 常時,操作員會將消息路由到運行上一版本軟件的端點,或可能將該消息重新格式化為新版本並將其返回 到輸入隊列中。

這樣操作員就很可能會了解到數據庫離線的消息,但如果單個數據文件的錯誤只 影響了幾個表,錯誤隊列中的消息以及相應的日志文件會標記出這些隱藏問題,以便快速地識別並解決它 們。

從中毒消息這一主題中我們可以了解到服務對向其發送的消息作出響應所花費的時間可能會 相當長。實際上,時間問題成為設計基於消息的系統中主要關注的問題,有時,遲來的響應就等於根本沒 來。

時間和消息丟失

您可能認為通過穩定消息傳送和事務性消息傳送可完全去除消息丟失 ,而現實始終要比想像更為復雜。

以企業對企業方案為例,訂單系統與外部的運輸公司進行交流 以負責向客戶發送訂單。對於我們接受的每個訂單,都需要向運輸系統發送一條消息。為防止由於任何系 統關閉導致消息丟失,我們使用穩定消息傳送以及事務性消息處理來傳送訂單信息

在我們分析該 案例的細節時,我們發現消息丟失不僅不可避免而且開銷驚人。如果每條發送的訂單的消息大小平均計為 1MB,我們的訂單系統每秒鐘可處理 10 個訂單(負擔一點也不沉重),運輸服務器不可用時,這些消息 將一直存儲在外發隊列中。I/O 使用率為每秒 10MB(或每分鐘 600MB)。

在通信中斷三小時後發 現此情況,此時系統將嘗試向三碟 RAID 5 陣列的 36GB SCSI 磁盤(有部分空間)改寫 100GB 的數據。 當服務器無法向其硬盤驅動器中繼續寫入時就會失去穩定性,不僅如此,還幾乎無法備份它們。

盡管三個小時的訂單積壓是該問題的來源,但不是我們在此刻應關注的問題。事實是我們在花費兩個 小時備份系統的過程中丟失了總額數百萬美元的訂單 - 這足以造成嚴峻的管理壓力,要求解決這個當前 問題並防止其再次發生。我們獲得的教訓就是穩定消息傳送並不是萬靈丹,它也會有相關的開銷。

為保持穩定性,我們的服務器需要丟棄尚未成功發送到其目標的消息難點在於確定哪些消息應保留以 及應保留多長時間。

TimetoBeReceived

訂單系統接收來自很多其他系統的請求,還發布事件並從其他系統接收事件 – 每一項都會在傳入隊 列或在外發隊列中產生消息。有些服務使用某個源上傳入的數據,而這些數據的有效性只有幾分鐘。將這 些消息保留更長的時間沒有任何意義 – 即使這種延長能看到所有服務器進程啟動(IIS、SQL Server、 其他 Windows 服務)也是如此。這一點對於系統發出的數據源同樣適用 – 如果某個訂閱者離線時間長 於消息中數據的有效時間,那麼該消息在外發隊列中繼續占據空間就沒有任何意義了。

當探討組成各項服務合同的各種信息時,我們注意到對於那些其數據不一定具有有效期的消息,服務 將響應時間要求作為其服務級別協議的一部分。換言之,如果服務無法在給定的時間段(包括消息在隊列 中的等候時間)內完成對某條消息的處理,則從業務角度來看,該服務已經出現故障。

這意味著可為各個消息類型定義其有效期該時間需要考慮花費在發送者的外發隊列中的時間、傳送時 間以及在接收者的隊列中的時間。在 MSMQ 中,我們可使用 Message 類(有關實現的詳細信息,請參閱 “深入了解 TimeToBeReceived”側欄)的 TimeToBeReceived 屬性來定義此值。

現在我們已然清楚消息丟失是一個不可避免的現實問題。從客戶端的角度來看,丟失消息、丟失響應 或服務器速度慢產生的結果並無二致。在處理消息時,如果我們的服務使用其他服務作為客戶端,為確保 相應的響應時間遵守服務級別協議所指示的時間,我們的服務需要自我保護。我們決定使用超時,這樣一 來,如果響應未在規定時間內到達,則我們的服務最起碼可以通告已接收到請求,會在稍後發送該響應, 如果是與用戶聯系,可發送電子郵件。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved