高併發系統演進之路(上)--基礎篇

高併發、高可用、可擴展是互聯網技術井噴後軟件系統演進的基本要求。

性能指標:度量性能的指標是系統接口的響應時間:平均值、最大值、分位數

高併發下的性能優化:

1)提高系統的處理核心數,

    但隨着併發進程數的增加,並行的任務對於系統資源的爭搶也會愈發嚴重。在某一個臨界點上繼續增加併發進程數,反而會造成系統性能的下降,這就是性能測試中的拐點模型

2)減少單次任務響應時間

要看你的系統是 CPU 密集型還是 IO 密集型的,因爲不同類型的系統性能優化方式不盡相同。優化方案會隨着問題的不同而不同。比方說,如果是數據庫訪問慢,那麼就要看是不是有鎖表的情況、是不是有全表掃描、索引加得是否合適、是否有 JOIN 操作、需不需要加緩存,等等;如果是網絡的問題,就要看網絡的參數是否有優化的空間,抓包來看是否有大量的超時重傳,網卡是否有大量丟包等。

高可用性

高可用性度量指標:

1)MTBF(Mean Time Between Failure)是平均故障間隔的意思,代表兩次故障的間隔時間,也就是系統正常運轉的平均時間。這個時間越長,系統穩定性越高。

2)MTTR(Mean Time To Repair)表示故障的平均恢復時間,也可以理解爲平均故障時間。這個值越小,故障對於用戶的影響越小。

Availability = MTBF / (MTBF + MTTR)

高可用系統設計思路:
1)failover(故障轉移)

A. 是在完全對等的節點之間做 failover。B. 是在不對等的節點之間,即系統中存在主節點也存在備節點。

2)超時控制以及降級和限流。

A.降級是爲了保證核心服務的穩定而犧牲非核心服務的做法。

B.限流完全是另外一種思路,它通過對併發的請求進行限速來保護系統。

高可用系統運維:
1)灰度發佈

2)故障演練

開發注重的是如何處理故障,關鍵詞是冗餘和取捨。冗餘指的是有備用節點,集羣來頂替出故障的服務,比如文中提到的故障轉移,還有多活架構等等;取捨指的是丟卒保車,保障主體服務的安全。從運維角度來看則更偏保守,注重的是如何避免故障的發生,比如更關注變更管理以及如何做故障的演練。

可擴展性高可用系統

拆分是提升系統擴展性最重要的一個思路,它會把龐雜的系統拆分成獨立的,有單一職責的模塊。

1)存儲層的擴展性

2)業務層的擴展,業務緯度,重要性緯度和請求來源緯度。

 

數據庫篇

1)用連接池預先建立數據庫連接:頻繁的創建釋放連接池會導致請求變慢

最小連接數和最大連接數,它們控制着從連接池中獲取連接的流程:

如果當前連接數小於最小連接數,則創建新的連接處理數據庫請求;

如果連接池中有空閒連接則複用空閒連接;

如果空閒池中沒有連接並且當前連接數小於最大連接數,則創建新的連接處理請求;

如果當前連接數已經大於等於最大連接數,則按照配置中設定的時間(C3P0 的連接池配置是 checkoutTimeout)等待舊的連接可用;

如果等待超過了這個設定時間則向用戶拋出錯誤。

池子的最大值和最小值的設置很重要,初期可以依據經驗來設置,後面還是需要根據實際運行情況做調整。

池子中的對象需要在使用之前預先初始化完成,這叫做池子的預熱,比方說使用線程池時就需要預先初始化所有的核心線程。如果池子未經過預熱可能會導致系統重啓後產生比較多的慢請求。

池化技術核心是一種空間換時間優化方法的實踐,所以要關注空間佔用情況,避免出現空間過度使用出現內存泄露或者頻繁垃圾回收等問題。

2)查詢請求增加,主從分離

大部分系統的訪問模型是讀多寫少,讀寫請求量的差距可能達到幾個數量級。

主從讀寫的兩個技術關鍵點:

A.數據的拷貝,我們稱爲主從複製

B.屏蔽主從分離帶來的訪問數據庫方式的變化

主從分離不足:

部署上的複雜度,還有就是會帶來一定的主從同步的延遲

1. 主從讀寫分離以及部署一主多從可以解決突發的數據庫讀流量,是一種數據庫橫向擴展的方法;

2. 讀寫分離後,主從的延遲是一個關鍵的監控指標,可能會造成寫入數據之後立刻讀的時候讀取不到的情況;

3. 業界有很多的方案可以屏蔽主從分離之後數據庫訪問的細節,讓開發人員像是訪問單一數據庫一樣,包括有像 TDDL、Sharding-JDBC 這樣的嵌入應用內部的方案,也有像 Mycat 這樣的獨立部署的代理方案。

3)分庫分表

問題點:

A.如何提升查詢性能

B.數據庫存儲量大

C.如何做到不同業務線故障隔離

D.數據庫系統如何來處理更高的併發寫入請求

解決辦法:

分庫分表是一種常見的將數據分片的方式,它的基本思想是依照某一種策略將數據儘量平均地分配到多個數據庫節點或者多個表中。不同於主從複製時數據是全量地被拷貝到多個節點,分庫分表後,每個節點只保存部分的數據,這樣可以有效地減少單個數據庫節點和單個數據表中存儲的數據量,在解決了數據存儲瓶頸的同時也能有效地提升數據查詢的性能。同時,因爲數據被分配到多個數據庫節點上,那麼數據的寫入請求也從請求單一主庫變成了請求多個數據分片節點,在一定程度上也會提升併發寫入的性能。

1)垂直拆分,顧名思義就是對數據庫豎着拆分,也就是將數據庫的表拆分到多個不同的數據庫中。按業務分

2)水平拆分,單一數據表按照某一種規則拆分到多個數據庫和多個數據表中,關注點在數據的特點

      A. 按照某一個字段的哈希值做拆分,這種拆分規則比較適用於實體表,比如說用戶表,內容表,我們一般按照這些實體表的 ID 字段來拆分

      B.另一種比較常用的是按照某一個字段的區間來拆分,比較常用的是時間字段。

1. 如果在性能上沒有瓶頸點那麼就儘量不做分庫分表;

2. 如果要做,就儘量一次到位,比如說 16 庫,每個庫 64 表就基本能夠滿足爲了幾年內你的業務的需求。

3. 很多的 NoSQL 數據庫,例如 Hbase,MongoDB 都提供 auto sharding 的特性,如果你的團隊內部對於這些組件比較熟悉,有較強的運維能力,那麼也可以考慮使用這些 NoSQL 數據庫替代傳統的關係型數據庫。

分庫分表後ID的全局唯一性:

A. 使用業務字段作爲主鍵,比如說對於用戶表來說,可以使用手機號,email 或者身份證號作爲主鍵。

B. 使用生成的唯一 ID 作爲主鍵。

NOSQL對關係數據庫的互補:

A. 在性能方面,NoSQL 數據庫使用一些算法將對磁盤的隨機寫轉換成順序寫,提升了寫的性能;

B. 在某些場景下,比如全文搜索功能,關係型數據庫並不能高效地支持,需要 NoSQL 數據庫的支持;

C. 在擴展性方面,NoSQL 數據庫天生支持分佈式,支持數據冗餘和數據分片的特性。

4)緩存

常見的緩存主要就是靜態緩存、分佈式緩存和熱點本地緩存這三種;

對於靜態的資源的緩存你可以選擇靜態緩存,對於動態的請求你可以選擇分佈式緩存,熱點本地緩存主要部署在應用服務器的代碼中,用於阻擋熱點查詢對於分佈式緩存節點或者數據庫的壓力。

緩存不足:

A.首先,緩存比較適合於讀多寫少的業務場景,並且數據最好帶有一定的熱點屬性

B.其次,緩存會給整體系統帶來複雜度,並且會有數據不一致的風險

C.再次,之前提到緩存通常使用內存作爲存儲介質,但是內存並不是無限的。

D.最後,緩存會給運維也帶來一定的成本,運維需要對緩存組件有一定的瞭解,在排查問題的時候也多了一個組件需要考慮在內。

緩存可以有多層,比如上面提到的靜態緩存處在負載均衡層,分佈式緩存處在應用層和數據庫層之間,本地緩存處在應用層。我們需要將請求儘量擋在上層,因爲越往下層,對於併發的承受能力越差;緩存命中率是我們對於緩存最重要的一個監控項,越是熱點的數據,緩存的命中率就越高。

緩存的讀寫策略:

讀策略的步驟是:

從緩存中讀取數據;

如果緩存命中,則直接返回數據;

如果緩存不命中,則從數據庫中查詢數據;

查詢到數據後,將數據寫入到緩存中,並且返回給用戶。

寫策略的步驟是:

更新數據庫中的記錄;

刪除緩存記錄。

Cache Aside(旁路緩存)策略

寫數據庫,然後清理緩存

Cache Aside 存在的最大的問題是當寫入比較頻繁時,緩存中的數據會被頻繁地清理,這樣會對緩存的命中率有一些影響。

A. 一種做法是在更新數據時也更新緩存,只是在更新緩存前先加一個分佈式鎖,因爲這樣在同一時間只允許一個線程更新緩存,就不會產生併發問題了。當然這麼做對於寫入的性能會有一些影響;

B. 另一種做法同樣也是在更新數據時更新緩存,只是給緩存加一個較短的過期時間,這樣即使出現緩存不一致的情況,緩存的數據也會很快地過期,對業務的影響也是可以接受。

Read/Write Through(讀穿 / 寫穿)策略

這個策略的核心原則是用戶只與緩存打交道,由緩存和數據庫通信,寫入或者讀取數據。

Write Through 的策略是這樣的:先查詢要寫入的數據在緩存中是否已經存在,如果已經存在,則更新緩存中的數據,並且由緩存組件同步更新到數據庫中,如果緩存中數據不存在,我們把這種情況叫做“Write Miss(寫失效)”。

Read Through 策略就簡單一些,它的步驟是這樣的:先查詢緩存中數據是否存在,如果存在則直接返回,如果不存在,則由緩存組件負責從數據庫中同步加載數據。

Write Back(寫回)策略

這個策略的核心思想是在寫入數據時只寫入緩存,並且把緩存塊兒標記爲“髒”的。而髒塊兒只有被再次使用時纔會將其中的數據寫入到後端存儲中。

在“Write Miss”的情況下,我們採用的是“Write Allocate”的方式,也就是在寫入後端存儲的同時要寫入緩存,這樣我們在之後的寫請求中都只需要更新緩存即可,而無需更新後端存儲了,我將 Write back 策略的示意圖放在了下面:

讀的策略也有一些變化了。我們在讀取緩存時如果發現緩存命中則直接返回緩存數據。如果緩存不命中則尋找一個可用的緩存塊兒,如果這個緩存塊兒是“髒”的,就把緩存塊兒中之前的數據寫入到後端存儲中,並且從後端存儲加載數據到緩存塊兒,如果不是髒的,則由緩存組件將後端存儲中的數據加載到緩存中,最後我們將緩存設置爲不是髒的,返回數據就好了。

1.Cache Aside 是我們在使用分佈式緩存時最常用的策略,你可以在實際工作中直接拿來使用。

2.Read/Write Through 和 Write Back 策略需要緩存組件的支持,所以比較適合你在實現本地緩存組件的時候使用;

3.Write Back 策略是計算機體系結構中的策略,不過寫入策略中的只寫緩存,異步寫入後端存儲的策略倒是有很多的應用場景。

分佈式緩存的高可用方案:

客戶端方案、中間代理層方案和服務端方案三大類:

客戶端方案就是在客戶端配置多個緩存的節點,通過緩存寫入和讀取算法策略來實現分佈式,從而提高緩存的可用性。

中間代理層方案是在應用代碼和緩存節點之間增加代理層,客戶端所有的寫入和讀取的請求都通過代理層,而代理層中會內置高可用策略,幫助提升緩存系統的高可用。

服務端方案就是 Redis 2.4 版本後提出的 Redis Sentinel 方案。

客戶端方案:

A.緩存數據如何分片:hash分片、一致性hash分片

B.Memcached 的主從機制

C.多副本

分佈式緩存的高可用方案主要有三種,首先是客戶端方案,一般也稱爲 Smart Client。我們通過制定一些數據分片和數據讀寫的策略,可以實現緩存高可用。這種方案的好處是性能沒有損耗,缺點是客戶端邏輯複雜且在多語言環境下不能複用。其次,中間代理方案在客戶端和緩存節點之間增加了中間層,在性能上會有一些損耗,在代理層會有一些內置的高可用方案,比如 Codis 會使用 Codis Ha 或者 Sentinel。最後,服務端方案依賴於組件的實現,Memcached 就只支持單機版沒有分佈式和 HA 的方案,而 Redis 在 2.4 版本提供了 Sentinel 方案可以自動進行主從切換。服務端方案會在運維上增加一些複雜度。

緩存穿透解決方案:
回種空值以及使用布隆過濾器

1. 回種空值是一種最常見的解決思路,實現起來也最簡單,如果評估空值緩存佔據的緩存空間可以接受,那麼可以優先使用這種方案;2. 布隆過濾器會引入一個新的組件,也會引入一些開發上的複雜度和運維上的成本。所以只有在存在海量查詢數據庫中,不存在數據的請求時纔會使用,在使用時也要關注布隆過濾器對內存空間的消耗;3. 對於極熱點緩存數據穿透造成的“狗樁效應”,可以通過設置分佈式鎖或者後臺線程定時加載的方式來解決。

CDN 對靜態資源進行加速的原理和使用的核心技術,

這裏你需要了解的重點有以下幾點:

1.DNS 技術是 CDN 實現中使用的核心技術,可以將用戶的請求映射到 CDN 節點上;

2.DNS 解析結果需要做本地緩存,降低 DNS 解析過程的響應時間;

3.GSLB 可以給用戶返回一個離着他更近的節點,加快靜態資源的訪問速度

5)消息隊列

削峯填谷,解耦合

削峯填谷是消息隊列最主要的作用,但是會造成請求處理的延遲。

異步處理是提升系統性能的神器,但是你需要分清同步流程和異步流程的邊界,同時消息存在着丟失的風險,我們需要考慮如何確保消息一定到達。

解耦合可以提升你的整體系統的魯棒性。

消息丟失:

要保證消息只被消費一次,首先就要保證消息不會丟失。那麼消息從被寫入到消息隊列,到被消費者消費完成,這個鏈路上會有哪些地方存在丟失消息的可能呢?

其實,主要存在三個場景:

消息從生產者寫入到消息隊列的過程。

消息在消息隊列中的存儲場景。

消息被消費者消費的過程。

A. 在消息生產的過程中丟失消息:重傳一次

B.消息隊列中丟失:以集羣方式部署 Kafka 服務,通過部署多個副本備份數據,保證消息儘量不丟失

C.在消費的過程中存在消息丟失的可能,等待消費完成信號,否則重傳

如何保證消息只被消費一次:

冪等,它的含義是多次執行同一個操作和執行一次操作,最終得到的結果是相同的。

在生產、消費過程中增加消息冪等性的保證消息在生產和消費的過程中都可能會產生重複,所以你要做的是,在生產過程和消費過程中增加消息冪等性的保證,這樣就可以認爲從“最終結果上來看”,消息實際上是隻被消費了一次的。

做法是給每一個生產者一個唯一的 ID,並且爲生產的每一條消息賦予一個唯一 ID,消息隊列的服務端會存儲 < 生產者 ID,最後一條消息 ID> 的映射。當某一個生產者產生新的消息時,消息隊列服務端會比對消息 ID 是否與存儲的最後一條 ID 一致,如果一致,就認爲是重複的消息,服務端會自動丟棄

減少消息延遲:

提升消費者的消息處理能力:

優化消費代碼提升性能;消息的存儲;零拷貝技術

增加消費者的數量(這個方式比較簡單)

 

消息隊列

服務架構

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