讀書筆記《發(fā)布!設(shè)計與部署穩(wěn)定的分布式系統(tǒng)》

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

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

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

以下為摘抄及讀書筆記,主要為反模式與模式相關(guān)部分。

第1章 生產(chǎn)環(huán)境的生存法則

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

第一部分

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

本章節(jié)分享了一個實際的故障案例的發(fā)現(xiàn)、處理過程、事后分析,最后找到的原因竟然是一個未被捕獲的 SQLException 異常。

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

第3章 讓系統(tǒng)穩(wěn)定運行

當構(gòu)建系統(tǒng)的架構(gòu)、設(shè)計甚至底層實現(xiàn)時,許多決策點對系統(tǒng)的最終穩(wěn)定性具有很大的影響力。而面對這些決策點,高度穩(wěn)定的設(shè)計與不穩(wěn)定的設(shè)計投入的成本通常是相同的。

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

第4章 穩(wěn)定性的反模式

一、集成點

集成點是系統(tǒng)的頭號殺手。每一個傳入的連接都存在穩(wěn)定性風險。每個套接字、進程、管道或RPC都會停止響應(yīng)。即使是對數(shù)據(jù)庫的調(diào)用,也可能會以明顯而微妙的方式停止響應(yīng)。系統(tǒng)收到的每一份數(shù)據(jù),都可能令系統(tǒng)停止響應(yīng)、崩潰,甚至產(chǎn)生其他的沖擊。

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

二、同層連累反應(yīng)

這種情況對應(yīng)于通過負載均衡器實現(xiàn)水平擴展的集群。
當負載均衡組中的一個節(jié)點發(fā)生故障時,其他節(jié)點必須額外分擔該節(jié)點的負載。

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

  1. 一臺服務(wù)器的停機會波及其余的服務(wù)器
  2. 一臺服務(wù)器的內(nèi)存泄漏的停機會導致其他服務(wù)器的負載增加,增加的負載又會加速其他服務(wù)的內(nèi)存泄露速度
  3. 一臺服務(wù)器陷入死鎖,其他服務(wù)器所增加的負載會導致它們也更容易陷入死鎖
  4. (措施)采用自動擴展。應(yīng)該為云端每個自動擴展組創(chuàng)建健康檢查機制。自動擴展將關(guān)閉未通過健康狀況檢查的服務(wù)器實例,并啟動新的實例。只要自動擴展機制的響應(yīng)速度比同層連累反應(yīng)的蔓延速度快,那么系統(tǒng)服務(wù)就依然可用。
  5. 利用“艙壁模式”進行保護

三、層疊失效

原因:

  1. 內(nèi)存泄露
  2. 某個組件超負荷運行
  3. 依賴的數(shù)據(jù)庫失效
  4. 等等

層疊失效有一個將系統(tǒng)失效從一個層級傳到另一個層級的機制。

層疊失效通常源于枯竭的資源池。資源池枯竭的原因往往是較低層級所發(fā)生的系統(tǒng)失效。沒有設(shè)置超時時間的集成點,必定會導致層疊失效。

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

總結(jié):

阻止裂紋跨層蔓延。當裂紋從一個系統(tǒng)或?qū)蛹壧搅硪粋€系統(tǒng)或?qū)蛹墪r,會發(fā)生層疊失效。這通常是因為集成點沒有完善自我防護措施。較低層級中的同層連累反應(yīng)也可能引發(fā)層疊失效。一個系統(tǒng)肯定需要調(diào)用其他系統(tǒng),但當后者失效時,需要確保前者能夠保持運轉(zhuǎn)。

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

用超時模式和斷路器模式實現(xiàn)保護。層疊失效在其他系統(tǒng)已經(jīng)出現(xiàn)故障之后發(fā)生。斷路器模式通過避免向已經(jīng)陷入困境的集成點發(fā)出調(diào)用請求,進而保護系統(tǒng)。使用超時模式,可以確保對有問題的集成點的調(diào)用能及時返回。

四、用戶

一、網(wǎng)絡(luò)流量

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

    image

    解決辦法:

    1. 盡可能少保留內(nèi)存中的會話

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

      image

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

  2. 堆外內(nèi)存和主機外內(nèi)存
    redis、memcache

  3. 服務(wù)器上的套接字數(shù)量
    一般人可能不太會關(guān)注服務(wù)器上的套接字數(shù)量。但在流量過大時,這也可能會造成限制。每個處于活動狀態(tài)的請求都對應(yīng)著一個開放式套接字,操作系統(tǒng)會將進入的連接,分配給代表連接接收端的“臨時”端口。如果查看TCP數(shù)據(jù)包的格式,就能看到端口號長16位,這表示端口號最大只能到65535。不同的操作系統(tǒng)會對臨時套接字使用不同的端口范圍,但互聯(lián)網(wǎng)數(shù)字分配機構(gòu)的建議范圍是49152~65535。這樣一來,服務(wù)器最多可以打開16383個連接。但是機器可能用來處理專門的服務(wù),而不是如用戶登錄這樣的通用服務(wù),所以可以將端口范圍擴展為1024~65535,這樣最多可以有64511個連接。
    然而,一些服務(wù)器能夠處理100多萬個并發(fā)連接。有人會把上千萬的連接推給單獨一臺服務(wù)器。
    如果只有64511個端口可用于連接,那么一臺服務(wù)器如何能有100萬個連接?秘訣在于虛擬IP地址。操作系統(tǒng)將多個IP地址綁定到同一個網(wǎng)絡(luò)接口。每個IP地址都有自己的端口號范圍,所以要處理上百萬個連接,總共只需要16個IP地址
    這不是一個容易解決的問題。應(yīng)用程序可能需要進行一些更改,從而監(jiān)聽多個IP地址,處理它們之間的連接,并且保證所有監(jiān)聽隊列的正常運行。100萬個連接也需要很多內(nèi)核緩沖區(qū)。此時需要花一些時間,了解操作系統(tǒng)的TCP調(diào)優(yōu)參數(shù)。

  4. 已關(guān)閉的套接字
    TIME_WAIT

二、 難伺候的用戶

—— 真正下單的用戶,這些用戶相對而言涉及的事務(wù)處理更多更復(fù)雜(從下單到結(jié)算等等),所以需要關(guān)注這些用戶相關(guān)的操作,因為他們時網(wǎng)站創(chuàng)造收入的來源。

通常零售系統(tǒng)的轉(zhuǎn)化率為2%左右,所以我們可以按4%或者6%或者10%進行系統(tǒng)負載測試。

三、不受歡迎的用戶

可能是爬蟲之類的程序產(chǎn)生的請求,這些請求可能會對系統(tǒng)產(chǎn)生較大的負載。

解決辦法:

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

四、不受歡迎的用戶

惡意攻擊的用戶,即黑客

五、線程阻塞

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

● 仔細檢查資源池。就像層疊失效一樣,線程阻塞反模式通常發(fā)生在資源池(特別是數(shù)據(jù)庫連接池)周圍。數(shù)據(jù)庫內(nèi)發(fā)生死鎖以及不當?shù)漠惓L幚矸绞綍斐蛇B接永久丟失。

● 使用經(jīng)過驗證的元操作。學習并使用安全的元操作。形成自己的生產(chǎn)者-消費者隊列看似很容易,但事實并非如此。相比新形成的隊列系統(tǒng),任何并發(fā)實用程序庫都執(zhí)行了多重測試。

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

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

六、自黑式攻擊

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

● 保持溝通渠道暢通。自黑式攻擊發(fā)源于組織內(nèi)部,人們通過制造自己的“快閃族”行為和流量高峰,加重對系統(tǒng)本身的傷害。這時,可以同時幫助和支持這些營銷工作并保護系統(tǒng),但前提是要知道將會發(fā)生的事情。確保沒有人發(fā)送大量帶有深層鏈接的電子郵件,可以將大量的電子郵件分批陸續(xù)發(fā)送,分散高峰負載。針對首次點擊優(yōu)惠界面的操作,創(chuàng)建一些靜態(tài)頁面作為“登陸區(qū)域”。注意防范URL中嵌入的會話ID。

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

● 快速地重新分配實惠的優(yōu)惠。任何一個認為能限量發(fā)布特惠商品的人,都在自找麻煩。根本就沒有限量分配這回事。即使限制了一個超劃算的特惠商品可以購買的次數(shù),系統(tǒng)仍然會崩潰。

七、放大效應(yīng)

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

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

在開發(fā)環(huán)境中,每個應(yīng)用程序都只在一臺機器上運行。在測試環(huán)境中,幾乎每個應(yīng)用程序都只安裝在一兩臺機器上。然而,當?shù)搅松a(chǎn)環(huán)境時,一些應(yīng)用程序看起來非常小,另一些應(yīng)用程序則是中型、大型或超大型的。由于開發(fā)環(huán)境和測試環(huán)境的規(guī)模很少會與生產(chǎn)環(huán)境一致,因此很難在前兩個環(huán)境中,看到放大效應(yīng)跳出來“咬人”。

  1. 點對點通信

    image

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

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

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

八、失衡的系統(tǒng)容量

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

● 檢查服務(wù)器和線程的數(shù)量。在開發(fā)環(huán)境和QA環(huán)境中,系統(tǒng)可能看起來在一兩臺服務(wù)器上運行,其調(diào)用的其他系統(tǒng)的所有QA環(huán)境也是如此。然而在生產(chǎn)環(huán)境中,比例更有可能是10比1,而不是1比1。要將QA環(huán)境和生產(chǎn)環(huán)境對照起來,檢查前端服務(wù)器與后端服務(wù)器的數(shù)量比,以及兩端所能處理的線程的數(shù)量比。

● 密切觀察放大效應(yīng)和用戶行為。失衡的系統(tǒng)容量是放大效應(yīng)的特例:關(guān)系中一方的增幅變化大大超過另一方。季節(jié)性、市場驅(qū)動或宣傳驅(qū)動等流量模式的變化,會導致前端系統(tǒng)的大量請求涌向后端系統(tǒng)(通常是良性的),就像熱門的社交媒體帖子導致網(wǎng)站流量劇增。

● 實現(xiàn)QA環(huán)境虛擬化并實現(xiàn)擴展。即使生產(chǎn)環(huán)境的規(guī)模是固定的,也不要僅只使用兩臺服務(wù)器運行QA環(huán)境。擴展測試環(huán)境,嘗試在調(diào)用方和服務(wù)提供方的規(guī)模擴展到不同比例的環(huán)境中運行測試用例,這些應(yīng)該能夠通過數(shù)據(jù)中心的自動化工具自動實現(xiàn)。

● 重視接口的兩側(cè)。如果所開發(fā)的是后端系統(tǒng),假如系統(tǒng)突然收到10倍于歷史最高的請求數(shù)量,并且都涌向了系統(tǒng)開銷最大的事務(wù)部分,將會發(fā)生什么?系統(tǒng)完全失效了嗎?它是先慢下來然后又恢復(fù)了正常嗎?如果所開發(fā)的是前端系統(tǒng),那么就需要看一看當后端系統(tǒng)在被調(diào)用時停止了響應(yīng),或變得非常慢的時候,前端系統(tǒng)會發(fā)生什么。

九、一窩蜂

系統(tǒng)達到穩(wěn)態(tài)時的負載,會與系統(tǒng)啟動或周期性運行的負載存在明顯不同。想象一個應(yīng)用程序服務(wù)器農(nóng)場的啟動過程,每臺服務(wù)器都需要連接到數(shù)據(jù)庫,并加載一定數(shù)量的參考數(shù)據(jù)或種子數(shù)據(jù)。每臺服務(wù)器的緩存都從空閑狀態(tài)開始,逐漸形成一個有用的工作集。到那時,大多數(shù)HTTP請求會轉(zhuǎn)換為一個或多個數(shù)據(jù)庫查詢。這意味著當應(yīng)用程序啟動時,數(shù)據(jù)庫上的瞬時負載要比運行一段時間后的負載高得多。

一堆服務(wù)器一同對數(shù)據(jù)庫施加瞬時負載,這被稱為“一窩蜂”。

引發(fā)一窩蜂現(xiàn)象的幾種情況如下。
? 在代碼升級和重新運行之后,啟動多臺服務(wù)器。
? 午夜(或任何一個整點時間)觸發(fā)cron作業(yè)。
? 配置管理系統(tǒng)推出變更。

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

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

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

● 一窩蜂所需系統(tǒng)成本過高,高峰需求無法處理。一窩蜂是對系統(tǒng)的集中使用,相比將峰值流量分散開后所需的系統(tǒng)能力,一窩蜂需要一個更高的系統(tǒng)容量峰值?!?使用隨機時鐘擺動以分散需求。不要將所有cron作業(yè)都設(shè)置在午夜或其他任何整點時間執(zhí)行。用混合的方式設(shè)置時間,分散負載。
● 使用增加的退避時間避免脈沖。固定的重試時間間隔,會集中那段時間的調(diào)用方需求。相反,使用退避算法,不同調(diào)用方在經(jīng)過自己的退避時間后,在不同的時間點發(fā)起調(diào)用。

十、做出誤判的機器

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

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

可以在控制層軟件中實現(xiàn)類似的防護措施。
? 如果軟件觀測器顯示系統(tǒng)中80%以上的部分不可用,那么與系統(tǒng)出問題相比,軟件觀測器出問題的可能性更大。
? 運用滯后原則,快速啟動機器,但要慢慢關(guān)機,啟動新機器要比關(guān)閉舊機器更安全。
? 當期望狀態(tài)與觀測狀態(tài)之間的差距很大時,要發(fā)出確認信號,這相當于工業(yè)機器人上的大型黃色旋轉(zhuǎn)警示燈在報警。
? 那些消耗資源的系統(tǒng)應(yīng)該設(shè)計成有狀態(tài)的,從而檢測它們是否正在試圖啟動無限多個實例。
? 構(gòu)建減速區(qū)域,緩解勢能。假想一下,控制層雖然每秒都能感知到系統(tǒng)已經(jīng)過載,但它啟動一臺虛擬機處理負載需要花費5分鐘。所以在大量負載依然存在的情況下,要確??刂茖硬粫?分鐘內(nèi)啟動300個虛擬機。

● 在造成一片狼藉之前尋求幫助?;A(chǔ)設(shè)施管理工具可以迅速對系統(tǒng)產(chǎn)生巨大的影響,要在其內(nèi)部構(gòu)建限制器和防護措施,防止其快速毀掉整個系統(tǒng)。
● 注意滯后時間和勢能。由自動化所引發(fā)的操作,需要花時間才能完成。這段時間通常會比監(jiān)控的時間間隔要長,因此請務(wù)必考慮到系統(tǒng)需要經(jīng)過一些延遲后,才能對操作做出響應(yīng)。
● 謹防幻想和迷信??刂葡到y(tǒng)會感知環(huán)境,但它們有可能被愚弄。它們會計算出一個預(yù)期狀態(tài),并形成有關(guān)當前狀態(tài)的“信念”,但這兩者都有可能是錯誤的。

十一、緩慢的響應(yīng)

生成響應(yīng)較慢比拒絕連接或返回錯誤更糟,在中間層服務(wù)中尤為如此。

快速返回系統(tǒng)失效信息,能使調(diào)用方的系統(tǒng)快速完成事務(wù)處理,最終成功或是系統(tǒng)失效,取決于應(yīng)用程序的邏輯。但是,緩慢的響應(yīng)會將調(diào)用系統(tǒng)和被調(diào)用系統(tǒng)中的資源拖得動彈不得。

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

緩慢的響應(yīng)傾向于逐層向上傳播,并逐漸導致層疊失效。

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

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

十二、無限長的結(jié)果集

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

第5章 穩(wěn)定性的模式

一、超時

良好的超時機制可以提供失誤隔離功能——其他服務(wù)或設(shè)備中出現(xiàn)的問題不一定會成為你的問題。

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

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

● 將超時模式應(yīng)用于集成點、阻塞線程和緩慢響應(yīng)。超時模式可以防止對集成點的調(diào)用轉(zhuǎn)變?yōu)閷ψ枞€程的調(diào)用,從而避免層疊失效。
● 采用超時模式,從意外系統(tǒng)失效中恢復(fù)。當操作時間過長,有時無須明確其原因時,只需要放棄操作并繼續(xù)做其他事。超時模式可以幫助我們實現(xiàn)這一點。
● 考慮延遲重試。大多數(shù)超時原因涉及網(wǎng)絡(luò)或遠程系統(tǒng)中的問題。這些問題不會立即被解決。立即重試很可能會遭遇同樣的問題,并導致再次超時。這只會讓用戶等待更長的時間才能看到錯誤消息。大多數(shù)情況下,應(yīng)該把操作任務(wù)放入隊列,稍后再重試。

二、斷路器

image

斷路器能有效防止集成點、層疊失效、系統(tǒng)容量失衡和響應(yīng)緩慢等危及穩(wěn)定性的反模式出現(xiàn),它能與超時模式緊密協(xié)作,跟蹤調(diào)用超時失?。▍^(qū)別于調(diào)用執(zhí)行失?。?。

三、艙壁

船舶的艙壁是一些隔板,一旦將其密封起來,就能將船分隔成若干獨立的水密隔艙。在艙壁口關(guān)閉的情況下,艙壁可以防止水從一個部分流到另一個部分。通過這種方式,船體即使被洞穿一次也不會沉沒。艙壁這種設(shè)計強調(diào)了控制損害范圍的原則。
在軟件開發(fā)中也可以使用相同的技術(shù)。通過使用隔板對系統(tǒng)進行分區(qū),就可以將系統(tǒng)失效控制在其中某個分區(qū)內(nèi),而不會令其摧毀整個系統(tǒng)。物理冗余是實現(xiàn)艙壁最常見的形式。如果系統(tǒng)有4臺獨立的服務(wù)器,那么其中一個硬件所出現(xiàn)的失效,就不會影響其他服務(wù)器。同樣,在一臺服務(wù)器上運行兩個應(yīng)用程序?qū)嵗绻渲幸粋€崩潰,那么另一個仍將繼續(xù)運行。(當然,除非讓第1個應(yīng)用程序?qū)嵗罎⒌哪欠N外部影響力也能讓第2個應(yīng)用程序?qū)嵗罎ⅰ#?/p>

四、穩(wěn)態(tài)

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

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

● 避免擺弄。人為干預(yù)生產(chǎn)環(huán)境會導致問題。要消除對生產(chǎn)環(huán)境重復(fù)進行人為干預(yù)的需求。系統(tǒng)應(yīng)在無須手動清理磁盤或每晚重新啟動的情況下,至少運行一個發(fā)布周期。
● 清除帶有應(yīng)用程序邏輯的數(shù)據(jù)。DBA可以通過創(chuàng)建腳本清除數(shù)據(jù),但他們不知道刪除數(shù)據(jù)之后,應(yīng)用程序會如何運轉(zhuǎn)。為了保持邏輯完整性(特別是在使用對象關(guān)系映射工具時),應(yīng)用程序需要清除自己的數(shù)據(jù)。
● 限制緩存。內(nèi)存中的緩存可以加快應(yīng)用程序的運行速度。但若不對內(nèi)存中的緩存加以控制,執(zhí)行速度仍會降低。需要限制緩存可消耗的內(nèi)存量。
● 滾動日志。不要無限量保留日志文件?;谌罩疚募拇笮砼渲萌罩疚募剞D(zhuǎn)。如果合規(guī)性要求保留,則在非生產(chǎn)服務(wù)器上執(zhí)行。

五、快速失敗

即使在快速失敗時,也要確保用不同的方式報告系統(tǒng)性失效(資源不可用)和應(yīng)用程序失?。▍?shù)違規(guī)或無效狀態(tài))。否則,哪怕僅是用戶輸入了錯誤數(shù)據(jù)并點擊了三四次重新加載,若報告成不具分辨度的一般性錯誤,也可能導致上游系統(tǒng)引發(fā)斷路器跳閘??焖偈∧J酵ㄟ^避免響應(yīng)緩慢來提高整個系統(tǒng)的穩(wěn)定性。與超時模式配合使用,快速失敗模式有助于避免層疊失效。當系統(tǒng)由于部分失效而面臨壓力時,快速失敗模式還有助于保持系統(tǒng)容量。

為了讓“任其崩潰并替換”卓有成效,系統(tǒng)要具備幾個前提。

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

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

六、任其崩潰并替換

有時,為了實現(xiàn)系統(tǒng)級穩(wěn)定性,放棄組件級穩(wěn)定性就是所能做的最好的事情了。在Erlang語言中,這被稱為“任其崩潰并替換”的哲學。

程序所能擁有的最干凈的狀態(tài),就是在剛剛完成啟動的那一刻。任其崩潰并替換的方法認為錯誤恢復(fù)難以完成且不可信賴,所以我們的目標應(yīng)該是盡快回到剛完成啟動時的干凈狀態(tài)。

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

七、握手

當失衡的系統(tǒng)容量導致響應(yīng)緩慢時,“握手”可能是最有價值的。如果服務(wù)器檢測到自己不能滿足其SLA要求,那么它應(yīng)該通過一些方法請求調(diào)用方停止進程。如果服務(wù)器位于負載均衡器之后,那么它們可以使用“開關(guān)”控制,來“開啟或停止”對負載均衡器的響應(yīng),然后負載均衡器可以將無響應(yīng)的服務(wù)器移出負載均衡池。不過,這是一種粗放的機制,最好在執(zhí)行的所有自定義協(xié)議中構(gòu)建握手機制。
當調(diào)用缺乏握手機制的服務(wù)時,斷路器是一種可以使用的權(quán)宜之計。在這種情況下,無須禮貌地詢問服務(wù)器能否處理請求,只須發(fā)起調(diào)用并跟蹤調(diào)用是否有效??傮w而言,握手是一種未被充分利用的技術(shù),在應(yīng)用層協(xié)議中擁有巨大的優(yōu)勢。在層疊失效情況下,握手是一種防止裂紋跨層蔓延的有效方法。

● 創(chuàng)建基于合作的需求控制機制??蛻舳撕头?wù)器之間的握手,允許將需求的流量調(diào)節(jié)到可服務(wù)的級別。在構(gòu)建客戶端和服務(wù)器時,兩者都必須實現(xiàn)握手。而大多數(shù)最常見的應(yīng)用程序級協(xié)議,并沒有實現(xiàn)握手。
● 考慮健康狀況檢查。在集群或負載均衡服務(wù)中,使用健康狀況檢查實現(xiàn)實例與負載均衡器握手。
|● 在自己的低層協(xié)議中構(gòu)建握手。如果創(chuàng)建了基于套接字的協(xié)議,那么可以在其中構(gòu)建握手機制。這樣一來,端點就可以在未準備好接受工作時,通知其他端點。

八、考驗機

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

模擬依賴系統(tǒng)的失效行為來測試集成系統(tǒng)的一些無法驗證的行為。

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

混沌工程

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

九、中間件解耦

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

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

十、卸下負載

服務(wù)應(yīng)該模仿TCP的做法:當負載過高時,就開始拒絕新的工作請求。這與快速失敗模式相關(guān)。

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

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

十一、背壓模式

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

十二、調(diào)速器

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

十三、總結(jié)

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

第16章

二、過程和組織

開發(fā)和運維之間的邊界不僅已經(jīng)模糊,而且已經(jīng)被全部打亂并重新組合了。這種狀況甚至在DevOps這個詞廣為人知之前就已經(jīng)出現(xiàn)了(參閱本節(jié)框注)。虛擬化和云計算的興起,實現(xiàn)了基礎(chǔ)設(shè)施的可編程性。開源運維工具同樣實現(xiàn)了運維工作的可編程性。虛擬機鏡像以及后來的容器和unikernel的出現(xiàn),意味著程序都變成了“操作系統(tǒng)”。

回顧第7章介紹的各個層級,可以發(fā)現(xiàn)整個層級棧都需要軟件開發(fā)。

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

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

(平臺團隊與應(yīng)用程序開發(fā)團隊的職責分工)

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

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

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

四、在團隊級別實現(xiàn)自治

亞馬遜公司創(chuàng)始人兼首席執(zhí)行官Jeff Bezos的“兩個比薩團隊”規(guī)則:每個團隊的規(guī)模不應(yīng)該超過能被兩張大號比薩喂飽的人數(shù)。
但“兩個比薩團隊”不僅僅是減少團隊人數(shù)的問題,而是需要減少團隊的外部依賴,減少各種上下游的評審和審批,包括架構(gòu)設(shè)計、發(fā)布管理、變更管理、

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

本博客(liqipeng)除非已明確說明轉(zhuǎn)載,否則皆為liqipeng原創(chuàng)或者整理,轉(zhuǎn)載請保留此鏈接:https://www.cnblogs.com/liqipeng/p/15377850.html


如果你覺得這篇文章對你有幫助或者使你有所啟發(fā),請點擊右下角的推薦按鈕,謝謝,:)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容