后端工程師都應該知道的最佳實踐


0 前言

《On Designing and Deploying Internet-Scale Services》是一篇非常經典的論文,例舉了設計和部署互聯網規模的服務要注意的方方面面,其核心內容是自動化、輕依賴、可監控且信息准確、可應急

上一篇內容翻譯了《On Designing and Deploying Internet-Scale Services》,本篇文章進行總結,對以下各個部分(忽略了硬件部分)的核心原則進行提煉,便於時常回顧自查。建議大家有時間都閱讀一些原文。

  1. 整體設計

  2. 自動化管理和配置

  3. 依賴管理

  4. 發布周期和測試

  5. 運維和容量規划

  6. 審計、監控和報警

  7. 優雅降級和准入控制

 

1 整體設計

面向失敗設計(design for failure)

  1. 大規模服務中機器故障是頻繁發生的(大規模服務中,機器數足夠多,那么每天都可能會發生磁盤、網絡等故障)

  2. 常規的機器故障不能依賴於人工介入恢復

  3. 故障恢復過程應該足夠簡單,且經常被測試

  4. 可以考慮永遠采用暴力關閉(比如kill -9)的方式停止服務,來檢驗故障恢復邏輯

冗余和故障恢復(redundancy and fault recovery)

  1. 任意服務器在任何時候出現故障不會影響服務的SLA

  2. 考慮每個組件,或者多個組件同時失敗時系統如何運行(明確系統可以接受的情況,比如強依賴DB,DB故障后系統不可用是預期內可接受的)

  3. 當集群足夠大的時候,多個組件同時故障的概率會大大提高,並且未來一定會發生

通用硬件(commodity hardware slice)

  1. 同等計算/存儲能力下,由普通服務器組成的大集群要比由大型服務器組成的小集群更便宜

  2. 大集群中普通服務器的故障比小集群中大型服務器故障造成的影響要小

單一版本軟件(single-version software)

  1. 保持單一版本的軟件,可以使運維成本降低

  2. 為了保持單一版本,需要保證在版本演進時不造成用戶體驗的損失(盡量要保持兼容)

多租戶(multi-tenancy)

  1. 讓多個租戶共享一個物理集群,而不是為每個租戶部署一個物理集群,這樣才能大大降低運維成本

快速的服務健康檢查(quick service health check)

  1. 提供一套快速的測試機制,一旦這個測試通過就能保證基礎的功能沒有問題,代碼可以執行Check In,這將大大提高開發效率(比如編寫足夠好的單元測試,保證每次check in代碼時這些測試都能運行通過,且這些測試保證了代碼基礎功能的正確性)

在完整環境下進行開發(develope in the full environment)

  1. 盡量保證能提供一個完成的環境給開發人員進行測試,因為開發人員需要確認變更代碼除了通過自己的測試外,還要不影響系統其它組件的功能(最好是能本機啟動一個完成的環境,這樣將大大提高測試效率)

零信任依賴組件(zero trust of underlying components)

  1. 考慮依賴的組件一定會掛掉,並考慮如何在它掛掉后提供服務,比如:

    1. 以只讀模式提供服務(降級)

    2. 繼續為未受影響的用戶提供服務(隔離)

不重復實現(do not build the same functionality in multi components)

  1. 避免在多個組件中實現相同的功能(保證代碼不重復、功能不重復)

一個節點或者集群不應該影響另一個節點或者集群(one pod or cluster should not affect another pod or cluster)

  1. 一個服務由若干個節點或者子集群組成,保證每個節點和集群的獨立性(避免雪崩)

  2. 當依賴不可避免時,盡量將依賴放到集群內部

允許人工應急干預(allow rare emergency human intervention)

  1. 將系統設計的盡量自動化,避免人工介入

  2. 但是人工介入是不可避免的,將人工介入的步驟制作成腳本程序,且這些程序要經常進行測試

  3. 需要在生產環境進行演練,沒有經過生產環境演練的程序在應急時是不可靠的

保持簡單和健壯(keep things simple and robust)

  1. 復雜的算法和組件交互會將調試和部署變得困難

  2. 一個總體的原則是:超過一個數量級的優化才值得去做,只有幾個百分點的提升和改進是不值得去做的

在所有層級進行准入控制(enforce admission control at all levels)

  1. 在服務入口設計准入控制,避免請求繼續進入已經過載的系統

  2. 在所有重要組件的入口都提供准入的控制(比如核心的異步處理邏輯,控制隊列的大小)

  3. 盡量提供優雅降級的能力,而不是直接無法提供服務

服務拆分(partition the service)

  1. 分區應該是無限可調的,不應該綁在任何物理世界的實體上,否則容易出現熱點問題

理解網絡(understand the network design)

  1. 在早期就去了解系統最終部署的物理架構,網絡架構,跨數據中心的情況等

分析吞吐和延遲(analyze throughput and latency)

  1. 分析關鍵操作的吞吐和延遲,了解這些操作對系統的影響

  2. 針對每個服務需要跟蹤這些指標來為擴容等操作提供依據

將工具作為系統的一部分(treat operations utilites as part of the service)

  1. 開發、測試、運維的工具都需要進行Code Review,跟代碼一起維護

  2. 通常這些工具非常重要但是又沒有被測試和維護

理解訪問模式(Understand access patterns)

  1. 當規划新特性時,一定要考慮它給后端存儲帶來的負載。

  2. 通常服務模型和服務開發者所在的抽象層次太高了,以至於他們無法注意到負載給底層存儲數據庫帶來怎樣的影響。

  3. 一個最佳實踐是SPEC(Standard Performance Evaluation Corporation,系統性能評估測試)加上一節:“這個feature對系統其它部分有什么影響?”然后feature上線時驗證負載的情況是否符合。

對一切進行版本化(version everything)

  1. 目標是只運行維護一個版本,但是發布、灰度過程會運行多個版本

  2. 保證N和N-1版本是兼容的

保留上一次發布的所有測試用例(keep the unit/functional tests from the last release)

  1. 這些測試用來保證上一個版本的特性沒有被破壞掉

  2. 持續性的在生產環境跑測試

避免單點失敗(avoid single points of failure)

  1. 優先采用無狀態的架構,保證水平擴展的能力

  2. 不要把請求和用戶綁定到特定的服務器上,采用負載均衡分配到一組服務器上

  3. 數據庫很難解決單點問題,要合理的拆分Partition

 

2 自動化管理和配置

在設計和部署之后進行服務的自動化是非常困難的。成功的自動化應當是簡單和清晰的,易於做出運維判斷的。這反過來又取決於服務的設計,必要的時候可以犧牲一些吞吐和延遲來達到自動化的目的。

  1. 所有的操作都需要支持重啟,所有的持久化數據都需要有備份

  2. 支持跨分區部署(geo-distribution)

  3. 自動化的安裝和配置

  4. 配置和代碼作為整體提交,將配置和代碼作為一個整體進行管理

  5. 意識到多系統故障是常態

  6. 始終堅持對非臨時的狀態數據進行備份

  7. 讓部署過程保持簡單

  8. 停掉數據中心,關閉機架,讓服務器掉電。定期引入人為故障,不斷暴露系統、網絡、服務中的弱點

 

3 依賴管理

有如下情況的依賴,那么依賴管理是有意義的:

  1. 依賴的組件很大或者很復雜

  2. 依賴的服務的價值在於它是單一中心實例的

第一種情況的實例是存儲和一致性算法的實現。第二種情況的實例是身份管理系統。這些系統的價值在於,他們是單一的共享實例,無法采用多實例避免這種依賴。

假設滿足以上條件,管理他們的最佳實踐如下:

  1. 考慮調用延遲:考慮外部調用的延遲,不要讓一個服務或者組件的延遲導致其他地方的延遲

  2. 考慮失敗隔離:架構需要能保持隔離,避免級聯故障

  3. 使用可靠的組件:使用穩定的版本,穩定版本總是比嘗鮮版本要好,哪怕新feature有多么的誘人

  4. 服務間的監控和告警:通過監控和告警,保證一個服務導致另一個服務過載的情況可以被發現

  5. 依賴雙方有一致的設計目標:被依賴組件的SLA需要和依賴者保持一致

  6. 模塊解耦:保證一個服務可以在依賴的服務故障時依舊提供服務,哪怕是降級了服務的能力(對於非強依賴的組件發生故障,要保證繼續提供服務,比如以只讀模式提供服務等)

 

4 發布周期和測試

推薦在新版本的服務經過標准的單元測試、功能測試和類生產環境的測試后,就進入到一個受限制的生產環境進行最后的測試。嚴格遵循以下規則:

  1. 生產環境要保證冗余,當新服務發生故障時能快速的恢復狀態

  2. 絕對不能破壞數據或狀態的一致性

  3. 錯誤必須能被檢測到,同時工程師團隊必須持續監控受測試代碼系統狀態

  4. 保證所有變更能被回滾,且回滾操作是經過驗證的

一些發布和測試相關的最佳實踐:

  1. 頻繁發布:有點反直覺,但是頻繁的發布可以避免大爆炸式的變更,建議發布周期最長不超過3個月,甚至做到按周發布

  2. 使用生產環境的數據發現問題:收集最原始的數據來反映系統的狀態,減少誤報,分析數據的趨勢;讓系統的健康狀況清晰可見——最好在自己的系統上就包含系統的健康狀況

  3. 在開發中投入更多精力:有些問題看似是運維問題,實際是開發設計的問題,提前做好開發和設計能減少運維問題;將更多的精力投入到設計和開發階段,避免問題在運維階段才被發現

  4. 支持回滾到特定版本:必須支持回滾到特定的版本來做應急

  5. 保持向前和向后兼容:保持兼容才能做回滾,否則將因為回滾后無法解析磁盤上的數據等問題導致故障

  6. 壓力測試:以兩倍的負載來對系統進行壓測(根據實際情況,以高於預期的負載進行壓測)

 

5 運維和容量規划

要高效運維服務,關鍵在於構建系統時消除各種運維交互過程。目標在於讓一個高可靠的、7*24小時可用的服務只需要5*8小時工作的運維團隊維護即可。

關於運維和容量相關的最佳實踐:

  1. 任何運維腳本都需要經過測試,沒有經過頻繁測試的工具是無法使用的;不要開發任何團隊成員沒有勇氣去使用的工具;

  2. you build it, you manage it:如果開發人員經常在半夜被叫醒,那么他們會去優化系統;如果運維人員經常半夜被叫醒,可能會擴容運維團隊

  3. 只進行軟刪除:只對刪除數據進行標記而不進行刪除,避免因誤操作導致的數據丟失

  4. 追蹤資源分配情況:每個服務需要將在線用戶數,用戶每秒的請求數這些數據和機器負載及資源情況進行綁定跟蹤

  5. 讓一切可配置化:任何可能在系統中發生變更的東西都應該是在生產環境下可配置和調整的,而不需要改變代碼;即使某個值看起來沒有很好的在生產環境中發生變更的理由,如果很容易的話,也應該將它們做成可配置的

 

6 審計、監控和報警

審計、監控、告警是避免故障,以及保證故障及時被處理的重要手段,關於審計、監控、報警的最佳實踐:

  1. 審計一切:有任何配置發生變更都需要記錄下來:誰,在什么時候,改了什么;生產環境出了問題第一個要考慮的問題就是最近是否做了什么變更

  2. 報警評價標准:只在需要的時候報警,如果報警之后不需要做任何錯誤,那么這個報警就不是必要的;報警和故障比應該是1,沒有產生報警的故障數應該是0

  3. 從用戶的角度看問題:進行端到端的測試,保證重要和復雜的邏輯都被測試到

  4. 延遲是最難發現的問題:像是IO緩慢但是還沒有不可用,這種情況是很難發現的,要做好監控

  5. 可配置的日志:可以動態調整日志級別以幫助快速定位問題

  6. 快速定位問題:系統執行重要的操作時要打印必要的日志記錄上下文信息

 

7 優雅降級和准入控制

有些時候比如收到DOS攻擊或者模式發生某些改變時,會導致負載突然爆發。服務需要能夠進行優雅的降級及准入控制。兩個最佳實踐:“Big Red Switch”和准入控制,需要針對每個服務進行量身定制。但是這兩個都是非常強大和必要的。

  1. Big Red Switch:支持應急開關。大體來說“Big Red Switch”是一種當服務不再滿足SLA或者迫在眉睫時,可以采取的經過設計和測試的動作。將優雅的降級比喻為“Big Red Switch”,稍微有些不太恰當,但核心的意思是指那種可以在緊急情況下摒棄那些非關鍵負載的能力。

  2. 准入控制:當系統已經過載時,需要適當地在最前端拒絕掉部分請求,以免系統進入一種完全無法恢復的狀態

  3. 漸進式准入控制:當系統慢慢恢復時,需要可以逐漸讓請求進入,而不是一次全部放開,避免恢復后一時間大量的請求涌入導致系統再次故障

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM