因為考慮到比如業務需求的性質、托管基礎設施的類型(例如雲或本地)、成本等許多因素,本文不是一種放之四海而皆準的方法。隻是提供了一些通用的經驗總結。
高并發意味着什麼?
高并發指的是系統在同一時間段内同時處理大量的請求或任務。包括大量讀取資料、寫入資料、計算任務等。
特征:瞬時、大量請求。系統需要同時、短時處理大量的請求,可能是數千甚至數百萬個并發請求,毫秒級别。
在面對高并發的情況下,系統需要進行合理的架構設計、性能優化和資源管理,以確定系統能夠有效地應對大量的并發請求,提供穩定、高效的服務。
高并發意味着暴風驟雨來臨 ,非常挑戰我們的處理經驗。
衡量并發度的名額
2016 年“雙 11”支付寶每秒峰值達 12 萬筆支付。
2017 年春節微信紅包收發紅包每秒達到 76 萬個。
這些都是描述并發程度的内容。
一般通過以下幾個名額來描述并發程度:
- 吞吐量(Throughput):吞吐量是指系統在機關時間内能夠處理的任務數量。這是衡量系統并發處理能力的一個重要名額。通常,吞吐量越高,表明系統的并發處理能力越強。
- 響應時間(Response Time):響應時間是指系統從接收請求到傳回響應所花費的時間。在高并發環境下,響應時間可能會增加,是以,保持較低的響應時間是提高使用者體驗的關鍵。
- 并發使用者數(Concurrent Users):并發使用者數是指在同一時間内,系統正在服務的使用者數量。這是衡量系統并發處理能力的另一個重要名額。
- 請求并發數(Concurrent Requests):請求并發數是指系統在同一時間内正在處理的請求數量。對于Web伺服器或應用伺服器來說,這是一個重要的性能名額。
帶來的問題和挑戰
幾乎所有的系統面臨暴風驟雨來臨,随着流量變大,會遇到各種各樣的技術問題,如果沒有,可能隻是你的并發度還不夠高。在超高并發下依然面臨着普遍的問題,從系統整體來看,會表征為以下情況:
性能問題,如通路逾時,CPU load升高、GC頻繁、死鎖、大資料量存儲等等。
系統可用性降低,可能帶來大量5xx、資料庫壓力增大甚至崩潰
資料一緻性
這些問題以及正常的業務逐漸複雜,另外一般還需關注到的安全、成本等,系統的複雜度會幾何增長,是以如何綜合解決這些複雜的問題,我們必須跳出代碼,有總攬全局的思維,從架構的層面去分析解決這些問題。
這裡我們從宏觀架構思維,講解如何在無限的限制下,從全局系統,利用已有模式解決gao'bing'f系統的複雜性。
如何提升性能
性能問題是一個綜合性問題,隻是一個表征,伴随着性能問題的出現,一般會出現記憶體、cpu過載、網絡帶寬不足等。
是以從架構上來說,一般的解決模式,總結起來三點
- 分治,拆,分布式叢集化,合理規劃容量或者可以支援彈性伸縮,避免單點資源不足引起的過載問題
- 資源複用或者再利用,如緩存、池化
- 延遲處理,如異步化
其它還有一些優化邏輯或者技術實作的方法,如合并請求減少IO次數、IO多路複用、多線程、索引優化等。
性能優化原則
- 問題導向,不要過早進行優化,避免增加系統複雜度,同時也浪費研發人力
- 遵循二八原則 要抓住主要沖突,優先優化主要的性能瓶頸
- 優化需要有資料支撐 要時刻了解你的優化讓響應時間減少了多少,提升了多少的吞吐量。可以使用平均值、極值(最大/最小值)、分位值等作為統計的特征值
分治
分治的目的有兩個,一個是人類的思維有限,拆分子問題抓主要沖突,另一個是單機的性能有限,雖然從第1台計算機誕生至今已經可以支援從每秒幾次提升到每秒幾億次的運算,現代計算叢集化是不可避免的态勢。
無論微觀的程序拆分線程到多程序、多線程等技術,還是宏觀的從單體到SOA到微服務其實都是是追循的這個思路。
宏觀上的這種解決方案是有效的,但帶來很多的複雜性:
- 每個子問題如何配置設定到不同機器
- 子問題的拆分邏輯
針對第一個問題,很自然的我們可以使用一個排程器來進行機器的配置設定。
在web開發中排程器基本等價于負載均衡器的作用,負載均衡的種類及算法很多,包括:
- 硬體負載均衡和軟體負載均衡。硬體負載均衡通常是指專用的負載均衡裝置,如 F5、Radware 等;軟體負載均衡是指基于軟體實作的負載均衡,如 Nginx、HAProxy 等。
- 負載均衡算法:
- 輪詢(Round Robin)
- 權重輪詢(Weighted Round Robin)
- 最少連接配接(Least Connections)
- 權重最少連接配接(Weighted Least Connections)
- 随機(Random)
- 哈希與一緻性哈希
高性能資料庫
騰訊雲MySQL8.0,隻有部分資料可以放到緩存裡,查詢過程中需要讀寫磁盤更新緩存場景下的基準測試。
CPU | 記憶體 (MB) | 并發度 | 單表資料量 | 表總數 | SysBench TPS | SysBench QPS | avg_lat |
1 | 1000 | 8 | 800000 | 6 | 582.76 | 11655.1 | 13.73 |
1 | 2000 | 8 | 800000 | 12 | 588.92 | 11778.4 | 13.58 |
2 | 4000 | 16 | 800000 | 24 | 899.06 | 17981.2 | 17.8 |
4 | 8000 | 32 | 800000 | 48 | 1915.83 | 38316.6 | 16.7 |
4 | 16000 | 32 | 6000000 | 13 | 1884.2 | 37684 | 16.98 |
8 | 16000 | 64 | 6000000 | 13 | 3356.71 | 67134.2 | 19.06 |
8 | 32000 | 64 | 6000000 | 25 | 3266.73 | 65334.6 | 19.59 |
16 | 32000 | 128 | 6000000 | 25 | 5370.18 | 107404 | 23.83 |
16 | 64000 | 128 | 6000000 | 49 | 5910.85 | 118217 | 21.65 |
16 | 96000 | 128 | 6000000 | 74 | 5813.94 | 116279 | 22.01 |
16 | 128000 | 128 | 6000000 | 98 | 5700.06 | 114001 | 22.45 |
單機的MySQL性能有限,在高并發的場景下,如果大量請求通路到資料庫上,MySQL單機很難應付。是以按照上面的理論,從兩個方面對資料做叢集化架構提升性能。
- 讀寫分離,主節點(Master) :負責處理寫操作(如插入、更新、删除),是資料的主要來源。從節點(Slave) :負責處理讀操作(如查詢),從主節點同步資料,并提供讀取資料的服務。
可能主從延遲導緻的資料一緻性問題,這需要一些權衡及優化政策。
- 分庫分表,資料庫水準拆分的政策,用于解決單一資料庫存儲容量有限、性能瓶頸。 分庫(Sharding) :将資料按照一定規則分散存儲到多個資料庫執行個體中,每個資料庫執行個體稱為一個分片(Shard),可以通過分片鍵(Shard Key)來确定資料存儲在哪個分片中。 分表(Sharding) :在每個資料庫執行個體中,将資料按照一定規則分散存儲到多個表中,每個表稱為一個分表,可以通過分表鍵(Shard Key)來确定資料存儲在哪個分表中。
- 冷熱分離
其它:根據成本、規模、資源做好資料庫選型,當然也可以使用其他NoSQL。
緩存化
緩存是建構高性能系統的必要且常用的手段,很多場景下,單純依靠存儲系統的性能是不夠的,這時候便有了緩存的應用場景。
緩存可以減少對資料庫的通路、減少網絡IO,進而實作整個系統的性能。
建構緩存常用的元件包括Redis、Memcached等。使用緩存時一定要做好的原則,以Redis為例:
- 使用cluster,合理規劃容量,做好監控預案
- 好的key命名規範及長度
- 避免大key
- 緩存政策
使用緩存時整個系統架構變得更複雜,同樣可能會遇到一些新問題,以下是一些問題及相應的解決思路:
- 緩存穿透:指查詢一個不存在的資料,由于緩存中沒有該資料,每次查詢都會穿透到資料庫,導緻資料庫壓力過大。 解決思路包括使用布隆過濾器攔截不存在的資料、設定空值緩存、使用緩存擊穿解決方案等。
- 緩存擊穿:指某個熱點資料突然失效,導緻大量請求直接通路資料庫,造成資料庫壓力過大。 解決思路包括設定熱點資料永不過期、使用互斥鎖更新緩存、預先加載熱點資料等。
- 緩存雪崩:指大量緩存資料同時失效,導緻大量請求直接通路資料庫,造成資料庫壓力過大。 解決思路包括設定不同的過期時間、使用分布式鎖更新緩存、使用備份緩存等。
- 熱點問題: 指在一個系統中頻繁被通路的資料,通常是一些熱門、熱點的資料。特點包括:高頻、重要、量大。 解決思路包括 : 預熱緩存:在系統啟動或高峰期之前,預先将熱點資料加載到緩存中,提前緩存熱點資料,以提高緩存命中率。 設定合适的緩存過期時間:對于熱點資料,可以設定較長的緩存過期時間,避免頻繁地更新緩存,提高緩存命中率。 使用緩存淘汰政策:針對熱點資料,可以采用合适的緩存淘汰政策,保證緩存中始終存儲重要的熱點資料。 緩存預取:根據使用者的通路模式和行為,預先将可能被通路的資料加載到緩存中,提高緩存命中率。 多級緩存 key 水準拆分
- 緩存資料一緻性:在分布式環境下,緩存資料的一緻性是一個挑戰,可能會出現緩存髒資料的情況。 解決思路包括使用緩存更新政策、使用緩存鎖、使用緩存失效通知等。
池化
在高性能應用中,池化技術是一種常見且有效的技術,用于管理和複用資源,以提高系統的性能和效率。常用的池化技術包括幾個方面:
- 連接配接池(Connection Pool) :在資料庫通路、網絡通信等場景中,連接配接池是一種常見的池化技術。連接配接池會預先建立一定數量的連接配接,當需要進行資料庫查詢或網絡通信時,可以從連接配接池中擷取連接配接,而不是每次都建立新連接配接,進而減少連接配接建立和銷毀的開銷,提高系統性能。
- 線程池(Thread Pool) :在多線程應用中,線程池可以管理和複用線程,避免頻繁地建立和銷毀線程,減少線程切換的開銷,提高系統的并發性能和響應速度。
- 對象池(Object Pool) :對象池用于管理和複用對象執行個體,避免頻繁地建立和銷毀對象,提高系統的記憶體使用率和性能。對象池可以用于複雜對象的建立,例如資料庫連接配接、HTTP連接配接、線程等。
- 記憶體池(Memory Pool) :記憶體池是一種用于管理記憶體配置設定和釋放的技術,通過預先配置設定一塊連續的記憶體空間,并按需配置設定給應用程式,減少記憶體碎片和頻繁的記憶體配置設定操作,提高記憶體管理效率和系統性能。
伺服器模式
通常用于實作高性能伺服器的模式有Reactor和Proactor是兩種設計模式,它們在處理I/O密集型任務時非常有用,可以提高系統的并發性能和響應速度。
- Reactor模式:Reactor模式是一種事件驅動的設計模式,通過一個事件循環(Event Loop)來監聽和分發事件,當有事件發生時,Reactor會調用相應的處理程式來處理事件。Reactor模式通常用于實作基于事件驅動的網絡程式設計,例如基于事件的伺服器。
- Proactor模式:Proactor模式也是一種事件驅動的設計模式,不同于Reactor模式的是,Proactor模式中的I/O操作是由作業系統或架構來完成,而不是應用程式自己處理。應用程式隻需要在I/O操作完成時得到通知,然後進行相應的處理。Proactor模式通常用于實作異步I/O操作。
異步化
異步化模式是一種通過異步處理請求來提高系統性能的方法。在異步化模式下,請求不會直接等待處理結果,而是将請求放入隊列或其他資料結構中,然後立即傳回。這樣,請求處理過程不會阻塞主線程,進而提高系統的吞吐量和響應速度。
異步化通常情況下使用消息隊列進行解耦,延遲處理提升整個系統的吞吐,它本質上是一種延遲處理的技術。
CQRS
CQRS(Command Query Responsibility Segregation,指令查詢職責分離)是一種軟體架構模式,它将資料的讀操作(查詢)和寫操作(指令)分離到不同的模型中。這種分離有助于提高系統的可擴充性、性能和安全性。
CQRS的核心概念是将應用程式分為兩個部分:
- 指令模型(Command Model):負責處理資料的修改操作,如建立、更新或删除。指令模型通常會修改資料存儲并産生相應的事件。這些事件可以用于更新查詢模型或觸發其他業務邏輯。
- 查詢模型(Query Model):負責處理資料的讀取操作,如擷取資料或執行搜尋。查詢模型通常會從資料存儲中擷取資料并将其傳回給用戶端。查詢模型可以針對特定的查詢進行優化,以提高性能。
事件驅動&事件溯源
事件驅動架構(Event-Driven Architecture,EDA)是一種軟體架構模式,它支援應用程式、系統或服務之間通過事件進行異步通信。在事件驅動架構中,事件是狀态變化或特定條件的發生,它們可以觸發其他元件或服務的響應。事件驅動架構允許元件或服務互相獨立,這有助于提高可擴充性、靈活性和故障隔離。
事件驅動架構的主要組成部分包括:
- 事件生産者:負責建立和釋出事件的元件或服務。事件生産者不關心誰訂閱了其釋出的事件,也不關心事件的處理方式。
- 事件消費者:訂閱并處理特定事件的元件或服務。事件消費者與事件生産者之間的通信是間接的,通常通過事件總線或消息隊列實作。
- 事件總線/消息隊列:用于傳輸事件的中間件。事件總線或消息隊列負責将事件從生産者傳遞到消費者,同時確定事件的傳遞可靠、有序和安全。
此外還有一些更微觀的提升性能的技術包括不限于如批量打包、零拷貝、編碼方式、SQL調優、無鎖程式設計。
可用性和彈性伸縮
可用性是指系統無中斷地執行其功能的能力,代表系統的可用性程度,是進行系統設計時的準則之一。
高可用一般衡量的名額為sla 幾個9描述。高可用的設計過程其實也是一個取舍的過程。這也就是為什麼系統可用性永遠隻是說幾個九,永遠缺少那個一。
基本思路
架構方面
備援、故障轉移恢複、治理
觀測方面
日志、名額、追蹤全方位建設
工具方面
做好演練、壓測、故障回報等
CAP理論
CAP理論是分布式計算領域的一個重要理論,它指出在分布式系統中,一緻性(Consistency)、可用性(Availability)和分區容錯性(Partition Tolerance)這三個核心名額無法同時達到最優。換句話說,當我們設計一個分布式系統時,必須在這三個名額中權衡取舍。
CAP理論的三個名額定義如下:
- 一緻性(Consistency):一緻性是指分布式系統中的所有節點在同一時刻具有相同的資料副本。簡單來說,當一個用戶端對資料進行寫操作後,其他用戶端能立即讀取到最新的資料。
- 可用性(Availability):可用性是指分布式系統在正常運作和故障情況下都能對用戶端提供服務。換句話說,當用戶端送出請求時,系統總是能在有限的時間内傳回一個響應,無論是成功還是失敗。
- 分區容錯性(Partition Tolerance):分區容錯性是指分布式系統在網絡分區(即節點之間的通信故障)發生時仍能正常運作。在這種情況下,系統可能會出現不一緻或不可用的情況,但它會盡力保持正常運作。
根據CAP理論,我們可以将分布式系統劃分為以下三種類型:
- CA(一緻性和可用性):這類系統在沒有網絡分區的情況下可以保證一緻性和可用性。然而,當出現網絡分區時,這類系統很難維持正常運作。傳統的關系型資料庫(如MySQL和PostgreSQL)通常屬于這類系統。
- CP(一緻性和分區容錯性):這類系統在網絡分區發生時仍能保證一緻性,但可能會犧牲可用性。當出現網絡分區時,這類系統可能會拒絕一部分請求,以保證資料的一緻性。ZooKeeper和Google Spanner等分布式資料庫屬于這類系統。
- AP(可用性和分區容錯性):這類系統在網絡分區發生時仍能保證可用性,但可能會犧牲一緻性。當出現網絡分區時,這類系統可能會傳回過時的資料,以保證對用戶端的響應。Cassandra、Couchbase和Amazon Dynamo等NoSQL資料庫屬于這類系統。
也就是說在實際應用中,基本無法保證CA。
C 與 A 之間的取舍可以在同一系統内以非常細小的粒度反複發生,而每一次的決策可能因為具體的操作,乃至因為牽涉到特定的資料或使用者而有所不同。
BASE
BASE 是指基本可用(Basically Available)、軟狀态( Soft State)、最終一緻性( Eventual Consistency),核心思想是即使無法做到強一緻性(CAP 的一緻性就是強一緻性),但應用可以采用适合的方式達到最終一緻性。
它主要是對AP方案的一個延伸,為我們提供思路。
備援
多機高可用
設計目标是當出現部分硬體損壞時,計算任務能夠繼續正常運作。
常用的存雙機備援架構有主備、主從、叢集、分區
其中主備/主從複制一般都會在各種存儲元件如DB、reddis、MongoDB、kafka中實作。
叢集和分區則在計算、存儲架構中都有應用,叢集分為互備式和獨立式兩種,分别對應對稱式和非對稱式。
容災-異地多活
在原有單中心的基礎上,複制多個中心,覆寫應用、存儲,形成異地雙中心。
故障轉移
對等節點的故障轉移,Nginx和服務治理架構均支援一個節點失敗後通路另一個節點。
非對等節點的故障轉移,通過心跳檢測并實施主備切換(比如redis的哨兵模式或者叢集模式、MySQL的主從切換等)。
另外,接口層面的逾時設定、重試政策和幂等設計。
治理政策
限流處理
限流是一種控制請求速率的政策,旨在防止系統過載。通過對請求進行限制,可以確定系統在高并發場景下能夠應對突發流量不會因為資源耗盡而崩潰,常用的包含幾種算法:
- 固定視窗電腦優點是簡單,但存在臨界場景無法限流的情況。
- 漏桶是通過排隊來控制消費者的速率,适合瞬時突發流量的場景,面對恒定流量上漲的場景,排隊中的請求容易逾時餓死。
- 令牌桶允許一定的突發流量通過,如果下遊(callee)處理不了會有風險。
- 滑動視窗計數器可以相對準确地完成限流。
- 自适應限流
熔斷處理
熔斷是一種自動切斷對故障服務通路的政策,以防止故障擴散和系統雪崩。當某個服務出現故障(如連續失敗、逾時等)時,熔斷器會自動打開,阻止對該服務的進一步通路。在熔斷器打開期間,請求會被快速拒絕,而不會消耗系統資源。經過一段時間後,熔斷器會自動進入半開狀态,嘗試放行部分請求以測試服務是否恢複。如果服務恢複正常,熔斷器會關閉;如果仍然故障,熔斷器會繼續保持打開狀态。
降級處理
降級是一種在系統壓力過大或出現故障時,臨時關閉部分非關鍵功能或降低服務品質的政策。通過降級,可以確定關鍵功能的正常運作,同時減輕系統的負擔。降級可以根據不同的政策觸發,如錯誤率、響應時間、系統負載等。降級後的服務可以傳回預設值、緩存資料或錯誤資訊,以便用戶端能夠處理降級情況
前提:做好服務分級。
灰階釋出
能支援按機器次元進行小流量部署,觀察系統日志和業務名額,等運作平穩後再推全量。
可觀測
名額、日志和鍊路追蹤構成可觀測的三大基石,為我們提供架構感覺、瓶頸定位、故障溯源等能力。借助可觀測性,我們可以對系統有更全面和精細的洞察,發現更深層次的系統問題,進而提升可用性。
Logging
ELK大法,一般的日志流程會涉及到埋點、采集、加工、索引、檢索等。是一個複雜的大資料加工工程。
Metrics
主流的名額建設工具包括Prometheus、grafana、influxdb、elastic等。
四大黃金名額:Traffic(QPS)、Latency(延時)、Error(成功率)、Staturation(資源使用率)。
名額設計原則:RED
Tracing
在微服務架構的複雜分布式系統中,一個用戶端請求由系統中大量微服務配合完成處理,這增加了定位問題的難度。如果一個下遊服務傳回錯誤,我們希望找到整個上遊的調用鍊來幫助我們複現和解決問題,類似gdb的backtrace檢視函數的調用棧幀和層級關系。 Tracing在觸發第一個調用時生成關聯辨別Trace ID,我們可以通過RPC把它傳遞給所有的後續調用,就能關聯整條調用鍊。Tracing還通過Span來表示調用鍊中的各個調用之間的關系。
常用的建構全鍊路追蹤的開源元件很多,比如skywalking、jager等。
工具鍊
變被動為主動?在故障之前,盡可能多地識别風險,針對性地加強和防範,而不是等着故障發生。業界有比較成熟的理論和工具,混沌工程和全鍊路壓測。
總結高可用的方案主要從備援、治理、取舍、運維等幾個方向考慮,同時需要有配套的值班機制和故障處理流程,當出現線上問題時,可及時跟進處理。
彈性
彈性架構(Elastic Architecture)是一種能夠自動适應不同負載和故障場景的系統設計。彈性架構具有高度的可擴充性、可用性和容錯性,能夠在面臨流量波動、硬體故障或其他異常情況時保持穩定運作。彈性架構的目标是確定系統能夠在不同的運作環境和業務需求下自動調整資源配置設定,進而實作優化性能和成本的平衡。
要實作彈性的目的,需要做好以下幾個方面:
- 可擴充
- 容錯
- 自動化
- 資源管控
一緻性問題
前面已經介紹了CAP和BASE理論,在分布式系統重處理一緻性問題是一個非常棘手的問題。如何確定多個節點在某一時刻具有相同的資料副本,非常具有挑戰性的,因為需要處理節點故障、網絡延遲、消息丢失等問題。
常用的一緻性解決方案包括:
- 兩階段送出(2PC,Two-Phase Commit):兩階段送出是一種原子性送出協定,用于確定分布式事務的一緻性。在兩階段送出中,有一個協調者(Coordinator)負責管理參與者(Participants)的送出過程。協定分為兩個階段:預送出階段和送出階段。在預送出階段,協調者詢問所有參與者是否準備好送出;在送出階段,協調者根據參與者的回報決定是送出還是復原事務。兩階段送出可以確定分布式一緻性,但可能會導緻阻塞和性能問題。
- 三階段送出(3PC,Three-Phase Commit):三階段送出是兩階段送出的改進版本,通過引入逾時機制和額外的準備階段來減少阻塞問題。在三階段送出中,協定分為準備階段、預送出階段和送出階段。相比于兩階段送出,三階段送出具有更好的容錯性和性能,但仍然存在一定的局限性,如依賴于可靠的網絡和時鐘同步。
- Paxos算法:Paxos算法是一種基于消息傳遞的分布式一緻性算法。它通過多輪投票過程來達成分布式節點之間的共識。Paxos算法可以容忍一定數量的節點故障,但在實際應用中可能會受到消息延遲和複雜性的影響。
- Raft算法:Raft算法是一種為分布式系統提供強一緻性的算法,與Paxos算法具有相似的功能,但更易于了解和實作。Raft算法通過選舉一個上司者(Leader)來協調分布式節點的一緻性。上司者負責處理用戶端請求、日志複制和狀态機更新。當上司者發生故障時,其他節點會自動選舉新的上司者。
- 分布式鎖:分布式鎖是一種在分布式系統中實作互斥通路共享資源的方法。通過使用分布式鎖,可以確定在同一時刻隻有一個節點能夠通路共享資源,進而保證分布式一緻性。分布式鎖可以基于資料庫、緩存(如Redis)或專用的分布式協調服務(如ZooKeeper)實作。
- 最終一緻性(Eventual Consistency):最終一緻性是一種放寬一緻性要求的方法,允許分布式系統在短暫的時間内出現資料不一緻,但最終會達到一緻狀态。最終一緻性可以通過異步複制、向量時鐘、CRDT(Conflict-free Replicated Data Types)等技術實作。相比于強一緻性,最終一緻性具有更好的性能和可擴充性,但可能會導緻資料不一緻的問題。
最後
高并發确實是一個複雜且系統性的問題,由于篇幅有限,諸如分布式Trace、全鍊路壓測、柔性事務都是要考慮的技術點。另外,如果業務場景不同,高并發的落地方案也會存在差異,但是總體的設計思路和可借鑒的方案基本類似。
作者:譯譯譯
連結:https://juejin.cn/post/7355685027857694761