讀書筆記《發佈!設計與部署穩定的分佈式系統》

英文版原名:Release It! Design and Depoly Producation-Ready Software

不太習慣這本書的翻譯,讀起來令人略感不適,:(

總結:
這本書比較全面的介紹了建設穩定系統的反模式與模式,涵蓋了軟件系統開發的方方面面,當讀到一些例子時能讓人聯想到工作中遇到的一些故障案例。這些模式與反模式往往是我們在進行系統的設計中容易忽略的,我們可能更關注了功能性設計而忽略了一些影響系統穩定性的功能設計。架構設計除了考慮系統開發過程的成本,也應該考慮後期的運維成本,通過全面的評估來進行設計的決策,而不是僅僅關注開發而導致後期維護的痛苦。

以下爲摘抄及讀書筆記,主要爲反模式與模式相關部分。

第1章 生產環境的生存法則

  1. 瞄準正確的目標:系統的設計和構建目的不應該僅僅是爲了通過QA(quality assurance)的測試,而是應該“爲生產環境而設計”,並滿足能以低成本、高質量的方式進行運維工作。
  2. 應對不斷擴大的挑戰範圍:由於系統用戶的規模和範圍越來越大,對系統正常運行的時間要求也提高了。
  3. 重視運維成本:如果不取消或廢止系統,在系統的整個生命週期中,運維時間遠遠超過開發時間。如果在做成本決策時忽視了運維成本,那這爲了節省一次性的開發成本,卻耗費無盡的運維成本。而創建不停機發布的構建流水線和部署過程將可以避免停機發布(按系統使用5年且每月發佈停機5分鐘,那系統停機時間共計300分鐘,乘以系統的收入則可以計算出停機發布造成的損失)帶來的損失,而且大有可能提供系統部署頻率,以便於佔領更多的市場份額。
  4. 重視早期決策對系統的長期影響:雖然敏捷開發強調應該儘快交付和漸進式改進以便於軟件能快速上線,但即使在敏捷項目中,也最好要有遠見的制定系統架構設計的決策。雖然不同的設計方案通常具有相近的實施成本,但這些方案在整個軟件生命週期中的總成本截然不同。因此,考慮每個方案對系統可用性、系統容量和靈活性的影響至關重要。
  5. 設計務實的架構:務實的架構設計應該綜合考慮軟件、硬件、用戶三者之間的關係,並充分考慮系統在後續的運維中的問題,包括部署、監控等等。

第一部分

第2章 案例研究:讓航空公司停飛的代碼異常

本章節分享了一個實際的故障案例的發現、處理過程、事後分析,最後找到的原因竟然是一個未被捕獲的 SQLException 異常。

雖然當我們找到問題的原因之後可以比較方便的重現這個缺陷,但是如果按常規的測試用例有辦法全方位的避免這樣的問題嗎?很顯然,我們沒辦法把每一個這樣的缺陷都找出來。而我們更應該關注的是系統中的一個缺陷可能會傳播到所有的其他相關聯或者相互依賴的系統中,所以我們需要探討防止這類問題蔓延的設計模式。

第3章 讓系統穩定運行

當構建系統的架構、設計甚至底層實現時,許多決策點對系統的最終穩定性具有很大的影響力。而面對這些決策點,高度穩定的設計與不穩定的設計投入的成本通常是相同的。

  1. 什麼是穩定性:
    1. 事務:是系統處理的抽象工作單元,這與數據庫事務不同,一個工作單元可能包含許多數據庫事務、外部系統集成等。一個系統可以是隻能處理一種事務的專用系統,也可以是能處理不同種類事務的組合的混合工作負載。
    2. 系統:是指爲用戶處理事務所需的一套完備且相互依賴的硬件、應用程序和服務。
    3. 穩定性:即使在瞬時衝擊(對系統快速施加大量的訪問流量)、持續壓力(長時間持續地對系統施加訪問流量)或正常處理工作被失效的組件破壞的情況下,穩健的系統也能夠持續的處理事務。這不僅僅是指服務器或應用程序仍能保持運行,更多的是指用戶仍然可以完成工作。
    4. 壽命長的系統會長時間處理事務,“長時間”是指兩次代碼部署的間隔時間。
  2. 系統壽命測試(書上是“延長系統壽命”,?)。威脅系統壽命的主要敵人是內存泄漏和數據增長。可以通過負載測試工具(JMeter、Marathon或其他)持續向系統發送請求來模擬長時間運行的場景以便於測試系統壽命。(也可以每天有幾個小時不怎麼向系統發送請求來模擬半夜低峯時段)
  3. 系統失效方式
    1. 突發的壓力和過度的壓力
    2. 我們需要接受“系統必然失效”這一事實,然後針對性進行相應的設計。
  4. 系統失效鏈
    1. 術語:
      1. 失誤:軟件出現內部錯誤。出現失誤的原因既可能是潛在的軟件缺陷,也可能是在邊界或外部接口處發生的不受控制的狀況。
      2. 錯誤:明顯的錯誤行爲。
      3. 失效:系統不再響應。
    2. 失誤一旦被觸發,就會產生裂紋。失誤會變成錯誤,錯誤會引發失效。這就是裂紋的蔓延方式。
    3. 共識:第一,失誤總會發生,且永遠無法杜絕,必須防止失誤轉變爲錯誤;第二,即使在盡力防止系統出現失效和錯誤時,也必須決定承擔失效或錯誤的風險是否利大於弊。

第4章 穩定性的反模式

一、集成點

集成點是系統的頭號殺手。每一個傳入的連接都存在穩定性風險。每個套接字、進程、管道或RPC都會停止響應。即使是對數據庫的調用,也可能會以明顯而微妙的方式停止響應。系統收到的每一份數據,都可能令系統停止響應、崩潰,甚至產生其他的衝擊。

  1. 套接字協議
    1. 基於三次握手的連接的問題:a. SYN; b. SYN/ACK; c. ACK
      1. reset拒絕連接
      2. 網絡丟包
      3. 因各種原因(比如突發大量連接請求)服務端監聽隊列滿了
    2. read()調用阻塞
  2. (案例)防火牆對空閒連接的處理
    1. 案例中防火牆會自動刪除空閒連接導致JDBC連接失效
    2. 解決辦法:Oracle數據庫增加無效連接檢測主動探測有效客戶端
  3. HTTP協議
    1. 問題:
      1. 服務提供方可能接受TCP連接,但不會響應HTTP請求
      2. 服務提供方可以接受連接但不能讀取請求。如果請求體很大,它可能會填滿服務提供方的TCP窗口,導致調用方的TCP緩衝區一直去填充,從而阻塞套接字寫操作。在這種情況下,即使發送請求也永遠不會完成。
      3. 服務提供方可能返回調用方不知道如何處理的響應狀態。如:418 我是茶壺;451 資源被刪除
      4. 服務提供方返回的響應的內容類型是調用方不知道如何處理的
      5. 服務提供方可能聲稱要發送JSON,但實際發送了純文本,或者是二進制文件,或者其他格式文件
    2. 解決辦法:
      1. 客戶端對超時時間進行細粒度控制(包括連接超時時間和讀取超時時間),並能對響應進行處理
      2. 應該先確認響應內容是否符合預期再處理響應內容,而不是直接把響應映射成對象
  4. 供應商的API程序庫
    1. 警惕供應商的API程序庫的穩定性,包括質量、風格和安全性等方面的不穩定性。---- 注 (現身說法):我今年2月份就因爲國內某大廠提供的sdk的默認超時時間設置踩過坑。
    2. 阻塞是影響供應商API程序庫穩定性的首要問題,包括內部資源池、套接字讀取指令、HTTP連接、Java序列化等
    3. 所以我們在使用供應商的API程序庫前,最好能夠獲取到源碼或者反編譯審視一下里面的實現
  5. 總結
    1. 每個集成點最終都會以某種方式發生系統失效,所以需要爲系統失效做好準備
    2. 爲各種形式的系統失效做好準備,包括網絡錯誤、語義錯誤
    3. 由於調試集成點有時比較困難,所以可以考慮通過數據包嗅探器和其他網絡診斷工具來排查問題
    4. 如果系統的代碼缺乏一定的防禦性,集成點失效的影響會迅速蔓延。所以系統代碼需要考慮通過斷路器、超時、中間件解耦和握手等模式來進行防禦性編程,以防止集成點出現問題

二、同層連累反應

這種情況對應於通過負載均衡器實現水平擴展的集羣。
當負載均衡組中的一個節點發生故障時,其他節點必須額外分擔該節點的負載。

比如一個8個節點的集羣,每個節點分擔12.5%的負載。
當1個節點故障時剩餘的7個節點每個節點需要處理總負載的14.3%,雖然每個節點只增加了1.8%的負載分擔,但單節點的負載比之前增加了14.4%。
當2個節點故障時剩餘的6個節點每個節點需要處理總負載的16.7%,雖然每個節點只增加了4.2%的負載分擔,但單節點的負載比之前增加了33.3%。

  1. 一臺服務器的停機會波及其餘的服務器
  2. 一臺服務器的內存泄漏的停機會導致其他服務器的負載增加,增加的負載又會加速其他服務的內存泄露速度
  3. 一臺服務器陷入死鎖,其他服務器所增加的負載會導致它們也更容易陷入死鎖
  4. (措施)採用自動擴展。應該爲雲端每個自動擴展組創建健康檢查機制。自動擴展將關閉未通過健康狀況檢查的服務器實例,並啓動新的實例。只要自動擴展機制的響應速度比同層連累反應的蔓延速度快,那麼系統服務就依然可用。
  5. 利用“艙壁模式”進行保護

三、層疊失效

原因:

  1. 內存泄露
  2. 某個組件超負荷運行
  3. 依賴的數據庫失效
  4. 等等

層疊失效有一個將系統失效從一個層級傳到另一個層級的機制。

層疊失效通常源於枯竭的資源池。資源池枯竭的原因往往是較低層級所發生的系統失效。沒有設置超時時間的集成點,必定會導致層疊失效。

正如集成點是裂紋的頭號來源,層疊失效是裂紋的頭號加速器。防止發生層疊失效,是保障系統韌性的關鍵。斷路器和超時是克服層疊失效最有效的模式。

總結:

阻止裂紋跨層蔓延。當裂紋從一個系統或層級跳到另一個系統或層級時,會發生層疊失效。這通常是因爲集成點沒有完善自我防護措施。較低層級中的同層連累反應也可能引發層疊失效。一個系統肯定需要調用其他系統,但當後者失效時,需要確保前者能夠保持運轉。

仔細檢查資源池。層疊失效通常是由枯竭的資源池(例如連接池)所導致的。當任何資源調用都沒有響應時,資源就會耗盡。此時獲得連接的線程會永遠阻塞,其他所有等待連接的線程也被阻塞。安全的資源池,總是會限制線程等待資源檢出的時間。

用超時模式和斷路器模式實現保護。層疊失效在其他系統已經出現故障之後發生。斷路器模式通過避免向已經陷入困境的集成點發出調用請求,進而保護系統。使用超時模式,可以確保對有問題的集成點的調用能及時返回。

四、用戶

一、網絡流量

  1. 堆內存:如果用戶的會話保存在內存中的話,那麼每增加一個額外的用戶,就會增加更多的內存。

    解決辦法:

    1. 儘可能少保留內存中的會話

    2. 使用弱引用(用弱引用完成這些操作。這種做法在不同程序庫中的叫法不同,比如在C#中叫System.WeakReference,在Java中叫java.lang.ref.SoftReference,在Python中叫weakref)。其基本思想是,在垃圾收集器需要回收內存之前,弱引用都可以持有另一個對象,後者稱爲前者的有效載荷。當該對象的引用只剩下軟引用[插圖]時,則軟引用就可以被回收。

      當內存不足時,垃圾收集器可以回收任何弱可達對象[插圖]。換句話說,如果對象不存在強引用,那麼有效載荷就可以被回收。關於何時回收弱可達對象、回收多少這樣的對象,以及可以釋放出多少內存,這些都完全由垃圾收集器決定。必須非常仔細地閱讀編程語言系統運行時的文檔,但通常唯一的保證,是在發生內存不足錯誤之前,弱可達對象都可以回收。

  2. 堆外內存和主機外內存
    redis、memcache

  3. 服務器上的套接字數量
    一般人可能不太會關注服務器上的套接字數量。但在流量過大時,這也可能會造成限制。每個處於活動狀態的請求都對應着一個開放式套接字,操作系統會將進入的連接,分配給代表連接接收端的“臨時”端口。如果查看TCP數據包的格式,就能看到端口號長16位,這表示端口號最大隻能到65535。不同的操作系統會對臨時套接字使用不同的端口範圍,但互聯網數字分配機構的建議範圍是49152~65535。這樣一來,服務器最多可以打開16383個連接。但是機器可能用來處理專門的服務,而不是如用戶登錄這樣的通用服務,所以可以將端口範圍擴展爲1024~65535,這樣最多可以有64511個連接。
    然而,一些服務器能夠處理100多萬個併發連接。有人會把上千萬的連接推給單獨一臺服務器。
    如果只有64511個端口可用於連接,那麼一臺服務器如何能有100萬個連接?祕訣在於虛擬IP地址。操作系統將多個IP地址綁定到同一個網絡接口。每個IP地址都有自己的端口號範圍,所以要處理上百萬個連接,總共只需要16個IP地址
    這不是一個容易解決的問題。應用程序可能需要進行一些更改,從而監聽多個IP地址,處理它們之間的連接,並且保證所有監聽隊列的正常運行。100萬個連接也需要很多內核緩衝區。此時需要花一些時間,瞭解操作系統的TCP調優參數。

  4. 已關閉的套接字
    TIME_WAIT

二、 難伺候的用戶

—— 真正下單的用戶,這些用戶相對而言涉及的事務處理更多更復雜(從下單到結算等等),所以需要關注這些用戶相關的操作,因爲他們時網站創造收入的來源。

通常零售系統的轉化率爲2%左右,所以我們可以按4%或者6%或者10%進行系統負載測試。

三、不受歡迎的用戶

可能是爬蟲之類的程序產生的請求,這些請求可能會對系統產生較大的負載。

解決辦法:

  1. 使用技術手段識別並屏蔽這種請求(一些CDN廠商就會提供這種服務),或者在對外的防火牆上執行屏蔽,最好可以讓被屏蔽的IP定期過期
  2. 法律手段:爲網站寫一些使用條款,聲明用戶僅能以個人或非商業用途查看網站內容。

四、不受歡迎的用戶

惡意攻擊的用戶,即黑客

五、線程阻塞

● 線程阻塞反模式是大多數系統失效的直接原因。應用程序的失效大多與線程阻塞相關。系統失效形式包括常見的系統逐漸變慢和服務器停止響應。線程阻塞反模式會導致同層連累反應和層疊失效。

● 仔細檢查資源池。就像層疊失效一樣,線程阻塞反模式通常發生在資源池(特別是數據庫連接池)周圍。數據庫內發生死鎖以及不當的異常處理方式會造成連接永久丟失。

● 使用經過驗證的元操作。學習並使用安全的元操作。形成自己的生產者-消費者隊列看似很容易,但事實並非如此。相比新形成的隊列系統,任何併發實用程序庫都執行了多重測試。

● 使用超時模式進行保護。雖然無法證明代碼不會發生死鎖,但可以確保死鎖不會一直持續下去。避免函數調用中的無限等待,使用需要超時參數的函數版本。即使意味着需要更多的錯誤處理代碼,調用過程中也要始終使用超時模式。

● 小心那些看不到的代碼。所有的問題都可能潛伏在第三方代碼的陰影中。要非常謹慎,自行測試一下。只要有可能,獲取並研究一下第三方代碼庫的源代碼,瞭解那些出人意料的代碼和系統失效方式。出於這個原因,相比閉源程序庫,大家可能更喜歡開源程序庫。

六、自黑式攻擊

自黑式攻擊的典型例子,是從公司市場部發出的致“精選用戶組”的一份郵件。該郵件包含一些特權或優惠信息,其複製速度比“庫娃”木馬病毒(或者是歲數稍大的人所熟悉的“莫里斯”蠕蟲)快得多。任何限一萬名用戶享受的特別優惠,都可以吸引數百萬的用戶。網絡比價社區,會以毫秒爲單位的速度檢測並分享可重複使用的優惠碼。

● 保持溝通渠道暢通。自黑式攻擊發源於組織內部,人們通過製造自己的“快閃族”行爲和流量高峯,加重對系統本身的傷害。這時,可以同時幫助和支持這些營銷工作並保護系統,但前提是要知道將會發生的事情。確保沒有人發送大量帶有深層鏈接的電子郵件,可以將大量的電子郵件分批陸續發送,分散高峯負載。針對首次點擊優惠界面的操作,創建一些靜態頁面作爲“登陸區域”。注意防範URL中嵌入的會話ID。

● 保護共享資源。當流量激增時,編程錯誤、意外的放大效應和共享資源都會產生風險。注意“搏擊俱樂部”軟件缺陷,此時前端負載的增加,會導致後端處理量呈指數級增長。

● 快速地重新分配實惠的優惠。任何一個認爲能限量發佈特惠商品的人,都在自找麻煩。根本就沒有限量分配這回事。即使限制了一個超划算的特惠商品可以購買的次數,系統仍然會崩潰。

七、放大效應

生物學中的平方-立方定律解釋了爲什麼永遠不會看到像大象一樣大的蜘蛛。蟲子的重量隨着體積增加而增加,符合時間複雜度O(n3)。蟲子的腿部力量會隨腿的橫截面積的增加而增加,符合時間複雜度O(n2)。如果讓蟲子增大爲原來的10倍,那麼變大後的蟲子的“力量與體重”之比就會變成原來的1/10。此時,蟲子的腿根本支撐不了10倍大的個頭。

我們總會遇到這樣的放大效應。當存在“多對一”或“多對少”的關係時,如果這個關係中一方的規模增大,另一方就會受到放大效應的影響。例如當1臺數據庫服務器被10臺機器調用時,可以很好地運行。但是當把調用它的機器數量再額外添加50臺時,數據庫服務器就可能會崩潰。

在開發環境中,每個應用程序都只在一臺機器上運行。在測試環境中,幾乎每個應用程序都只安裝在一兩臺機器上。然而,當到了生產環境時,一些應用程序看起來非常小,另一些應用程序則是中型、大型或超大型的。由於開發環境和測試環境的規模很少會與生產環境一致,因此很難在前兩個環境中,看到放大效應跳出來“咬人”。

  1. 點對點通信

    不像開發環境或者測試環境,生產環境的連接的總數,會以實例數量平方的數量級上升。當實例增加到100個時,連接數會擴展到時間複雜度O(n2),這會讓開發工程師感到非常痛苦。這是由應用程序實例數量所驅動的乘數效應,雖然根據系統的最終規模,O(n2)的擴展或許沒問題,但無論上述哪種情況,在系統進入生產環境之前,開發工程師都應該知道這種放大效應。
    點對點通信的替代方案:

    1. UDP廣播:廣播能夠應對服務器數量的不斷增長,但它不節省帶寬。由於服務器的網卡會獲取廣播,且必須要通知TCP/IP協議棧,因此廣播會讓那些與廣播消息不相關的服務器產生一些額外的負載。
    2. TCP或者UDP組播:組播只允許相關的服務器接收消息,因此傳送效率更高。
    3. 發佈-訂閱消息傳遞:發佈-訂閱消息傳遞的效率也較高,即使服務器在消息發送的那一刻沒有監聽,也可以收到消息。當然,發佈-訂閱消息傳遞,通常會讓基礎設施成本大增。
    4. 消息隊列
  2. 留意共享資源。

    共享資源會成爲系統的瓶頸、系統容量的約束和系統穩定性的威脅。如果系統必須使用某種共享資源,那就好好地對它進行壓力測試。另外,當共享資源處理速度變慢或發生死鎖時,要確保其客戶端能繼續工作。

八、失衡的系統容量

服務提供方和服務調用方的服務器的比例往往不好確定一個絕對合適的比例,所以對於服務的構建,如果不能使之全部滿足前端潛在的壓倒性需求,那麼就必須構建服務調用方和服務提供方的韌性,從而能夠應對海嘯般襲來的請求。對服務調用方來說,當響應獲取速度變慢或連接被拒絕時,使用斷路器模式有助於緩解下游服務的壓力。對服務提供方來說,可以使用握手和背壓[插圖]通知調用方,限制調用方發送請求的速度。還可以考慮使用艙壁模式,爲關鍵服務的高優先級調用方預留系統容量。

● 檢查服務器和線程的數量。在開發環境和QA環境中,系統可能看起來在一兩臺服務器上運行,其調用的其他系統的所有QA環境也是如此。然而在生產環境中,比例更有可能是10比1,而不是1比1。要將QA環境和生產環境對照起來,檢查前端服務器與後端服務器的數量比,以及兩端所能處理的線程的數量比。

● 密切觀察放大效應和用戶行爲。失衡的系統容量是放大效應的特例:關係中一方的增幅變化大大超過另一方。季節性、市場驅動或宣傳驅動等流量模式的變化,會導致前端系統的大量請求湧向後端系統(通常是良性的),就像熱門的社交媒體帖子導致網站流量劇增。

● 實現QA環境虛擬化並實現擴展。即使生產環境的規模是固定的,也不要僅只使用兩臺服務器運行QA環境。擴展測試環境,嘗試在調用方和服務提供方的規模擴展到不同比例的環境中運行測試用例,這些應該能夠通過數據中心的自動化工具自動實現。

● 重視接口的兩側。如果所開發的是後端系統,假如系統突然收到10倍於歷史最高的請求數量,並且都湧向了系統開銷最大的事務部分,將會發生什麼?系統完全失效了嗎?它是先慢下來然後又恢復了正常嗎?如果所開發的是前端系統,那麼就需要看一看當後端系統在被調用時停止了響應,或變得非常慢的時候,前端系統會發生什麼。

九、一窩蜂

系統達到穩態時的負載,會與系統啓動或週期性運行的負載存在明顯不同。想象一個應用程序服務器農場的啓動過程,每臺服務器都需要連接到數據庫,並加載一定數量的參考數據或種子數據。每臺服務器的緩存都從空閒狀態開始,逐漸形成一個有用的工作集。到那時,大多數HTTP請求會轉換爲一個或多個數據庫查詢。這意味着當應用程序啓動時,數據庫上的瞬時負載要比運行一段時間後的負載高得多。

一堆服務器一同對數據庫施加瞬時負載,這被稱爲“一窩蜂”。

引發一窩蜂現象的幾種情況如下。
❑ 在代碼升級和重新運行之後,啓動多臺服務器。
❑ 午夜(或任何一個整點時間)觸發cron作業。
❑ 配置管理系統推出變更。

一些配置管理工具允許配置一個隨機的“擺動”(slew)值,使各臺服務器在稍微不同的時間點下載配置變更,從而把一窩蜂分散到幾秒鐘內。

當一些外部現象引起流量的同步“脈衝”時,也可能發生一窩蜂現象。想象城市街道每個角落安裝的紅綠燈。當綠燈亮時,人們會簇擁成羣地過馬路。因爲每個人的行走速度不同,所以他們會在一定程度上分散開,但下一個紅綠燈會再次將他們重新聚成一羣。注意系統中阻塞許多線程的所有地方,它們在等待某個線程完成工作。而當這個狀態打破時,新釋放的線程就會對任何接收數據包的下游系統施加一窩蜂。

如果虛擬用戶的腳本存在固定等待時間,則在進行負載測試時,就會產生流量脈衝。此時,腳本中的每個等待時間都應該附帶一個小的隨機時間增量。

● 一窩蜂所需系統成本過高,高峯需求無法處理。一窩蜂是對系統的集中使用,相比將峯值流量分散開後所需的系統能力,一窩蜂需要一個更高的系統容量峯值。● 使用隨機時鐘擺動以分散需求。不要將所有cron作業都設置在午夜或其他任何整點時間執行。用混合的方式設置時間,分散負載。
● 使用增加的退避時間避免脈衝。固定的重試時間間隔,會集中那段時間的調用方需求。相反,使用退避算法,不同調用方在經過自己的退避時間後,在不同的時間點發起調用。

十、做出誤判的機器

就像槓桿一樣,自動化使得管理員能夠花費較少的努力進行大量的操作,這就是一個力量倍增器。
同樣,當自動化程序出現“判斷失誤”時,引發的故障同樣也會被放大。

這種情況一般會出現在控制層中。控制層中的軟件主要管理基礎設施和應用程序,而不是直接交付用戶功能。日誌記錄、監控、調度程序、擴展控制器、負載均衡器和配置管理等都是控制層的一部分。
貫穿這些系統失效事件的常見線索是,自動化系統並未簡單地按照人類管理員的意願行事。相反,它更像是工業機器人:掌握控制層感知系統的當前狀態,將其與期望的狀態進行對比,然後對系統施加影響,使當前狀態進入到期望狀態。

可以在控制層軟件中實現類似的防護措施。
❑ 如果軟件觀測器顯示系統中80%以上的部分不可用,那麼與系統出問題相比,軟件觀測器出問題的可能性更大。
❑ 運用滯後原則,快速啓動機器,但要慢慢關機,啓動新機器要比關閉舊機器更安全。
❑ 當期望狀態與觀測狀態之間的差距很大時,要發出確認信號,這相當於工業機器人上的大型黃色旋轉警示燈在報警。
❑ 那些消耗資源的系統應該設計成有狀態的,從而檢測它們是否正在試圖啓動無限多個實例。
❑ 構建減速區域,緩解勢能。假想一下,控制層雖然每秒都能感知到系統已經過載,但它啓動一臺虛擬機處理負載需要花費5分鐘。所以在大量負載依然存在的情況下,要確保控制層不會在5分鐘內啓動300個虛擬機。

● 在造成一片狼藉之前尋求幫助。基礎設施管理工具可以迅速對系統產生巨大的影響,要在其內部構建限制器和防護措施,防止其快速毀掉整個系統。
● 注意滯後時間和勢能。由自動化所引發的操作,需要花時間才能完成。這段時間通常會比監控的時間間隔要長,因此請務必考慮到系統需要經過一些延遲後,才能對操作做出響應。
● 謹防幻想和迷信。控制系統會感知環境,但它們有可能被愚弄。它們會計算出一個預期狀態,並形成有關當前狀態的“信念”,但這兩者都有可能是錯誤的。

十一、緩慢的響應

生成響應較慢比拒絕連接或返回錯誤更糟,在中間層服務中尤爲如此。

快速返回系統失效信息,能使調用方的系統快速完成事務處理,最終成功或是系統失效,取決於應用程序的邏輯。但是,緩慢的響應會將調用系統和被調用系統中的資源拖得動彈不得。

緩慢的響應通常由過度的需求引起。當所有可用的請求處理程序都已開始工作時,就不會有任何餘力接受新的請求了。當出現一些底層的問題時,也會表現出響應緩慢。內存泄漏也經常表現爲響應緩慢,此時虛擬機就越來越奮力地回收空間,從而處理事務。CPU利用率雖然很高,但都用在垃圾回收上,而非事務處理。另外,我偶爾會看到由於網絡擁塞而導致的響應緩慢。這種情況在局域網內部相對少見,但在廣域網上是肯定存在的,尤其是在協議過於“饒舌嘮叨”的情況下。然而,更常見的情況是,應用程序一方面忙着清空它們的套接字發送緩衝區,另一方面又任由其接收緩衝區不斷積壓,從而導致TCP協議停頓。當開發工程師自行編寫一個較低層級的套接字協議時,這種情況經常發生,其中read()例程只有當接收緩衝區被清空之後,纔會被循環調用。

緩慢的響應傾向於逐層向上傳播,並逐漸導致層疊失效。

讓系統具備監控自身性能的能力,就能辨別其何時違背SLA。假設服務提供方需要在100毫秒內做出響應,當剛剛過去的20次事務的響應時長移動平均值超過100毫秒時,系統就會開始拒絕請求。這可能發生在應用層,此時系統可以利用給定的協議返回一個錯誤響應。這也可能發生在連接層,此時系統開始拒絕新的套接字連接。當然,任何這樣的拒絕服務,都必須有詳細的文檔記錄,並且調用方也能對此有所預期。

● 緩慢的響應會觸發層疊失效。一旦陷入響應緩慢,上游系統本身的處理速度也會隨之變慢,並且當響應時間超過其自身的超時時間時,會很容易引發穩定性問題。
● 對網站來說,響應緩慢會招致更多的流量。那些等待頁面響應的用戶,會頻繁地單擊重新加載按鈕,爲已經過載的系統施加更多的流量。
● 考慮快速失敗。如果系統能跟蹤自己的響應情況,那麼就可以知道自己何時變慢。當系統平均響應時間超出系統所允許的時間時,可以考慮發送一個即時錯誤響應。至少,當平均響應時間超過調用方的超時時間時,應該發送這樣的響應。
● 搜尋內存泄漏或資源爭奪之處。爭着使用已經供不應求的數據庫連接,會使響應變慢,進而加劇這種爭用,導致惡性循環。內存泄漏會導致垃圾收集器過度運行,從而引發響應緩慢。低層級的低效協議會導致網絡停頓,從而導致響應緩慢。

十二、無限長的結果集

● 使用切合實際的數據量。典型的開發數據集和測試數據集都太小了,不能呈現“無限長結果集”的問題。當查詢返回100萬行記錄並轉成對象時,使用生產環境規模大小的數據集查看會發生什麼情況。這樣做還有一個額外的好處:當使用生產環境規模的測試數據集時,性能測試的結果更可靠。
● 在前端發送分頁請求。前端在調用服務時,就要構建好分頁信息。該請求應包含需要獲取的第一項和返回總個數這樣的參數。服務器端的回覆應大致指明其中有多少條結果。
● 不要依賴數據生產者。即使認爲某個查詢的結果固定爲幾個,也要注意:由於系統某個其他部分的作用,這個數量可能會在沒有警告的情況下發生變化。合理的數量只能是“零”“一”和“許多”。因此除非單單查詢某一行,否則就有可能返回太多結果。要想對創建的數據量加以限制,不要依賴數據生產者。他們遲早會瘋狂起來,無端地塞滿一張數據庫表,而那個時候該找誰說理去?
● 在其他應用程序級別的協議中使用返回數量限制機制。服務調用、RMI、DCOM[插圖]、XML-RPC[插圖]以及任何其他類型的請求-回覆調用,都容易返回巨量的對象,從而消耗太多內存。

第5章 穩定性的模式

一、超時

良好的超時機制可以提供失誤隔離功能——其他服務或設備中出現的問題不一定會成爲你的問題。

超時機制與斷路器相得益彰。斷路器可以記錄一段時間內的超時情況,如果超時過於頻繁,斷路器就會跳閘。

超時模式和快速失敗模式(請參閱5.5節)都解決了延遲問題。當其他系統失效時,要保證自身系統不受影響,超時模式非常有用。若需要報告某些事務無法處理的原因,快速失敗模式則能派上用場。就像一枚硬幣的兩面,快速失敗模式適用於傳入系統的請求,超時模式則主要適用於系統發出的出站請求。

● 將超時模式應用於集成點、阻塞線程和緩慢響應。超時模式可以防止對集成點的調用轉變爲對阻塞線程的調用,從而避免層疊失效。
● 採用超時模式,從意外系統失效中恢復。當操作時間過長,有時無須明確其原因時,只需要放棄操作並繼續做其他事。超時模式可以幫助我們實現這一點。
● 考慮延遲重試。大多數超時原因涉及網絡或遠程系統中的問題。這些問題不會立即被解決。立即重試很可能會遭遇同樣的問題,並導致再次超時。這隻會讓用戶等待更長的時間才能看到錯誤消息。大多數情況下,應該把操作任務放入隊列,稍後再重試。

二、斷路器

斷路器能有效防止集成點、層疊失效、系統容量失衡和響應緩慢等危及穩定性的反模式出現,它能與超時模式緊密協作,跟蹤調用超時失敗(區別於調用執行失敗)。

三、艙壁

船舶的艙壁是一些隔板,一旦將其密封起來,就能將船分隔成若干獨立的水密隔艙。在艙壁口關閉的情況下,艙壁可以防止水從一個部分流到另一個部分。通過這種方式,船體即使被洞穿一次也不會沉沒。艙壁這種設計強調了控制損害範圍的原則。
在軟件開發中也可以使用相同的技術。通過使用隔板對系統進行分區,就可以將系統失效控制在其中某個分區內,而不會令其摧毀整個系統。物理冗餘是實現艙壁最常見的形式。如果系統有4臺獨立的服務器,那麼其中一個硬件所出現的失效,就不會影響其他服務器。同樣,在一臺服務器上運行兩個應用程序實例,如果其中一個崩潰,那麼另一個仍將繼續運行。(當然,除非讓第1個應用程序實例崩潰的那種外部影響力也能讓第2個應用程序實例崩潰。)

四、穩態

無論是文件系統中的日誌文件,數據庫中的記錄行還是內存中的緩存,任何累積資源的機制,都令人聯想起美國高中微積分題目中的存儲桶。隨着數據的累積,存儲桶會以一定的速率填滿。此時這個存儲桶必須以相同或更快的速率清空數據,否則最終會溢出。當該存儲桶溢出時,壞事會發生:服務器停機,數據庫變慢或拋出錯誤信息,響應長得彷彿是在星球間進行通信。穩態模式表明,針對每個累積資源的機制,要相應存在另一個機制回收該資源。下面介紹幾種長期存在並有可能繼續擴大的問題,以及如何避免去擺弄它們。

  1. 數據清除
  2. 日誌文件:不加控制的日誌文件會打滿磁盤,耗盡文件系統的空間。最好一開始就避免一直往文件系統裏添加內容。配置日誌文件迴轉(rotation)只需要花幾分鐘時間。
  3. 內存中的緩存:如果緩存鍵數量沒有上限,則必須限制緩存大小,並且採用某種形式的緩存失效機制。定時刷新緩存是最簡單的緩存失效機制,“最近最少使用”[插圖]或工作集算法也值得研究,但定時刷新緩存能夠處理90%的情況。

● 避免擺弄。人爲干預生產環境會導致問題。要消除對生產環境重複進行人爲干預的需求。系統應在無須手動清理磁盤或每晚重新啓動的情況下,至少運行一個發佈週期。
● 清除帶有應用程序邏輯的數據。DBA可以通過創建腳本清除數據,但他們不知道刪除數據之後,應用程序會如何運轉。爲了保持邏輯完整性(特別是在使用對象關係映射工具時),應用程序需要清除自己的數據。
● 限制緩存。內存中的緩存可以加快應用程序的運行速度。但若不對內存中的緩存加以控制,執行速度仍會降低。需要限制緩存可消耗的內存量。
● 滾動日誌。不要無限量保留日誌文件。基於日誌文件的大小來配置日誌文件迴轉。如果合規性要求保留,則在非生產服務器上執行。

五、快速失敗

即使在快速失敗時,也要確保用不同的方式報告系統性失效(資源不可用)和應用程序失敗(參數違規或無效狀態)。否則,哪怕僅是用戶輸入了錯誤數據並點擊了三四次重新加載,若報告成不具分辨度的一般性錯誤,也可能導致上游系統引發斷路器跳閘。快速失敗模式通過避免響應緩慢來提高整個系統的穩定性。與超時模式配合使用,快速失敗模式有助於避免層疊失效。當系統由於部分失效而面臨壓力時,快速失敗模式還有助於保持系統容量。

爲了讓“任其崩潰並替換”卓有成效,系統要具備幾個前提。

  1. 有限的粒度:必須爲崩潰定義邊界。發生崩潰的組件應該是獨立的,系統的其餘部分必須能夠自我防護,避免受到層疊失效的影響。在微服務架構中,服務的整個實例可能是正確的崩潰粒度。這在很大程度上取決於它被一個乾淨的實例所取代的速度,繼而引入了下面這個關鍵的前提。
  2. 快速替換:啓動NodeJS進程需要花幾毫秒,可實現快速替換;但是啓動一臺新的虛擬機則需要幾分鐘,就不適合“任其崩潰並替換”的策略了。
  3. 監管:監管器需要密切注意它們重新啓動子進程的頻率。如果重新啓動子進程過於頻繁,那麼監管器可能需要自行崩潰。這種情況表明系統狀態沒有得到充分的清理,或者整個系統面臨危險,而監管器只是掩蓋了根本問題。

● 快速失敗,而非緩慢響應。如果系統無法滿足SLA要求,快速通知調用者。不要讓調用者等待錯誤信息,也不要讓他們一直等到超時。否則你的問題也會變成他們的問題。
● 預留資源,並儘早驗證集成點有效。本着“不做無用功”的原則,確保在開始之前就能完成事務。如果關鍵資源不可用,比如所需調用的斷路器已跳閘,那麼就不要再浪費精力去調用。在事務的開始階段和中間階段,關鍵資源可用狀態發生變化的可能性極小。
● 使用輸入驗證。即使在預留資源之前,也要進行基本的用戶輸入驗證。不要糾結於檢查數據庫連接,獲取域對象,填充數據以及調用validate()方法,找到未輸入的必需參數纔是關鍵。

六、任其崩潰並替換

有時,爲了實現系統級穩定性,放棄組件級穩定性就是所能做的最好的事情了。在Erlang語言中,這被稱爲“任其崩潰並替換”的哲學。

程序所能擁有的最乾淨的狀態,就是在剛剛完成啓動的那一刻。任其崩潰並替換的方法認爲錯誤恢復難以完成且不可信賴,所以我們的目標應該是儘快回到剛完成啓動時的乾淨狀態。

● 通過組件崩潰保護系統。通過組件級不穩定性構建系統級穩定性,這似乎違反直覺。即便如此,這可能是將系統恢復到已知良好狀態的最佳方式。
● 快速重新啓動與重新歸隊。優雅崩潰的關鍵是快速恢復。否則,當太多組件同時啓動時,就可能會失去服務。一旦一個組件重新啓動,就應該自動令其重新歸隊。
● 隔離組件以實現獨立崩潰。使用斷路器將調用方與發生崩潰的組件隔離開來。使用監管器確定重新啓動的範圍。設計監管器層級樹,既實現崩潰隔離,又不會影響無關的功能。
● 不要讓單體系統崩潰。運行時負載較大或啓動時間較長的大型進程,不適合運用“任其崩潰並替換”策略。同樣,將許多特性耦合到單個進程中的應用程序,也不推薦運用該策略。

七、握手

當失衡的系統容量導致響應緩慢時,“握手”可能是最有價值的。如果服務器檢測到自己不能滿足其SLA要求,那麼它應該通過一些方法請求調用方停止進程。如果服務器位於負載均衡器之後,那麼它們可以使用“開關”控制,來“開啓或停止”對負載均衡器的響應,然後負載均衡器可以將無響應的服務器移出負載均衡池。不過,這是一種粗放的機制,最好在執行的所有自定義協議中構建握手機制。
當調用缺乏握手機制的服務時,斷路器是一種可以使用的權宜之計。在這種情況下,無須禮貌地詢問服務器能否處理請求,只須發起調用並跟蹤調用是否有效。總體而言,握手是一種未被充分利用的技術,在應用層協議中擁有巨大的優勢。在層疊失效情況下,握手是一種防止裂紋跨層蔓延的有效方法。

● 創建基於合作的需求控制機制。客戶端和服務器之間的握手,允許將需求的流量調節到可服務的級別。在構建客戶端和服務器時,兩者都必須實現握手。而大多數最常見的應用程序級協議,並沒有實現握手。
● 考慮健康狀況檢查。在集羣或負載均衡服務中,使用健康狀況檢查實現實例與負載均衡器握手。
|● 在自己的低層協議中構建握手。如果創建了基於套接字的協議,那麼可以在其中構建握手機制。這樣一來,端點就可以在未準備好接受工作時,通知其他端點。

八、考驗機

(本書中,“考驗機”是指能夠用網絡錯誤、協議錯誤或應用級錯誤等各種低層錯誤來測試被測軟件。)

模擬依賴系統的失效行爲來測試集成系統的一些無法驗證的行爲。

假設要構建一個替代每個遠端Web服務調用的考驗機。由於遠程調用使用網絡,因此套接字連接容易出現以下類型的失效。
❑ 連接被拒絕。
❑ 數據一直在監聽隊列中等待,直到調用方超時。
❑ 遠端在回覆了SYN/ACK之後就不再發送任何數據。
❑ 遠端只發送了一些RESET數據包。
❑ 遠端報告接收窗口已滿,但從不清空數據。
❑ 建立了連接,但遠端一直不發送數據。
❑ 建立了連接,但數據包丟失,重新傳輸導致延遲。
❑ 建立了連接,但遠端對接收到的數據包從不進行確認,導致無休止的重新傳輸。
❑ 服務接受了請求,並且發送了響應頭(假設是HTTP),但從不發送響應正文。
❑ 服務每30秒發送一字節的響應。
❑ 服務發送的響應格式是HTML,而不是預期的XML。
❑ 服務本應發送幾千字節的數據,但實際上發送了幾兆字節。
❑ 服務拒絕了所有身份驗證證書授權。
以上失效問題可以分爲幾類:網絡傳輸問題、網絡協議問題、應用程序協議問題和應用程序邏輯問題。

混沌工程

要點回顧
● 模擬偏離接口規範的系統失效方式。調用真正的應用程序,僅能測試真實應用程序刻意生成的那些錯誤。優秀的考驗機可以讓你模擬現實世界中的各種混亂的系統失效方式。
● 給調用方施加壓力。考驗機能產生緩慢響應和垃圾響應,甚至不響應。這樣一來,就可以看看應用程序如何反應。
● 利用共享框架處理常見系統失效問題。對每個集成點來說,不一定需要有單獨的考驗機。考驗機這個“殺手”服務器可以監聽多個端口,並根據你所連接的端口創建不同的系統失效方式。
● 考驗機僅是補充,不能取代其他測試方法。考驗機模式是對其他測試方法的補充和完善。它並不能取代單元測試、驗收測試、滲透測試等(這些測試都有助於驗證系統的功能性行爲)。考驗機有助於驗證非功能性行爲,同時又與遠程系統保持隔離。

九、中間件解耦

松耦合、緊耦合
同步、異步

● 在最後責任時刻再做決定。在不對設計或架構進行大規模更改的情況下,大部分穩定性模式可以實施。但中間件解耦是架構決策,相關的實施會波及系統的每個部分。應該在最後責任時刻到來時,儘早做出這種幾乎不可逆轉的決策。
● 通過完全的解耦避免衆多系統失效方式。各臺服務器、層級和應用程序解耦得越徹底,集成點、層疊失效、響應緩慢和線程阻塞等問題就越少。應用程序解耦後,系統可以單獨更改其他應用程序的所有配件,因此也更具適應性。
● 瞭解更多架構,從中進行選擇。並非每個系統都必須看起來像是帶有關係數據庫的三層應用程序。多瞭解一些架構,並針對所面臨的問題選擇最佳的架構。

十、卸下負載

服務應該模仿TCP的做法:當負載過高時,就開始拒絕新的工作請求。這與快速失敗模式相關。

在系統或企業的內部,運用背壓機制會更有效(請參閱5.11節),這樣做有助於在同步耦合的服務中,維持均衡的請求吞吐量。在這種情況下,卸下負載模式可以作爲輔助措施。

● 無法滿足全世界的請求。無論基礎設施的規模有多大,也無論容量的擴展速度有多快,這個世界總是擁有超出系統容量極限的人數和設備數。當系統面對數量不受控制的需求時,一旦來自世界各地的請求瘋狂地湧來,系統就需要卸下負載。
● 通過卸下負載避免響應緩慢。響應緩慢可不是好事。讓系統的響應時間得到控制,而不是任其讓調用方超時。
● 將負載均衡器用作減震器。個別的服務實例可以通過向負載均衡器報告HTTP 503錯誤,獲得片刻喘息,而負載均衡器擅於快速地回收這些連接。

十一、背壓模式

每個性能問題都源於其背後的一個等待隊列,如套接字的監聽隊列、操作系統的運行隊列或數據庫的I/O隊列。
如果隊列無限長,那麼它就會耗盡所有可用的內存。隨着隊列長度的增加,完成隊列中某項工作的時間也會增加(類似“利特爾法則”)。因此,當隊列長度達到無窮大時,響應時間也會趨向無窮大。我們絕對不希望系統中出現無限長的隊列。
如果隊列的長度是有限的,那麼當隊列已滿且生產者仍試圖再塞入一個新請求時,必須立刻採取應對措施。即使要塞入的新請求很小,也沒有任何多餘的空間。
我們可以在以下情況中做出選擇。
❑ 假裝接受新請求,但實際上將其拋棄。
❑ 確實接受新請求,但拋棄隊列中的某一個請求。
❑ 拒絕新請求。
❑ 阻塞生產者,直至隊列出現空的位置。
對於某些使用場景,拋棄新請求可能是最佳選擇。對於那些隨着時間的推移價值迅速降低的數據,拋棄隊列中最先發出的請求可能是最佳選擇。
阻塞生產者是一種流量控制手段,允許隊列向發送數據包的上游系統實施“背壓”措施。有可能這個背壓措施會一直傳播到最終的客戶端,而這個客戶端會降低請求發送的速度,直到隊列出現空位置。
TCP在每個數據包中都採用額外的字段構建背壓機制。一旦接收方的窗口已滿,發送方就不得發送任何內容,直至窗口被釋放。來自TCP接收方窗口的背壓,會讓發送方填滿其發送緩衝區,這時後續寫入套接字的調用將被阻塞。發送方與接收方的機制有所不同,但做法仍然是讓發送方放慢速度,直至接收方處理完“手上堆積的工作”。
顯然,背壓機制會導致線程阻塞。將兩種由臨時狀態導致的背壓,和消費者運行中斷導致的背壓區別開來極爲重要。背壓機制最適合異步調用和編程,如果編程語言支持,可以利用許多Rx框架、actor或channel工具實現這個機制。
當消費者的緩衝池容量有限時,背壓機制就只能對負載進行管理。原因在於,發送數據包的各個上游系統千差萬別,無法對其施加系統性的影響。可以用一個例子來說明這一點,假設系統提供了一個API,能讓用戶在特定位置創建“標籤”。而使用該API的上游,就包括大批手機應用程序和Web應用程序。
在系統內部,創建新標籤併爲其創建索引的速度是確定的,並且會受到存儲和索引技術的限制。當上遊調用“創建標籤”的速率超過存儲引擎的處理上限時,會發生什麼情況?調用會變得越來越慢。如果沒有背壓機制,這會導致處理速度逐漸減慢,直至該API與離線無異。
然而,可以利用一個阻塞隊列構建背壓機制,實現“創建標籤”的調用。假設每臺API服務器允許100個調用同時訪問存儲引擎。當第101個調用抵達API服務器時,調用線程將被阻塞,直至隊列中空出一個位置,這種阻塞就是背壓機制。API服務器不能超出額定速度對存儲引擎發起調用。
在這種情況下,限制每臺服務器僅接受100個調用,這樣處理過於粗糙。這意味着有可能一臺API服務器有被阻塞的線程,而另一臺服務器隊列中卻有空閒位置。爲了令其更加智能化,可以讓API服務器發起任意多次調用,但將阻塞置於接收端。這種情況下,就必須在現有的存儲引擎外面包裹一個服務,進而接收調用、度量響應時間並調整其內部隊列長度,實現吞吐量的最大化,保護存儲引擎。
然而在某些時候,API服務器仍然會有一個等待調用的線程。正如4.5節所述,被阻塞的線程會快速引發系統失效。當跨越系統邊界時,被阻塞的線程會阻礙用戶使用,或引發反覆重試操作。因此,在系統邊界內運用背壓機制效果最好。而在系統邊界之間,還是需要使用卸下負載模式和異步調用。
在上面的示例中,API服務器應該用一個線程池接受調用請求,然後用另一組線程向存儲引擎發出後續的出站調用。這樣一來,當後續出站調用阻塞時,前面請求處理的線程就可以超時、解除阻塞並回應HTTP 503錯誤狀態碼。或者,API服務器可以丟棄隊列中的一個“創建標籤”命令,以便進行索引,此時返回HTTP 202狀態碼(表示“請求已接受,但尚未處理”)更爲合適。
系統邊界內的消費者,會以性能問題或超時的方式再現背壓過程。事實上,這確實表明了一個真實的性能問題,消費者集體產生了超出提供者所能處理的負載!儘管如此,有時提供者也情有可原。儘管它有足夠的容量應對“正常”的流量,但碰上一個消費者瘋狂地發送請求,這可能源於自黑式攻擊或者僅是網絡流量模式自身發生了變化。
當背壓機制生效時,需要通知監控系統,從而判斷背壓是隨機波動還是大體趨勢。
要點回顧
● 背壓機制通過讓消費者放慢工作來實現安全性。消費者的處理速度終究會減慢,此時唯一能做的就是讓消費者“提醒”提供者,不要過快地發送請求。
● 在系統邊界內運用背壓機制。如果是跨越系統邊界的情況,就要換用卸下負載模式,當用戶羣是整個互聯網時更應如此。
● 要想獲得有限的響應時間,就需要構建有限長度的等待隊列。當等待隊列已滿時只有以下選擇(雖然都不令人愉悅):丟棄數據,拒絕工作或將其阻塞。消費者必須當心,不要永久阻塞。

十二、調速器

● 放慢自動化工具的工作速度,以便人工干預。當事情的發展即將脫離控制時,我們經常會發現自動化工具會像“將油門踩到底”似的將事情搞砸。因爲人類更擅長情景思維,所以我們需要創造機會親身參與其中。
● 在不安全的方向上施加阻力。有些行爲本身是不安全的。關機、刪除、阻塞……這些都可能會中斷服務。自動化工具將迅速執行這些操作,所以應該使用一個調速器,讓人們獲得時間來干預。
● 考慮使用響應曲線。在規定範圍內操作就是安全的。但如果在該範圍之外,行動就應該遇到相應的阻力,以減緩速度。

十三、總結

系統失效是不可避免的。我們的系統及其所依賴的系統,將會以大大小小的方式失效。穩定性的反模式放大了瞬態事件,它們會加速裂紋的蔓延。避免反模式雖然不能防止壞事發生,但當災禍來臨時,這樣做有助於將損害降到最低。
無論面臨什麼困難,只要明智地運用本章所描述的穩定性模式,就會令軟件始終保持運行。正確的判斷是成功地運用這些模式的關鍵。因此,要本着不信有好事的原則審查軟件的需求;以懷疑和不信任的眼光審視其他企業系統,防備這些系統在你背後“捅刀子”;識別這些威脅,並運用與每種威脅相關的穩定性模式;“迫害妄想”般的自我保護也是不錯的工程實踐。

第16章

二、過程和組織

開發和運維之間的邊界不僅已經模糊,而且已經被全部打亂並重新組合了。這種狀況甚至在DevOps這個詞廣爲人知之前就已經出現了(參閱本節框注)。虛擬化和雲計算的興起,實現了基礎設施的可編程性。開源運維工具同樣實現了運維工作的可編程性。虛擬機鏡像以及後來的容器和unikernel的出現,意味着程序都變成了“操作系統”。

回顧第7章介紹的各個層級,可以發現整個層級棧都需要軟件開發。

同樣,這個層級棧也需要整體運維。現在,基礎設施(過去由運維團隊負責)中出現了大量的可編程組件,這些基礎設施發展成了平臺,其他軟件都能在這個平臺上運行。無論是在雲上還是在自己的數據中心裏,都需要有一個平臺團隊,將應用程序開發團隊視作其客戶,爲應用程序所需的常用功能以及第10章介紹的控制層工具提供API和命令行配置。

這個列表很長,而且隨着時間的推移會變得更長。雖然其中的每一項都可以由單個團隊自行構建,但若將它們相互孤立起來,那就毫無價值了。平臺團隊要記住,當前實施的機制,應該允許其他團隊自行配置,這一點很重要。換句話說,平臺團隊不應該實施各個應用程序開發團隊特定的監控規則。相反,平臺團隊應該爲應用程序開發團隊提供API,使其能在平臺提供的監控服務上,實施自己的監控規則。同樣,平臺團隊也不會爲各個應用程序開發團隊構建API網關,而是構建一種服務,各個應用程序開發團隊能夠利用該服務構建各自的API網關。

(平臺團隊與應用程序開發團隊的職責分工)

平臺團隊要記住,當前實施的機制,應該允許其他團隊自行配置,這一點很重要。換句話說,平臺團隊不應該實施各個應用程序開發團隊特定的監控規則。相反,平臺團隊應該爲應用程序開發團隊提供API,使其能在平臺提供的監控服務上,實施自己的監控規則。同樣,平臺團隊也不會爲各個應用程序開發團隊構建API網關,而是構建一種服務,各個應用程序開發團隊能夠利用該服務構建各自的API網關。

必須讓應用程序開發團隊,而不是平臺團隊,負責應用程序的可用性。相反,平臺的可用性是衡量平臺團隊的標準。

平臺團隊需要以聚焦客戶爲導向,其客戶就是應用程序開發人員。這與舊的開發團隊與運維團隊的劃分理念截然不同。

四、在團隊級別實現自治

亞馬遜公司創始人兼首席執行官Jeff Bezos的“兩個比薩團隊”規則:每個團隊的規模不應該超過能被兩張大號比薩餵飽的人數。
但“兩個比薩團隊”不僅僅是減少團隊人數的問題,而是需要減少團隊的外部依賴,減少各種上下游的評審和審批,包括架構設計、發佈管理、變更管理、

這個概念不僅僅是指爲一個項目分配幾個編程人員,這實際上是如何讓一個小組能夠實現自給自足,並能將成果一直推入生產環境的問題。縮小每個團隊的規模,需要大量的工具和基礎設施的支持。諸如防火牆、負載均衡器和存儲區域網絡等專業硬件,都必須有API包裹在其周圍,這樣每個團隊才能管理自己的配置,而不會對其他人造成嚴重破壞。16.2.1節所討論的平臺團隊,此時就能扮演重要角色。平臺團隊的目標,必須是實現和促進上述團隊規模的自治。

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