高併發,你真的了解嗎?

2021-10-09 07:01:21 字數 4773 閱讀 7509

摘要:本文介紹高併發系統的度量指標,講述高併發系統的設計思路,再梳理高併發的關鍵技術,最後結合作者的經驗做一些延伸**。

當前,數位化在給企業帶來業務創新,推動企業高速發展的同時,也給企業的it軟體系統帶來了嚴峻的挑戰。面對流量高峰,不同的企業是如何通過技術手段解決高併發難題的呢?

0、引言

軟體系統有三個追求:高效能、高併發、高可用,俗稱三高。三者既有區別也有聯絡,門門道道很多,全面討論需要三天三夜,本篇討論高併發。

高併發(high concurrency)。併發是作業系統領域的乙個概念,指的是一段時間內多工流交替執行的現象,後來這個概念被泛化,高併發用來指大流量、高請求的業務情景,比如春運搶票,電商雙十一,秒殺大促等場景。

本文先介紹高併發系統的度量指標,然後講述高併發系統的設計思路,再梳理高併發的關鍵技術,最後結合作者的經驗做一些延伸**。

比如有100萬次請求,99萬次請求都在10毫秒內響應,其他次數10秒才響應,平均時延不高,但時延高的使用者受不了,所以,就有了tp90/tp99指標,這個指標不是求平均,而是把時延從小到大排序,取排名90%/99%的時延,這個指標越大,對慢請求越敏感。

除此之外,有時候,我們也會關注可用性指標,這可歸到穩定性。

一般而言,使用者感知友好的高併發系統,時延應該控制在250毫秒以內。

什麼樣的系統才能稱為高併發?這個不好回答,因為它取決於系統或者業務的型別。不過我可以告訴你一些眾所周知的指標,這樣能幫助你下次在跟人扯淡的時候稍微靠點兒譜,不至於貽笑大方。

通常,資料庫單機每秒也就能抗住幾千這個量級,而做邏輯處理的服務單台每秒抗幾萬、甚至幾十萬都有可能,而訊息佇列等中介軟體單機每秒處理個幾萬沒問題,所以我們經常聽到每秒處理數百萬、數千萬的訊息中介軟體集群,而像阿某的api閘道器,每日百億請求也有可能。

2、高併發的設計思路

高併發的設計思路有兩個方向:

垂直方向擴充套件,也叫豎向擴充套件

水平方向擴充套件,也叫橫向擴充套件

垂直方向:提公升單機能力

提公升單機處理能力又可分為硬體和軟體兩個方面:

硬體方向,很好理解,花錢公升級機器,更多核更高主頻更大儲存空間更多頻寬

軟體方向,包括用各快的資料結構,改進架構,應用多執行緒、協程,以及上效能優化各種手段,但這玩意兒天花板低,就像提公升個人產出一樣,996、007、最多24 x 7。

水平方向:分布式集群

為了解決分布式系統的複雜性問題,一般會用到架構分層和服務拆分,通過分層做隔離,通過微服務解耦。

這個理論上沒有上限,只要做好層次和服務劃分,加機器擴容就能滿足需求,但實際上並非如此,一方面分布式會增加系統複雜性,另一方面集群規模上去之後,也會引入一堆aiops、服務發現、服務治理的新問題。

因為垂直向的限制,所以,我們通常更關注水平擴充套件,高併發系統的實施也主要圍繞水平方向展開。

3、高併發的關鍵技術

玩具式的網路服務程式,使用者可以直連伺服器,甚至不需要資料庫,直接寫磁碟檔案。但春運購票系統顯然不能這麼做,它肯定扛不住這個壓力,那一般的高併發系統是怎麼做呢?比如某寶這樣的正經系統是怎麼處理高併發的呢?

其實大的思路都差不多,層次劃分 + 功能劃分。可以把層次劃分理解為水平方向的劃分,而功能劃分理解為垂直方向的劃分。

首先,使用者不能直連伺服器,要做分布式就要解決「分」的問題,有多個服務例項就需要做負載均衡,有不同服務型別就需要服務發現。

集群化:負載均衡

負載均衡就是把負載(request)均衡分配到不同的服務例項,利用集群的能力去對抗高併發,負載均衡是服務集群化的實施要素,它分3種:

dns負載均衡,客戶端通過url發起網路服務請求的時候,會去dns伺服器做網域名稱解釋,dns會按一定的策略(比如就近策略)把url轉換成ip位址,同乙個url會被解釋成不同的ip位址,這便是dns負載均衡,它是一種粗粒度的負載均衡,它只用url前半部分,因為dns負載均衡一般採用就近原則,所以通常能降低時延,但dns有cache,所以也會更新不及時的問題。

硬體負載均衡,通過布置特殊的負載均衡裝置到機房做負載均衡,比如f5,這種裝置貴,效能高,可以支撐每秒百萬併發,還能做一些安全防護,比如防火牆。

軟體負載均衡,根據工作在iso 7層網路模型的層次,可分為四層負載均衡(比如章文嵩博士的lvs)和七層負載均衡(nginx),軟體負載均衡配置靈活,擴充套件性強,阿某雲的slb作為服務對外售賣,nginx可以對url的後半部做解釋承擔api閘道器的職責。

所以,完整的負載均衡鏈路是 client <-> dns負載均衡 -> f5 -> lvs/slb -> nginx

不管選擇哪種lb策略,或者組合lb策略,邏輯上,我們都可以視為負載均衡層,通過新增負載均衡層,我們將負載均勻分散到了後面的服務集群,具備基礎的高併發能力,但這只是萬里長征第一步。

資料庫層面:分庫分表+讀寫分離

前面通過負載均衡解決了無狀態服務的水平擴充套件問題,但我們的系統不全是無狀態的,後面通常還有有狀態的資料庫,所以解決了前面的問題,儲存有可能成為系統的瓶頸,我們需要對有狀態儲存做分片路由。

資料庫的單機qps一般不高,也就幾千,顯然滿足不了高併發的要求。

所以,我們需要做分庫分表 + 讀寫分離。

就是把乙個庫分成多個庫,部署在多個資料庫服務上,主庫承載寫請求,從庫承載讀請求。從庫可以掛載多個,因為很多場景寫的請求遠少於讀的請求,這樣就把對單個庫的壓力降下來了。

如果寫的請求上公升就繼續分庫分表,如果讀的請求上公升就掛更多的從庫,但資料庫天生不是很適合高併發,而且資料庫對機器配置的要求一般很高,導致單位服務成本高,所以,這樣加機器抗壓力成本太高,還得另外想招。

讀多寫少:快取

快取的理論依據是區域性性原理。

一般系統的寫入請求遠少於讀請求,針對寫少讀多的場景,很適合引入快取集群。

在寫資料庫的時候同時寫乙份資料到快取集群裡,然後用快取集群來承載大部分的讀請求,因為快取集群很容易做到高效能,所以,這樣的話,通過快取集群,就可以用更少的機器資源承載更高的併發。

快取的命中率一般能做到很高,而且速度很快,處理能力也強(單機很容易做到幾萬併發),是理想的解決方案。

cdn本質上就是快取,被使用者大量訪問的靜態資源快取在cdn中是目前的通用做法。

高寫入:訊息中介軟體

同理,通過跟主庫加機器,耗費的機器資源是很大的,這個就是資料庫系統的特點所決定的。

相同的資源下,資料庫系統太重太複雜,所以併發承載能力就在幾千/s的量級,所以此時你需要引入別的一些技術。

比如說訊息中介軟體技術,也就是mq集群,它是非常好的做寫請求非同步化處理,實現削峰填谷的效果。

訊息佇列能做解耦,在只需要最終一致性的場景下,很適合用來配合做流控。

假如說,每秒是1萬次寫請求,其中比如5千次請求是必須請求過來立馬寫入資料庫中的,但是另外5千次寫請求是可以允許非同步化等待個幾十秒,甚至幾分鐘後才落入資料庫內的。

那麼此時完全可以引入訊息中介軟體集群,把允許非同步化的每秒5千次請求寫入mq,然後基於mq做乙個削峰填谷。比如就以平穩的1000/s的速度消費出來然後落入資料庫中即可,此時就會大幅度降低資料庫的寫入壓力。

業界有很多著名的訊息中介軟體,比如zeromq,rabbitmq,kafka等。

訊息佇列本身也跟快取系統一樣,可以用很少的資源支撐很高的併發請求,用它來支撐部分允許非同步化的高併發寫入是很合適的,比使用資料庫直接支撐那部分高併發請求要減少很多的機器使用量。

避免擠兌:流控

再強大的系統,也怕流量短事件內集中爆發,就像銀行怕擠兌一樣,所以,高併發另乙個必不可少的模組就是流控。

流控的關鍵是流控演算法,有4種常見的流控演算法。

計數器演算法(固定視窗):計數器演算法是使用計數器在週期內累加訪問次數,當達到設定的限流值時,觸發限流策略,下乙個週期開始時,進行清零,重新計數,實現簡單。計數器演算法方式限流對於週期比較長的限流,存在很大的弊端,有嚴重的臨界問題。

滑動視窗演算法:將時間週期分為n個小週期,分別記錄每個小週期內訪問次數,並且根據時間滑動刪除過期的小週期,當滑動視窗的格仔劃分的越多,那麼滑動視窗的滾動就越平滑,限流的統計就會越精確。此演算法可以很好的解決固定視窗演算法的臨界問題。

漏桶演算法:訪問請求到達時直接放入漏桶,如當前容量已達到上限(限流值),則進行丟棄(觸發限流策略)。漏桶以固定的速率進行釋放訪問請求(即請求通過),直到漏桶為空。分布式環境下實施難度高。

令牌桶演算法:程式以r(r=時間週期/限流值)的速度向令牌桶中增加令牌,直到令牌桶滿,請求到達時向令牌桶請求令牌,如獲取到令牌則通過請求,否則觸發限流策略。分布式環境下實施難度高。

4、高併發的實踐經驗

接入-邏輯-儲存是經典的網際網路後端分層,但隨著業務規模的提高,邏輯層的複雜度也上公升了,所以,針對邏輯層的架構設計也出現很多新的技術和思路,常見的做法包括系統拆分,微服務。

除此之外,也有很多業界的優秀實踐,包括某信伺服器通過協程(無侵入,已開源libco)改造,極大的提高了系統的併發度和穩定性,另外,快取預熱,預計算,批量讀寫(減少io),池技術等也廣泛應用在實踐中,有效的提公升了系統併發能力。

為了提公升併發能力,邏輯後端對請求的處理,一般會用到生產者-消費者多執行緒模型,即i/o執行緒負責網路io,協議編譯碼,網路位元組流被解碼後產生的協議物件,會被包裝成task投入到task queue,然後worker執行緒會從該佇列取出task執行,有些系統會用多程序而非多執行緒,通過共享儲存,維護2個方向的shm queue,乙個input q,乙個output q,為了提高併發度,有時候會引入協程,協程是使用者執行緒態的多執行流,它的切換成本更低,通常有更好的排程效率。

另外,構建漏斗型業務或者系統,從客戶端請求到接入層,到邏輯層,到db層,層層遞減,過濾掉請求,fail fast(盡早發現盡早過濾),嘴大**小,哈哈。

漏斗型系統不僅僅是乙個技術模型,它也可以是乙個產品思維,配合產品的使用者分流,邏輯分離,可以構建全方位的立體模型。

5、小結

莫讓浮雲遮望眼,除去繁華識真顏。我們不能掌握了大方案,吹完了牛皮,而忽視了程式設計最本質的東西,掌握最基本最核心的程式設計能力,比如資料架構和演算法,設計,慣用法,培養技術的審美,也是很重要的,既要致高遠,又要盡精微。

關於併發你真的了解嗎?(二)

1.如果web伺服器cpu使用率很低但是又需要更高效的處理併發連線請求,您可以嘗試設定最大工作程序數。iis作為windows平台下asp.net 發布的預設web伺服器,在效能上是提供了比較大的彈性和可伸縮性,通過應用程式池工作程序數的設定,可以支援從幾十到上萬併發數量的訪問。在主流的主機上,每個...

你真的了解Java嗎?

三目運算子規則 如果第二個和第三個運算元具有相同的型別,那麼它就是條件表示式的類 型。換句話說,你可以通過繞過混合型別的計算來避免 煩。如果乙個運算元的型別是 t,t 表示 byte short 或 char,而另乙個運算元是乙個 int 型別的常量表示式,它的值是可以用型別 t 表示的,那麼條件表...

你真的了解restful api嗎?

在以前,乙個 的完成總是 all in one 頁面,資料,渲染全部在服務端完成,這樣做的最大的弊端是後期維護,擴充套件極其痛苦,開發人員必須同時具備前後端知識。於是慢慢的後來興起了前後端分離的思想 後端負責資料編造,而前端則負責資料渲染,前端靜態頁面呼叫指定api獲取到有固定格式的資料,再將資料展...