工作十年,談談我的高可用架構和系統設計經驗

一、高可用架構和系統設計思想
可用性和高可用概念
可用性是一個可以量化的指標,計算的公式在維基百科中是這樣描述的:根據系統損害、無法使用的時間,以及由無法運作恢復到可運作狀況的時間,與系統總運作時間的比較。行業內一般用幾個9表示可用性指標,對應用的可用性程度一般衡量標準有三個9到五個9;一般我們的系統至少要到 4 個 9(99.99%)的可用性才能談得上高可用。
高可用(High Availability)的定義:(From 維基百科)是 IT 術語,指系統無中斷地執行其功能的能力,代表系統的可用性程度,是進行系統設計時的準則之一。服務不可能 100% 可用,因此要提高我們的高可用設計,就要盡最大可能的去增加我們服務的可用性,提高可用性指標。一句話來表述就是:高可用就是讓我們的服務在任何情況下都盡最大可能能夠對外提供服務。
高可用系統設計思想
高可用系統的設計,需要有一套比較科學的工程管理套路,要從產品、開發、運維、基建等全方位去考量和設計,高可用系統的設計思想包括但不限于:
- 做好研發規范,系統都是研發人員設計和編碼寫出來的,因此首先要對研發層面有一個規范和標準
- 做好容量規劃和評估,主要是讓開發人員對系統要抗住的量級有一個基本認知,方便進行合理的架構設計和演進。
- 做好服務層面的高可用,主要是負載均衡、彈性擴縮容、異步解耦、故障容錯、過載保護等。
- 做好存儲層面的高可用,主要是冗余備份(熱備、冷備)、失效轉移(確認,轉移,恢復)等。
- 做好運維層面的高可用,主要是發布測試、監控告警、容災、故障演練等。
- 做好產品層面的高可用,主要是兜底策略。
- 做好應急預案,主要是在出現問題后怎么快速恢復,不至于讓我們的異常事態擴大。
二、研發規范層面
方案設計和編碼規范
研發規范層面這個是大家容易忽視的一個點,但是,我們所有的設計,都是研發人員來完成的,包括從設計文檔到編碼到發布上線,因此,研發層面也是有一個規范流程和套路,來讓我們更好的去研發和維護一個高可用的系統:
- 設計階段
規范好相關方案設計文檔的模板和提綱,讓團隊內部保持統一,可以參考我的文章《??技術方案設計模板??》
方案設計后一定要進行評審,在我們團隊中,新項目一定要評審,重構項目一定要評審,大的系統優化或者升級一定要評審,其他的一般研發工作量超過一周的建議要評審的。
- 編碼階段
不要隨便打日志
要接入遠程日志
要能夠分布式鏈路追蹤
代碼編寫完需要有一定的單測來保證代碼的健壯性,同時也能保障我們后續調整邏輯或者優化的時候可以保證代碼的穩定
包括增量覆蓋率、全量覆蓋率,具體的覆蓋率要達到多少可以根據團隊內部的實際情況來定,在我們團隊,定的規則是 50% 的覆蓋率。
工程的 layout 目錄結構規范,團隊內部保持統一,盡量簡潔
遵循團隊內部的代碼規范,一般公司都有對應語言的規范,如果沒有則參考官方的規范,代碼規范可以大大減少 bug 并且提高可用性。
執行代碼規范
單測覆蓋率
日志規范
- 發布上線階段,參考下面運維部署層面那一章節的灰度發布和接口測試相關說明
容量規劃和評估
容量評估,是指我們需要評估好,我們這個系統,是為了應對一個什么體量的業務,這個業務請求量的平均值、高峰的峰值大概都在一個什么級別。如果是新系統,那么就需要根據產品和運營同學對業務有一個大體的預估,然后開發同學根據產品給的數據再進行詳細的評估。如果是老系統,那么就可以根據歷史數據來評估。評估的時候,要從一個整體角度來看全局的量級,然后再細化到每個子業務模塊要承載的量級。
容量規劃,是指我們系統在設計的時候,就要能夠初步規劃好我們的系統大致能夠抗多少的量級,比如是十萬還是百萬級別的請求量,或者更多。不同的量級對應的系統架構的設計會完全不一樣,尤其到了千萬、億級別的量級的時候,架構的設計會有很多的考量。當然這里需要注意的是,我們不需要一上來就設計出遠超于我們當前業務真實流量的系統,要根據業務實際情況來設計。同時,容量規劃還涉及到,我們系統上下游的各個模塊、依賴的存儲、依賴的三方服務,分別需要多少資源,需要有一個相對可以量化的數據出來。容量規劃階段,更多是要依靠自身和團隊的經驗,比如要了解我們的 log 的性能、redis 的性能、rpc 接口的性能、服務化框架的性能等等,然后根據各種組件的性能來綜合評估自己設計的系統的整體性能情況。
容量評估和容量規劃之后,我們還需要做一件事情,就是性能壓測,最好是能夠做到全鏈路壓測。性能壓測的目的是為了確保你的容量規劃是準確的,比如我設計的這個系統,我規劃的是能夠抗千萬級別的請求,那么實際上,真的能夠抗住嗎 ?這個在上線之前,首先要根據經驗來判斷,然后是一定要經過性能壓測得出準確結論的。性能壓測要關注的指標很多,但是重點要關注是兩個指標,一個是 QPS、一個是響應耗時,要確保壓測的結果符合預期。壓測的步驟可以先分模塊單獨壓測,最后如果情況允許,那么最好執行全鏈路壓測。
QPS 預估(漏斗型)
QPS 預估(漏斗型),指的是一個真實的請求過來后,從接入層開始,分別經過了我們整個系統的哪些層級、哪些模塊,然后每一個層級的 QPS 的量級分別有多少,從請求鏈路上來看,層級越往下,那么下游層級的量級應該會逐步減少的,因為每經過一個層級,都有可能會被各種條件過濾掉的一部分請求。比如說進入活動頁后查看商品詳情然后下單這個例子,首先進入活動頁,所有的請求都會進入訪問;然后只會有部分用戶查詢商品詳情;最后查看商品詳情的這些用戶又只會有部分用戶會下單,因此這里就會有一個漏斗,從上層模塊到下層模塊的量級一定是逐步減少的。
QPS 預估(漏斗型)就是需要我們按照請求的層面和模塊來構建我們的預估漏斗模型,然后預估好每一個層級的量級,包括但不限于從服務、接口、分布式緩存等各個層面來預估,最后構成我們完整的 QPS 漏斗模型。
三、應用服務層面
無狀態和負載均衡設計
一般要做到系統的高可用,我們的應用服務的常規設計都是無狀態的,這也就意味著,我們可以部署多個實例來提高我們系統的可用性,而這多個實例之間的流量分配,就需要依賴我們的負載均衡能力。無狀態 + 負載均衡 既可以讓我們的系統提高并發能力,也可以提高我們系統的可用性。
如果我們的業務服務使用的是各種微服務框架來開發的,那么大概率在這個微服務框架里面就會包含了服務發現和負載均衡的能力。這是一整套流程,包括服務注冊和發現、負載均衡、健康狀態檢查和自動剔除。當我們的任何一個服務實例出現故障后會被自動剔除掉,當我們有新增一個服務實例后會自動添加進來提供服務。
如果我們不是使用的微服務框架來開發的,那么就需要依賴負載均衡的代理服務,比如 LVS、Nginx 來幫我們實現負載均衡。
彈性擴縮容設計
彈性擴縮容設計是應對突峰流量的非常有效的手段之一,同時也是保障我們服務可用性的必要手段。彈性擴縮容針對的是我們的無狀態的應用服務而言的,因為服務是無狀態的,因此可以隨時根據請求量的大小來進行擴縮容,流量大就擴容來應對大量請求,流量小的時候就縮容減少資源占用。
怎么實現彈性擴縮容呢?現階段都是云原生時代,大部分的公司都是采用容器化(K8s)部署,那么基于這個情況的話,彈性擴縮容就非常容易了,只需要配置好 K8s 的彈性條件就能自動根據 CPU 的使用率來實現。
如果不是容器化部署,是物理機部署的方式,那么要做到彈性擴縮容,必須要有一個公司內部的基礎建設能力,能夠在運營平臺上針對服務的 CPU 或者 QPS 進行監控,如果超過一定的比例就自動擴縮容,和 K8s 的彈性原理是一樣的,只是需要自行實現。
異步解耦和削峰設計(消息隊列)
要想我們的系統能夠高可用,那么從架構層面來說,要做到分層、分模塊來設計,而分層分模塊之后,那么各個模塊之間,還可以進行異步處理、解耦處理。目的是為了不相互影響,通過異步和解耦可以使我們的架構大大的提升可用性。
架構層面的異步解耦的方式就是采用消息隊列(比如常見的 Kafka),并且同時消息隊列還有削峰的作用,這兩者都可以提高我們的架構可用性:
- 異步解耦:采用消息隊列之后,可以把同步的流程轉換為異步的流程,消息生成者和消費者都只需要和消息隊列進行交互,這樣不僅做了異步處理,還講消息生成者和消費者進行了隔離。異步處理的優勢在于,不管消息的后續處理的業務服務是否 ok,只要消息隊列還沒滿,那么就可以執行對外提供服務,而消費方則可以根據自身處理能力來消費消息后進行處理。解耦的優勢在于,如果消費方異常,那么并不影響生產方,依然可以對外提供服務,消息消費者恢復后可以繼續從消息隊列里面消費數據后執行業務邏輯
- 削峰:采用消息隊列之后,還可以做到削峰的作用,當并發較高的時候,甚至是流量突發的時候,只要消息生產者能夠將消息寫入到消息隊列中,那么這個消息就不會丟,后續處理邏輯可以慢慢的去消息隊列里面消費這些突發的流量數據。這樣就不會因為有突發流量而把整個系統打垮。
故障和容錯設計
任何服務,一定會存在失敗的情況,不可能有 100% 的可用,服務在線上運行過程中,總會遇到各種各樣意想不到的問題會讓你的服務出現狀況,因此業界來評價可用性 SLA 都是說多少個 9,比如 4 個 9(99.99%)的可用性。
為此,我們的設計建議遵循"design for failure"的設計原則,設計出一套可容錯的系統,需要做到盡早返回、自動修復,細節如下
- 遵循 fail fast 原則,Fail fast 原則是說,當我們的主流程的任何一步出現問題的時候,應該快速合理地結束整個流程,盡快返回錯誤,而不是等到出現負面影響才處理。
- 具備自我保護的能力。當我們依賴的其他服務出現問題的時候,要盡快的進行降級、兜底等各種異常保護措施,要避免出現連鎖反應導致整個服務完全不可用。比如當我們依賴的數據存儲出現問題,我們不能一直重試從而導致數據完全不可用。
過載保護設計(限流、熔斷、降級)
系統無法高可用的一個重要原因就在于,我們的系統經常會有突發的流量過來,導致我們的服務超載運行,這個時候,首先要做的當然是快速擴容,并且我們事先就要預留好一定的冗余。另外一個情況下,就算我們擴容了,但是還是會超載,比如超過了下游依賴的存儲的最大容量、或者超過了下游依賴的三方服務的最大容量。那么這個時候,我們就需要執行我們的過載保護策略了,主要包括限流、熔斷、降級,過載保護是為了保證服務部分可用從而不至于整個服務完全不可用。
- 限流。限流是指對進入系統的請求進行限流處理,如果請求量超過了我們系統最大處理能力或者超過了我們指定的處理能力,那么直接拒絕請求,通過這種丟棄部分請求的方式可以保證整個系統有一定的可用性,從而不至于讓整個系統完全不可用。怎么判別超過最大處理能力呢?一般就是針對 QPS 來判別,如果 QPS 超過閾值,那么就直接拒絕請求。
- 限流有很多細節的策略,比如針對接口限流、針對服務限流、針對用戶限流。
- 熔斷。熔斷,斷路(開路)的價值在于限制故障影響范圍。我們希望控制、減少或中斷和故障系統之間的通信,從而降低故障系統的負載,有利于系統的恢復。一般我們的服務都會有很多下游依賴,如果下游依賴的服務出現問題,比如開始超時甚至響應非常慢的情況下,如果我們不做任何處理,那么會導致我們的整個請求都被卡住從而超時,那么我們的業務服務對外就無法提供任何正常的功能了。為此,熔斷策略就可以解決這個問題,熔斷就是當我們依賴的下游服務出現問題的時候,可以快速對其進行熔斷(不發起請求),這樣我們的業務服務至少可以提供部分功能。熔斷的設計至少需要包括 熔斷請求判斷機制算法、熔斷恢復、熔斷告警 三部分
- 降級。降級是指我們劃分好系統的核心功能和非核心功能,然后當我們的系統超過最大處理能力之后,直接關閉掉非核心的功能,從而保障核心功能的可用。關閉掉非核心的功能后可以使我們的系統釋放部分資源,從而可以有資源來處理核心功能。
- 熔斷和降級這兩個策略,看著比較像,字面的意思上來看都是要快速拒絕掉請求。但是他們是兩個維度的設計,降級的目的是應對系統自身的故障,而熔斷的目的是應對我們系統依賴的外部服務故障的情況。
四、存儲層面
在當前的互聯網時代,應用服務基本都是無狀態的,因此應用服務的高可用相對會比較簡單,但是對于數據存儲的高可用,相對來說,會復雜很多,因為數據是有狀態的,那具體我們要怎么保障數據存儲的高可用,我們來分析下。
存儲層面的高可用方案的本質都是通過通過數據冗余的方式來實現高可用,將數據復制到多個存儲介質里面,可以有效的避免數據丟失,同時還可以提高并發能力,因為數據是有狀態的,因此,這里會比服務的高可用要復雜很多,主要體現在如下幾個方面
- 數據如何復制?
- 各個節點的職責是什么?
- 如何應對復制延遲?
- 如何應對復制中斷?
常見的解決存儲高可用的方案有兩種:集群存儲和分布式存儲。業界大多是圍繞這些來構建,或者是做相關衍生和擴展。
集群存儲(集中式存儲)
集群就是邏輯上處理同一任務的機器集合,可以屬于同一機房,也可分屬不同的機房。集群存儲,就是把多臺機器上的存儲數據組合在一起對外形成一套統一的系統。集群存儲適合業務存儲量規模一般的場景,常規的業務數據存儲一般都是集群存儲方式就足夠了。現在我們一般對于業務數據存儲的使用,默認都是集群方式,比如 Redis、MySQL 等存儲類型,一般中大型互聯網公司,默認肯定都是集群存儲的方式。
集群存儲就是我們常說的 1 主多備或者 1 主多從的架構,寫數據通過主機,讀數據一般通過從機。集群存儲主要需要考慮如下幾個問題:
- 主機如何將數據復制給備機(從機)
數據的寫入都是通過主機,因此數據同步到備機(從機),就是要通過主機進行數據復制到備機(從機)。
還需要考慮主備同步的時間延遲問題。
- 備機(從機)如何檢測主機狀態
- 主機故障后,備機(從機)怎么切換為主機
主從架構中,如果主機故障,可直接將備機(從機)切換為主機
1.主備復制
主備復制是最常見也是最簡單的一種存儲高可用方案,幾乎所有的存儲系統都提供了主備復制的功能,例如 MySQL、Redis、MongoDB 等。
主備架構中的“備機”主要還是起到一個備份作用,并不承擔實際的業務讀寫操作,如果要把備機改為主機,需要人工操作。因此一般使用場景都是在一些內部的后臺管理系統中使用。
2.主從復制
主從復制和主備復制雖然只有一字之差,但是兩者是不一樣的設計思路,“從”意思是“隨從、仆從”,“備”的意思是備份。”從“ 的機制是要干活的,因此是承擔數據的“讀”操作的,一般就是主機負責讀寫操作,從機只負責讀操作,不負責寫操作。
3.主從切換
主備復制和主從復制方案存在兩個共性的問題:
- 主機故障后,無法進行寫操作。
- 如果主機無法恢復,需要人工指定新的主機角色。
主從切換(主備切換)就是為了解決這兩個問題而產生的,具體的設計就是在原有方案的基礎上增加“自動切換”的能力,當主機異常后,經過系統檢測并且自動將備機或者從機切換為主機。這個是實際應用中比較多的一個方案之一,因為我們一定能夠有機制保證主機異常后從機能夠自動切換為主機。
4.主主復制
主主復制指的是兩臺機器都是主機,互相將數據復制給對方,客戶端可以任意挑選其中一臺機器進行讀寫操作,如果采取主主復制架構,必須保證數據能夠雙向復制。這個相對來說,要求較高。
分布式存儲
集群指的是將幾臺服務器集中在一起,實現同一業務。而分布式是指將不同的業務分布在不同的地方,分布式中的每一個節點,都可以做集群。
分布式存儲就是通過網絡使用企業中的每臺機器上的磁盤空間,并將這些分散的存儲資源構成一個虛擬的存儲設備,數據分散的存儲在企業的各個角落。分布式存儲中的每臺服務器都可以處理讀寫請求,因此不存在集中式存儲中負責寫的主機那樣的角色。但在分布式存儲中,必須有一個角色來負責執行數據分配算法,這個角色可以是獨立的一臺服務器,也可以是集群自己選舉出的一臺服務器。分布式存儲適合非常大規模的數據存儲,業務數據量巨大的場景可以采用這種方式。常見的分布式存儲比如 Hadoop(HDFS)、HBase、Elasticsearch 等。
五、產品層面
產品層面的高可用架構解決方案,基本上就是指我們的兜底產品策略。降級/限流的策略,更多的是從后端的業務服務和架構上的設計來考慮相關解決方案。這里說的兜底策略,也可叫做柔性降級策略,更多則是通過產品層面上來考慮。
- 比如,當我們的頁面獲取不到數據的時候,或者無法訪問的時候,要如何友好的告知用戶,比如【稍后重試】之類的。
- 比如 當我們的真實的頁面無法訪問的時候,那么需要產品提供一個默認頁面,如果后端無法獲取真實數據,那么直接渲染默認頁面。
- 比如服務器需要停機維護,那么產品層面給一個停機頁面,所有用戶只會彈出這個停機頁面,不會請求后端服務
- 比如抽獎商品給一個默認兜底商品
- ...
六、運維部署層面
開發階段-灰度發布、接口測試設計
灰度發布、接口測試、接口撥測系列設計包括但不限于:
- 灰度發布,我們服務發布上線的時候,要有一個灰度的過程,先灰度 1-2 個服務實例,然后逐步放量觀察,如果一切 ok,再逐步灰度,直到所有實例發布完畢
- 接口測試,每次服務發布上線的時候,服務提供的各種接口,都要有接口測試用例,接口測試用例跑過之后,服務才能發布上線,目的是為了查看我們對外提供的接口是否能夠正常,避免服務發布上線后才發現有問題
灰度發布和接口測試,一般在大公司里面會有相關的 DevOps 流程來保證。
開發階段-監控告警設計
監控告警的設計,在大公司來說,根本不是問題,因為一定會有比較專門一撥人去做這種基礎能力的建設,會有對應的配套系統,業務開發的同學只需要配置或使用即可。那如果說公司內部沒有相關基礎建設,那么就需要自己分別來接入對應的系統了。
監控系統
一般在監控系統這方面的開源解決方案包括但不限于這些:
- ELK (Elasticsearch、Logstash、Kibana) 日志收集和分析
我們的日志記錄不能都本地存儲,因為微服務化后,日志散落在很多機器上,因此必須要有一個遠程日志記錄的系統,ELK 是不二人選
- Prometheus 監控收集
可以監控各種系統層面的指標,包括自定義的一些業務指標
- OpenTracing 分布式全鏈路追蹤
一個請求的上下游這么多服務,怎么能夠把一個請求的上下游全部串起來,那么就要依靠 OpenTracing,可以把一個請求下的所有鏈路都串起來并且有詳細的記錄
- OpenTelemetry 可觀測系統標準
最新的標準,大一統,集合了跟蹤數據(Traces),指標數據(Metrics),日志數據(Logs)來觀測分布式系統狀態的能力
我們會依托開源系統進行自建或者擴展,甚至直接使用都行,然后我們的監控的指標一般會包括:
- 基礎設施層的監控:主要是針對網絡、交換機、路由器等低層基礎設備,這些設備如果出現問題,那么依托其運行的業務服務肯定就無法穩定的提供服務,我們常見的核心監控指標包括網絡流量(入和出)、網絡丟包情況、網絡連接數等。
- 操作系統層的監控:這里需要包含物理機和容器。常見的核心指標監控包括 CPU 使用率、內存占用率、磁盤 IO 和網絡帶寬等。
- 應用服務層的監控:這里的指標會比較多,核心的比如主調請求量、被調請求量、接口成功率、接口失敗率、響應時間(平均值、P99、P95 等)等。
- 業務內部的自定義監控:每個業務服務自己的一些自定義的監控指標。比如電商系統這里的:瀏覽、支付、發貨等各種情況的業務指標
- 端用戶層的監控:前面的監控更多的都是內部系統層面的,但是用戶真正訪問到頁面,中間還有外網的情況,用戶真正獲取到數據的耗時、打開頁面的耗時等這些信息也是非常重要的,但是這個一般就是需要客戶端或者前端去進行統計了。
告警系統
這些系統接入完了之后,還只是做到監控和統計,當出現問題的時候,還需要進行實時告警,因此還要有一個實時告警系統,如果沒有實時報警,系統運行異常后我們就無法快速感知,這樣就無法快速處理,就會給我們的業務帶來重大故障和災難。告警設計需要包括:
- 實時性:實現秒級監控;
- 全面性:覆蓋所有系統業務;
- 實用性:預警分為多個級別,監控人員可以方便實用地根據預警嚴重程度做出精確的決策;
- 多樣性:預警方式提供推拉模式,包括短信,郵件,可視化界面,方便監控人員及時發現問題
開發階段-安全性、防攻擊設計
安全性、防攻擊設計的目的是為了防刷、防黑產、防黑客,避免被外部惡意攻擊,這個一般有幾個策略:
- 在公司級別的流量入口做好統一的防刷和鑒權的能力,比如再統一接入層做好封裝
- 在業務服務內部,做好相關的業務鑒權,比如登錄態信息、比如增加業務鑒權的邏輯
部署階段-多機房部署(容災設計)
一般的高可用策略,都是針對一個機房內來服務層面來設計的,但是如果整個機房都不可用了,比如地震、火災、光纖挖斷等。。。。那么這個情況怎么辦?這就需要我們的服務和存儲都能夠進行容災了,容災的一個常見方案就是多機房部署了。
- 服務的多機房部署,這個比較容易,因為我們的服務都是無狀態的,因此只要名字服務能夠發現不同機房的服務,就可以實現調用,這里需要注意的是名字服務(或者說負載均衡服務)要能夠有就近訪問的能力。
- 存儲的多機房部署,這個會比較難搞一點,因為存儲是有狀態的,部署在不同的機房就涉及到存儲的同步和復制問題。
條件不允許的情況下,我們保證多機房部署業務服務就可以了。
線上運行階段-故障演練(混沌實驗)
故障演練在大公司是一個常見的手段;在業界,Netflix 早在 2010 年就構建了混沌實驗工具 Chaos Monkey,混沌實驗工程對于提升復雜分布式系統的健壯性和可靠性發揮了重要作用。
簡單的故障演練就是模擬機房斷電、斷網、服務掛掉等場景,然后看我們的整個系統運行是否正常。系統的就要參考混沌實驗工程來進行詳細的規劃和設計,這個是一個相對比較大的工程,效果挺好,但是需要有大量人力去開發這種基礎建設。
線上運行階段-接口撥測系列設計
接口撥測,和巡檢類似,就是服務上線后,每隔一個固定時間(比如 5s)調用后端的各種接口,如果接口異常則進行告警
針對接口撥測,一般也會有相關配套設施來提供相關的能力去實現,如果沒有提供,那么我們可以自己寫一個接口撥測(巡檢)的服務,定期去調用重要的接口。
七、異常應急層面
前面做了這么多保障,但是終究架不住線上的各種異常情況,如果真出問題了,讓我們的服務異常,無法提供服務后,我們還需要最后一根救命稻草,那就是應急預案,將服務異常的損失降低到最小。
應急預案就是我們需要事先規劃好,我們業務系統在各個層級出現問題后,我們需要第一時間怎么恢復,制定好相關規則和流程,當出現異常狀況后可以按照既有的流程去執行,這樣避免出現問題后手忙腳亂導致事態擴大。




























