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

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

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

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

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

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

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

第一部分

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

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

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

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

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

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

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

一、集成點

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

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

二、同層連累反應

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

三、層疊失效

原因:

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

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

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

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

總結:

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

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

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

四、用戶

一、網(wǎng)絡流量

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

    image

    解決辦法:

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

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

      image

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

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

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

  4. 已關閉的套接字
    TIME_WAIT

二、 難伺候的用戶

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

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

三、不受歡迎的用戶

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

解決辦法:

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

四、不受歡迎的用戶

惡意攻擊的用戶,即黑客

五、線程阻塞

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

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

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

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

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

六、自黑式攻擊

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

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

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

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

七、放大效應

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

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

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

  1. 點對點通信

    image

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

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

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

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

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

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

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

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

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

九、一窩蜂

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

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

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

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

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

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

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

十、做出誤判的機器

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

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

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

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

十一、緩慢的響應

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

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

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

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

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

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

十二、無限長的結果集

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

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

一、超時

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

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

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

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

二、斷路器

image

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

三、艙壁

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

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

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

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

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

五、快速失敗

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

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

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

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

六、任其崩潰并替換

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

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

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

七、握手

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

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

八、考驗機

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

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

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

混沌工程

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

九、中間件解耦

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

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

十、卸下負載

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

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

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

十一、背壓模式

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

十二、調速器

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

十三、總結

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

第16章

二、過程和組織

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

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

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

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

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

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

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

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

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

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

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

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


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

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容