Practical Mission-critical and Real-time Systems

  • 546
  • 0
  • 2019-12-06

在普通應用上, 使用者遇上 Mission-critical 和 Real-time 系統的機會不大。但萬一你有需要做一個出來, 有什麼該注意的?

我現在公司裡使用的系統, 就是典型的 Mission-critical and Real-time。一般來講, Mission-critical 系統已經很稀有了, 同時又是 Real-time 系統, 就更少了。讀資工的人應該都知道, Mission-critical 系統和 Real-time 系統是最難做的, 因為要注意的細節很多, 又很容易出錯。

不過, 這套系統並不是我開發的, 我只是使用者。我很難得能夠以旁觀者的角度來看這類系統, 從而分析其中做得好或者不好的地方(當然是在不透露任何公司機密的前題下), 同時提供大家參考。

Mission-critical System

Mission-critical 在中文通稱「關鍵任務」。但是為了讓大家不要誤會是某部電影, 所以在本文中我就直接使用英文了。簡單來講, 所謂的 Mission-critical 是個蠻抽象的名詞; 也就是「不能出錯」的意思。

或許有人會認為系統本來就「不能出錯」, 那不是廢話嗎?

不! 那絕對不是廢話。絕大多數系統在資源考量下, 是允許出錯的; 或者說雖然它有時候會出錯, 卻不會引起大問題。我們必須在引進 Risk-assessment (危機評估)的概念下, 才有辦法做較為精確的界定。

在這裡所謂「不能出錯」, 並不是指「保證不會出錯」的系統。世界上也沒有這種東西。它實際上的意思指的是「必須比一般系統更嚴謹,而且如果出錯了, 要有辦法救回來, 或者有替代的方案」。絕對不能雙手一攤, 覺得不干我事一樣。在這種系統中, 出任何錯都有可能出大問題, 必須特別小心。

此外, 在一個大系統中可能會有很多個子系統。我們通常不會說整個大系統都是 Mission-critical; 而是只有最關鍵的幾個子系統才算是 Mission-critical。有時 Mission-critical 這個名詞也用來指最關鍵的因子 (factors), 而不一定是系統。

那麼, 到底要怎麼做到一個系統「不能出錯」? 

世界上絕對不會有一個系統是完全不會出錯的。理論上不可能, 實際上也不可能。但是我們起碼可以做到兩件事:

  1. 把出錯的機率降到最小
  2. 萬一出任何錯誤, 都要有補救措施

典型的 Mission-critical 系統就像火箭發射任務或者核電廠的運作。出錯的代價太大, 絕對不能大意。

但我們經常也把政府或企業中關乎重大的系統通稱為 Mission-critical, 就像股票、期貨、債券和金融轉帳等系統。

舉個例子來講好了。如果客戶在銀行裡從 A 帳戶轉帳到另一個帳戶 B, 但是過程中錢轉丟了, 而且沒有人知道錢被轉去了哪裡。這關乎企業的信譽, 當然它就是一個絕對不能允許的情況。所以, 若從這個面向考量, 像 Banking system 就是一個 Mission-critical 系統。股票交易也是一樣。

說來容易做來難。當你在設計這樣的系統時, 你必須把各種可能出錯的因素 (Mission-critical factors) 徹底研究得清清楚楚, 否則你根本無法防範各種致命的錯誤。隨便舉幾個例子:

  1. 客戶敲錯帳戶, 把錢轉錯了人
  2. 銀行或系統間的 gateway 突然當機
  3. 瞬間交易量太大, gateway 無法負荷
  4. 瞬間交易量太大, db 無法負荷
  5. 瞬間交易量太大, 其它 XXX 系統無法負荷
  6. 客戶端網路突然斷線
  7. 突然停電/UPS 壞掉
  8. 機房冷氣壞掉, 機櫃過熱
  9. 電線被老鼠咬斷
  10. Application Server 當機
  11. 各種 Server 被系統更新重開機
  12. 各種 Server 更新後並不匹配
  13. 被植入木馬
  14. 被 DDoS
  15. 操作人員誤刪了 table
  16. 系統到了定時備份的時間, 資料庫暫時鎖住
  17. 某個系統出錯, 編了重複的 ID
  18. 某個系統被客戶姓名中出現的異體字搞到當掉
  19. 某個系統被錯誤的 XML 或 JSON 搞到當掉
  20. ... 其它幾千個可能出錯的點
  21. 系統為了防以上各種錯誤, 過度運算而導致 CPU 或 IO 滿載, 結果 Server 又當掉

有時候就算是一個優秀且正常的系統也會被其它異常的系統拖累, 所以內部系統之間也要做適當的風險控管。如果你沒辦法想像這種情境, 或許可以參考「潘多拉爆雷心得」這篇網誌, 看看考慮不周詳的系統如何釀成大災難。

很不幸地, 許多 Mission-critical 系統經常同時也是 Real-time 系統。這又平添了它的複雜度。

Real-time System

所謂的 Real-time System 就是指「即時系統」(不一定是指作業系統)。顧名思義, 就是這個系統必須能夠即時回應的意思。當然, 人類的神經傳導需要時間, 電腦的信號傳遞需要時間, 運算需要時間, 顯示也需要時間; 所以所謂的「即時回應」, 從來都不是真的指「馬上」回應, 它是一個抽象的名詞。

當我還在宏碁科技擔任早期 Windows PM 的時候, 對於剛出現的圖形界面, 我們內部曾對作業系統的反應時間下了一個界定的標準。一般而言, 如果使用者對系統下了指令, 那麼它的回應若能在 1.5 秒內呈現, 我們就「承認」它的反應是「即時」的。不過, 對於人機界面(例如滑鼠和鍵盤)的反應時間就不是這樣定義了; 這種反應時間大多只允許幾十到幾百毫秒以內, 否則使用者是不會接受的。

隨著科技的進步, 現在我們對於作業系統就絕對不會容許像 1.5 秒那麼久的反應時間。依現在的定義, 系統的反應時間差不多只有 250 毫秒而已, 否則使用者就會感覺卡卡的。

但如果不是作業系統呢?

對於一般的商用系統, 尤其在網路上的應用, 我們不可能把 250 毫秒拿來當作是否為 Real-time eal-time 的標準, 不然就太不切實際了。在一般商業應用上, 我們仍然會採用稍為寬鬆的標準, 例如 1.5~5 秒左右。大致上, 如果反應時間超過 5 秒, 客戶就會有「慢」的感覺; 超過 10 秒, 就會開始覺得「很慢」。

像線上的股票交易市場, 就是一個典型的 Real-time System。如果你曾經在線上買賣過股票, 當你送出交易指令後, 你會不會希望馬上知道交易是否已經成立? 當然啦, 這裡指的並不是「成交」, 而是交易是否「成立」。有時候我們可能會遇到網路斷線的問題; 如果訂單送出卻沒有成立, 我們可以再補單。但是如果訂單送出後卻遲遲未能確定是否成立, 就會讓人覺得非常困擾。

高鐵的訂票系統也是一樣。當我們在預訂熱門時段的高鐵票時, 如果你已經送出訂單, 但等了很久都不知道到底訂票成功了沒有, 我們也會覺得不耐煩, 甚至生氣。

如果你是設計系統的, 你如何確保客戶能夠「即時」地得到他們期望中的回應?

Chaos Prevention

要做一個 Real-time 系統, 必須實做簡捷的設計, 才有辦法保證最快速的回應。如果要做一個 Mission-critical 系統, 則必須做到極為全面的防錯及防呆機制, 其邏輯非常的複雜。但如果你的系統既是 Mission-critical 又是 Real-time, 那麼你就會面臨到以上兩種互斥的前題。

像這樣的系統, 只有最優異的 architect 才有能力做得好。

我的建議是這樣子的:

  1. 做詳盡的 Risk Assessment - 你必須能夠先釐清系統中哪些才是最重要的、最危險的, 然後排出輕重等級
  2. 釐清到底哪些是系統中真正的 Mission-critical Factors, 哪些不是
  3. 釐清到底哪些任務必須做到 Real-time Response, 哪些不必
  4. 針對各種列入考量的 Mission-critical Factors, 提出 Recovery Plan - 你必須先考慮退路, 再考慮進攻路線
  5. 導入並嚴格實行 SOLID 準則
  6. 嚴格實行 TDD
  7. 評量最大的系統尖峰臨界條件 (例如 Maximum Concurrent Sessions), 再乘以三倍後用以做為 Load test 與 Stress test 的基準
  8. 訂定詳實的 Test Scenarios 並據以製訂詳實的 Test Cases
  9. 考慮硬體架構的橫向擴充能力
  10. 以最嚴謹的態度進行後續的軟體開發工程

以上十點通通都很重要。雖然愈上面看似愈抽象, 但是愈上面愈重要。

就我目前遇到的情況來說, 雖然系統平常就有或大或小的問題, 但是都還算容易解決。如果遇到一些 Mission-critical 的狀況, 就算出錯了 (無論是系統問題或者人為問題), 由於軌跡都留著, 所以還算容易克服。

但是, 由於前一陣子客戶數量暴增, 一些從未遇到過的問題開始出現了。

雖然我並不是系統的設計者, 但我站在使用者的立場冷眼旁觀, 也看得出問題出在哪裡。當我第一次遇到客戶的交易資料竟然大到無法順利下載到本機的時候, 我開始驚覺到這個系統或許已經面臨到臨界點了。這種問題之所以發生, 絕對是因為一開始的架構設計出了問題。

沒有中過邪的人一定不信邪。在職場生涯中我不知道聽過幾百次「哎呀, 不會有那麼多客戶同時來啦!」的講法。但如果真的來了, 怎麼辦?

我慢慢覺得我遇到的問題或許並不是軟體的問題(畢竟能夠同時做到 Mission-critical 和 Real-time 也已經著實不容易), 而是整體「架構」的問題。有些客戶的交易次數非常頻繁; 光是要計算「餘額」, 就要在上萬筆紀錄中做即時運算。如果要一下計算幾萬個會員的餘額, 至少要算個幾億次。何況, 要計算的還不只餘額一個欄位而已。我們光要在後台做一個即時的 Scoreboard, 起碼就有上百個欄位要做 Aggregation。然後還要即時呈現。

更大的問題來了。在某些地方, 後台所用的資料庫和即時系統的資料庫是同一個! 當主管們希望在後台中看到即時的 Stats, 其花費的運算也會佔用到客戶端的 Bandwidth!

這種情況有點像量子領域的雙縫實驗。當你在進行觀測的同時, 已經因為觀測的動作而擾動了量子的波動性, 導致它出乎意外地呈現了粒子性。

如果能夠早早預測到上述的現象, 那麼資料庫一開始就應該採用 Mirror writing, 或者就根本不要提供即時的統計功能。到底什麼才是 Mission-critical? 知道即時業績算是 Mission-critical 嗎? 客戶的交易不受到影響, 才是真的 Mission-critical 吧!

Rolling Changes Handling

除了上述的問題之外, 我們還遇到一個一開始沒有想清楚的內部問題; 嚴格說也算是一個 Mission-critical Factor, 只是由於它並不影響到客戶, 問題不是那麼嚴重而已。

假設有一項產品原本是歸類為 A 大類, 後來改歸為 B 大類。這項產品一直都有人買, 但是在做統計分析時, 這個產品的業績到底是歸給 A 類, 還是 B 類?

我把這類問題稱為「滾動式的變更」。意思是, 以上述產品為例, 合理地講, 它在改為 B 類以前, 其業績應該算在 A 類, 之後才算做 B 類。問題是, 在我們的設計中, 因為這項資訊放在關聯式資料庫中; 這個產品既然已經變更為 B 類, 那麼它不管何時的業績, 最後都算在 B 類頭上。以致於, 當它被改成 B 類之後, 負責 A 類業務的 PM 就平白損失了業績, 而負責 B 類的 PM 則憑空增加了業績。這是錯誤的。

若要解決這個問題, 資料庫的設計方式就應該改變。但是一來變更資料庫架構的危機何其重大, 根本沒有人敢亂改; 二來這種變更種類的例子極少, 所以最後不了了之, 只好手動調整。

比較理想的做法, 仍然要回到設計初始的周詳考慮。到底一個欄位會不會變成外部鍵的一部份、會不會被修改, 不管如何, 都必須在一開始就清楚地釐定, 而且一旦決定就不能再更改。如果一開始不能斬釘截鐵地釐清, 就應該把這個欄位從寬認定為「可以事後修改」的欄位。而凡是可以事後修改的欄位, 那麼在後面進行計算之前, 必須有辦法回溯到修改之前跟之後的計算基準, 否則你之後的任何計算都有可能是錯誤的!

Logical Firewalls

綜合上述幾個問題, 除了硬體架構暫且不提之外, 我們實在應該在大系統的內部、各子系統之間建立邏輯上的防火牆。換句話說, 我們真應該再度好好考量前面講過的 SOLID 準則。它在軟體開發領域中一再被提及, 當然有它的道理。而且它的部份準則並不限於軟體之間; 系統與系統之間也適用。

以我說的「滾動式變更」為例, 如果我們尊重 SOLID 準則, 那麼我們就應該事先考慮到資料庫的斷點設計問題。如果一筆資料的鍵值有可能會改變, 那麼一開始就不應該把它設計為外部鍵, 或者至少應該在做 Aggregation 時設法讓它關聯到某個時間因素, 或者增加一個紀錄欄位。

至於資料量愈來愈龐大所造成的效能影響, 我們在一開始就應該「過度高估」其臨界值 (請千萬別告訴我你在設計任何系統時根本沒有去考慮任何臨界值)。其次, 每當你遇到必須在資料庫中做無條件加總這個動作時, 都應該警覺這就是壞味道了。

相反地, 就算資料量還不大, 我們都應該定期(例如每半年)將交易資料做一個彙總, 之後的所有彙整動作都只參考既有的彙總資料庫, 盡量別去龐大的原始資料中做反覆運算, 以免影響效能。此外, 若有需要對彙總資料庫做異動時, 最好能夠離線操作(以確保正確性)。諸如此類。

總之, 我在本文中只能把可能的問題點提出來, 並且盡量提醒、提供一些準則, 但是沒辦法提供太多的解決方法。因為我不可能知道每個人會遇到什麼情境。面對這種極為複雜的系統, 它本來就不好做。不過, 事在人為, 只要有心, 凡事都會成功。人類早在 1969 年就能把人送去登陸月球了; 所以只要好好規劃, 天下無難事。


Dev 2Share @ 點部落