电竞比分网-中国电竞赛事及体育赛事平台

分享

高并發(fā),我把握不住啊

 竹隆居士 2021-05-19

慎入,作者高并發(fā)搞得少(沒搞過),這里面水太深,什么高并發(fā),大流量的東西都是虛擬的,作者還太年輕,沒有那個經(jīng)歷,把握不住。系統(tǒng)只有幾QPS,開心快樂就行,不PK,文明PK。

高并發(fā),我把握不住啊

我關注的大佬更新了,在干貨文章的下面有這么一小條:

高并發(fā),我把握不住啊

我承認我有賭的成分,點進去一看,果然是廣告。說真的,內容看起來還是很有吸引力的,但是貧窮阻止了我消費的沖動。

作為一個高并發(fā)的門外漢,嘗試結合學過的課程和一些網(wǎng)上的資料來整理一下對于高并發(fā)的認識?!獙崙?zhàn)是不可能實戰(zhàn)的,只能動動嘴皮這樣子。

什么是高并發(fā)

高并發(fā)指的是系統(tǒng)同時處理很多請求。

高并發(fā)是一個結果導向的東西,例如,常見的高并發(fā)場景有:淘寶的雙11、春運時的搶票、微博大V的熱點新聞等,這些典型場景并不是陡然出世,而是隨著業(yè)務發(fā)展的發(fā)展而逐漸出現(xiàn)。像2020年淘寶雙11全球狂歡季,訂單創(chuàng)建峰值達到了驚人的58.3萬筆/秒,4年前的2016年,這個數(shù)字大概是四分之一,再往前四年,這個數(shù)據(jù)不可考,但是肯定就沒這么夸張了。

高并發(fā)的業(yè)務場景出現(xiàn)了,隨之而來的就是要支持這個高并發(fā)業(yè)務場景的架構——技術要為業(yè)務服務,業(yè)務倒逼技術發(fā)展。高并發(fā)的架構也不是某個天才冥思苦想或者靈機一動,這個過程是隨著業(yè)務的發(fā)展而演進。用一個比喻,先有了秋名山,才到了老司機。

高并發(fā),我把握不住啊

那到底多大并發(fā)才算高并發(fā)呢?

這個本身是沒有具體標準的事情,只看數(shù)據(jù)是不行的,要結合具體的場景。不能說10W QPS的秒殺是高并發(fā),而1W QPS的信息流就不是高并發(fā)。信息流場景涉及復雜的推薦模型和各種人工策略,它的業(yè)務邏輯可能比秒殺場景復雜10倍不止。業(yè)務場景不一樣,執(zhí)行復雜度不一樣,單看并發(fā)量也沒有意義。

總結就是,高并發(fā)無定勢,是要和具體的業(yè)務場景相結合的。無高并發(fā)場景,無高并發(fā)架構。

高并發(fā)目標

宏觀目標

高并發(fā)絕不意味著只追求高性能。從宏觀角度看,高并發(fā)系統(tǒng)設計的目標有三個:高性能、高可用,以及高可擴展。就是所謂的“三高”,三高不是孤立的,而是相互支撐的。

1、高性能:性能體現(xiàn)了系統(tǒng)的并行處理能力,在有限的硬件投入下,提高性能意味著節(jié)省成本。同時,性能也反映了用戶體驗,響應時間分別是100毫秒和1秒,給用戶的感受是完全不同的。

2、高可用:表示系統(tǒng)可以正常服務的時間。一個全年不停機、無故障;另一個隔三差五出線上事故、宕機,用戶肯定選擇前者。另外,如果系統(tǒng)只能做到90%可用,也會大大拖累業(yè)務。

3、高擴展:表示系統(tǒng)的擴展能力,流量高峰時能否在短時間內完成擴容,更平穩(wěn)地承接峰值流量,比如雙11活動、明星離婚等熱點事件。

高并發(fā),我把握不住啊

這3個目標是需要通盤考慮的,因為它們互相關聯(lián)、甚至也會相互影響。

比如說:考慮系統(tǒng)的擴展能力,你需要將服務設計成無狀態(tài)的,這種集群設計保證了高擴展性,其實也間接提升了系統(tǒng)的性能和可用性。

再比如說:為了保證可用性,通常會對服務接口進行超時設置,以防大量線程阻塞在慢請求上造成系統(tǒng)雪崩,那超時時間設置成多少合理呢?一般,我們會參考依賴服務的性能表現(xiàn)進行設置。

具體目標

性能指標

性能指標通過性能指標可以度量目前存在的性能問題,也是高并發(fā)主要關注的指標,性能和流量方面常用的一些指標有

  1. QPS/TPS/HPS:QPS是每秒查詢數(shù),TPS是每秒事務數(shù),HPS是每秒HTTP請求數(shù)。最常用的指標是QPS。

需要注意的是,并發(fā)數(shù)和QPS是不同的概念,并發(fā)數(shù)是指系統(tǒng)同時能處理的請求數(shù)量,反應了系統(tǒng)的負載能力。

并發(fā)數(shù) = QPS?平均響應時間

  1. 響應時間:從請求發(fā)出到收到響應花費的時間,例如一個系統(tǒng)處理一個HTTP請求需要100ms,這個100ms就是系統(tǒng)的響應時間。
  2. 平均響應時間:最常用,但是缺陷很明顯,對于慢請求不敏感。比如 1 萬次請求,其中 9900 次是 1ms,100 次是 100ms,則平均響應時間為 1.99ms,雖然平均耗時僅增加了 0.99ms,但是 1%請求的響應時間已經(jīng)增加了 100 倍。
  3. TP90、TP99 等分位值:將響應時間按照從小到大排序,TP90 表示排在第 90 分位的響應時間, 分位值越大,對慢請求越敏感。
高并發(fā),我把握不住啊
  1. RPS(吞吐量):單位時間內處理的請求量,通常由QPS和并發(fā)數(shù)決定。
    通常,設定性能目標時會兼顧吞吐量和響應時間,比如這樣表述:在每秒 1 萬次請求下,AVG 控制在 50ms 以下,TP99 控制在 100ms 以下。對于高并發(fā)系統(tǒng),AVG 和 TP 分位值必須同時要考慮。另外,從用戶體驗角度來看,200 毫秒被認為是第一個分界點,用戶感覺不到延遲,1 秒是第二個分界點,用戶能感受到延遲,但是可以接受。
    因此,對于一個健康的高并發(fā)系統(tǒng),TP99 應該控制在 200 毫秒以內,TP999 或者 TP9999 應該控制在 1 秒以內。
  2. PV:綜合瀏覽量,即頁面瀏覽量或者點擊量,一個訪客在24小時內訪問的頁面數(shù)量。
  3. UV:獨立訪客 ,即一定時間范圍內相同訪客多次訪問網(wǎng)站,只計算為一個獨立的訪客。
  4. 帶寬: 計算帶寬大小需要關注兩個指標,峰值流量和頁面的平均大小。日網(wǎng)站帶寬可以使用下面的公式來粗略計算:日網(wǎng)站帶寬=pv/統(tǒng)計時間(換算到秒)*平均頁面大?。▎挝籯B)*8

峰值一般是平均值的倍數(shù);

QPS不等于并發(fā)連接數(shù),QPS是每秒HTTP請求數(shù)量,并發(fā)連接數(shù)是系統(tǒng)同時處理的請求數(shù)量:

峰值每秒請求數(shù)(QPS) = (總PV數(shù) * 80%) /(6小時秒數(shù) * 20%)

可用性指標

高可用性是指系統(tǒng)具有較高的無故障運行能力,可用性 = 平均故障時間 / 系統(tǒng)總運行時間,一般使用幾個 9 來描述系統(tǒng)的可用性。

高并發(fā),我把握不住啊

對于大多數(shù)系統(tǒng)。2個9是基本可用(如果達不到開發(fā)和運維可能就要被祭天了),3個9是較高可用,4個9是具有自動恢復能力的高可用。要想達到3個9和4個9很困難,可用性影響因素非常多,很難控制,需要過硬的技術、大量的設備資金投入,工程師要具備責任心,甚至還要點運氣。

可擴展性指標

面對突發(fā)流量,不可能臨時改造架構,最快的方式就是增加機器來線性提高系統(tǒng)的處理能力。

對于業(yè)務集群或者基礎組件來說,擴展性 = 性能提升比例 / 機器增加比例,理想的擴展能力是:資源增加幾倍,性能提升幾倍。通常來說,擴展能力要維持在 70%以上。

但是從高并發(fā)系統(tǒng)的整體架構角度來看,擴展的目標不僅僅是把服務設計成無狀態(tài)就行了,因為當流量增加 10 倍,業(yè)務服務可以快速擴容 10 倍,但是數(shù)據(jù)庫可能就成為了新的瓶頸。

像 MySQL 這種有狀態(tài)的存儲服務通常是擴展的技術難點,如果架構上沒提前做好規(guī)劃(垂直和水平拆分),就會涉及到大量數(shù)據(jù)的遷移。

我們需要站在整體架構的角度,而不僅僅是業(yè)務服務器的角度來考慮系統(tǒng)的擴展性 。所以說,數(shù)據(jù)庫、緩存、依賴的第三方、負載均衡、交換機帶寬等等都是系統(tǒng)擴展時需要考慮的因素。我們要知 道系統(tǒng)并發(fā)到了某一個量級之后,哪一個因素會成為我們的瓶頸點,從而針對性地進行擴展。

高并發(fā)架構演進

誰不是生下來就是老司機,架構也不是架起來就支持高并發(fā)。我們來看一個經(jīng)典的架構演進的例子——淘寶,真實詮釋了“好的架構是進化來的,不是設計來的”。

以下是來自《淘寶技術這十年》描述的淘寶2003—2012年的架構演進。

個人網(wǎng)站

初代淘寶的團隊人員只有十來個,而且面臨千載難逢的商業(yè)機會,所以要求上線的時間越快越好(實際用了不到一個月),那么淘寶的這些牛人是怎么做到的呢?

——買一個。

初代淘寶買了這樣一個架構的網(wǎng)站: LAMP(Linux+Apache+MySQL+PHP)。整個系統(tǒng)的架構如下:

高并發(fā),我把握不住啊

最后開發(fā)的網(wǎng)站是這樣的:

高并發(fā),我把握不住啊

由于商品搜索比較占用數(shù)據(jù)庫資源,后來還引入了阿里巴巴的搜索引擎iSearch。

Oracle/支付寶/旺旺

淘寶飛速發(fā)展,流量和交易量迅速提升,給技術帶來了新的問題——MySQL抗不住了。怎么辦?要搞點事情嗎?沒有,淘寶買了Oracle數(shù)據(jù)庫,當然這個也考慮到團隊里有Oracle大牛的原因。

替換了數(shù)據(jù)庫之后的架構:

高并發(fā),我把握不住啊

比較有意思的,當時由于買不起商用的連接池,所以用了一個開源的連接池代理服務SQLRelay,這個代理服務經(jīng)常會死鎖,怎么解決呢?人肉運維,工程師24小時待命,出現(xiàn)問題趕緊重啟SQL Relay服務。

后來為了優(yōu)化存儲,又買了NAS(Network Attached Storage,網(wǎng)絡附屬存儲),NetApp 的 NAS 存儲作為了數(shù)據(jù)庫的存儲設備,加上 Oracle RAC(Real Application Clusters,實時應用集群)來實現(xiàn)負載均衡。

Java 時代 1.0

2004年,淘寶已經(jīng)運行了一年的時間,上面提到的SQLRelay的問題解決不了,數(shù)據(jù)庫必須要用Oracle,所以決定更換開發(fā)語言。

在不拖慢現(xiàn)有業(yè)務發(fā)展的情況下,平滑更換整體的架構,對當時的淘寶仍然是個有挑戰(zhàn)性的事情。所以怎么辦?淘寶的解決方案是請了Sun公司的大佬。

當時,由于struts1.x存在很多問題,所以淘寶自研了一套MVC框架。Sun當時在推EJB,所以這套架構里也引入了EJB。

高并發(fā),我把握不住啊

Java 時代 2.0

在之前,淘寶的架構的架構主要思路還是“買”,隨著業(yè)務的發(fā)展,到了2005 年,“買”已經(jīng)很難解決問題了,需要對整個架構進行調整和優(yōu)化,需要綜合考慮容量、性能、成本的問題。

在Java時代2.0,主要做了對數(shù)據(jù)分庫、放棄EJB、引入Spring、加入緩存、加入CDN等。

高并發(fā),我把握不住啊

Java時代3.0

Java時代3.0的最大特點就是淘寶開始從商用轉為“自研”,開始真正創(chuàng)造自己的核心技術,例如緩存存儲引擎Tair,分布式存儲系統(tǒng)TFS。搜索引擎iSearch也進行了升級。引入了自研技術的淘寶架構:

高并發(fā),我把握不住啊

分布式時代1.0

到了2008年的時候,淘寶的業(yè)務進一步發(fā)展。

整個主站系統(tǒng)的容量已經(jīng)到了瓶頸,商品數(shù)在1億個以上,PV在2.5億個以上,會員數(shù)超過了 5000萬個。這時Oracle的連接池數(shù)量都不夠用了,數(shù)據(jù)庫的容量到了極限,即使上層系統(tǒng)加機器也無法繼續(xù)擴容,我們只有把底層的基礎服務繼續(xù)拆分,從底層開始擴容,上層才能擴展,這才能容納以后三五年的增長。

淘寶開始對業(yè)務模塊逐步拆分和服務化改造。例如拆分出了商品中心、商品中心等等。同時引入了一些自研的中間件,如分布式數(shù)據(jù)庫中間件,分布式消息中間件等等。

高并發(fā),我把握不住啊

《淘寶技術這十年》這本書只描述到了2012年,也就是分布式時代。上圖是根據(jù)參考【8】畫的一張圖。

轉眼理2012又快過了十年,這十年,阿里巴巴逐漸進入極盛時代,技術上也是風起云涌,才人輩出。粒度更細的微服務、隔離差距的容器化技術、快速伸縮的云平臺技術…… 如果《淘寶技術這十年》的作者能再寫一個十年,一定也是非常精彩。

按照參考【10】,接下來的淘寶服務化開始逐漸演進到云平臺架構,由于資料實在難找,而且這時候以淘寶的體量,內部的架構復雜度足以寫一本書了。所以接下來的架構演進參考服務端高并發(fā)分布式架構演進之路,是一個牛人以淘寶為模擬對象進行的架構演進,雖然不是淘寶真正的架構技術演進,但也很值得借鑒。

在這里我們略過了微服務架構——分布式時代2.0,微服務本身是更細粒度、更輕量級的服務化,這里插入一個關于微服務很有意思的說法——馬丁老哥老被人說設計的東西不符合面向服務的概念,于是他就自己發(fā)明創(chuàng)造了一個靈活的微服務理論,以后再有人說:馬老師,你又不遵循微服務架構設計的原則了。嗯,你說哪一點不符合,我立馬去改微服務的理論。

容器化時代

前最流行的容器化技術是Docker,最流行的容器管理服務是Kubernetes(K8S),應用/服務可以打包為Docker鏡像,通過K8S來動態(tài)分發(fā)和部署鏡像。Docker鏡像可理解為一個能運行你的應用/服務的最小的操作系統(tǒng),里面放著應用/服務的運行代碼,運行環(huán)境根據(jù)實際的需要設置好。把整個“操作系統(tǒng)”打包為一個鏡像后,就可以分發(fā)到需要部署相關服務的機器上,直接啟動Docker鏡像就可以把服務起起來,使服務的部署和運維變得簡單。

在大促的之前,可以在現(xiàn)有的機器集群上劃分出服務器來啟動Docker鏡像,增強服務的性能,大促過后就可以關閉鏡像,對機器上的其他服務不造成影響。

高并發(fā),我把握不住啊

云平臺時代

在服務化的時候,淘寶已經(jīng)演進到了云平臺架構。

所謂的云平臺,就是把海量機器資源,通過統(tǒng)一的資源管理,抽象為一個資源整體,在之上可按需動態(tài)申請硬件資源(如CPU、內存、網(wǎng)絡等),并且之上提供通用的操作系統(tǒng),提供常用的技術組件(如Hadoop技術棧,MPP數(shù)據(jù)庫等)供用戶使用,甚至提供開發(fā)好的應用,用戶不需要關系應用內部使用了什么技術,就能夠解決需求(如音視頻轉碼服務、郵件服務、個人博客等)。

高并發(fā),我把握不住啊

簡單總結一下:高并發(fā)的架構某種程度上是逼出來的,一般人誰能想到淘寶當年拋棄php是因為解決不了數(shù)據(jù)庫連接池的問題。架構演進就像是西湖的水——西湖的水,工程師的淚,說起來容易,里面究竟滅了多少火,填了多少坑。我們外人看到的平湖秋波,里面水很深。

高并發(fā)架構實現(xiàn)

想讓系統(tǒng)抗住更多的并發(fā),主要就是兩個方向:

  • 縱向擴展:1、提升單機的硬件性能:通過增加內存、 CPU核數(shù)、存儲容量、或者將磁盤 升級成SSD等堆硬件的方式來提升2、提升單機的軟件性能:使用緩存減少IO次數(shù),使用并發(fā)或者異步的方式增加吞吐量。
  • 橫向擴展:單機性能總會存在極限,所以最終還需要引入橫向擴展,通過集群部署以進一步提高并發(fā)處理能力。1、做好分層架構:這是橫向擴展的前提,因為高并發(fā)系統(tǒng)往往業(yè)務復雜,通過分層處理可以簡化復雜問題,更容易做到橫向擴展。2、各層進行水平擴展:無狀態(tài)水平擴容,有狀態(tài)做分片路由。業(yè)務集群通常能設計成無狀態(tài)的,而數(shù)據(jù)庫和緩存往往是有狀態(tài)的,因此需要設計分區(qū)鍵做好存儲分片,當然也可以通過主從同步、讀寫分離的方案提升讀性能。

用一個比喻,你要去打十個大漢,你大概是打不過的,最好的結果就是他們打不倒你——吊起來打。所以這時候就得想辦法了。第一個辦法就是努力鍛煉,然后全副武裝,也許還有點希望,這就是縱向擴展;第二個辦法,不行,你一看對面人多,你就叫了十九個兄弟,然后你們二十個打他們十個,唉,這下看上去能打的過了,這就是橫向擴展;還有第三個不常用的辦法,你找個門把住,每次就放一個大漢進來,打倒一個再放下一個,這個就是削峰限流的做法。

高并發(fā),我把握不住啊

我們看一下一個大概的支持三高的典型架構:

高并發(fā),我把握不住啊

接下來,我們從上往下,看一下,各層的一些關鍵技術。

網(wǎng)絡層

多機器

堆機器不是萬能的,不堆機器是萬萬不能的。

我們努力地升級改造架構,最后讓我們提供的服務能夠快速橫向擴展。橫向擴展的基礎同樣是要有一定數(shù)量的、一定性能的機器。

還是上面哪個比喻,你要打十個大漢,等你努力練成了葉師傅,你突然發(fā)現(xiàn)對面的孩子都長大了,人數(shù)×2,這時候你還是得叫兄弟。

一般狗大戶大廠在全國各地都有機房,可能光北京就有兩個,把不同地方的請求分到不同的機房,再分到不同的集群,再分到不同的機器,這么一勻,就在服務能扛的范疇之內了。我們大概來看一下,怎么估算所需機器的數(shù)量。

  • 通過QPS和PV計算部署服務器的臺數(shù)

單臺服務器每天PV計算:

公式1:每天總PV = QPS * 3600 * 6
公式2:每天總PV = QPS * 3600 * 8

服務器計算:

服務器數(shù)量 = ceil( 每天總PV / 單臺服務器每天總PV )

  • 峰值QPS和機器計算公式

原理:每天80%的訪問集中在20%的時間里,這20%時間叫做峰值時間

公式:( 總PV數(shù) * 80% ) / ( 每天秒數(shù) * 20% ) = 峰值時間每秒請求數(shù)(QPS)

機器:峰值時間每秒QPS / 單臺機器的QPS = 需要的機器。

一般有大流量業(yè)務的公司都實現(xiàn)了多機房,包括同城多機房、跨城多機房、跨國多機房等。為了保證可用性,財大氣粗的公司會預備大量的冗余,一般會保證機器數(shù)是計算峰值所需機器數(shù)的兩倍。需要節(jié)約成本的,也可以考慮當前流行的云平臺,之前熱點事件的時候,微博就從阿里云租了不少云服務器。

DNS

高并發(fā),我把握不住啊

DNS是請求分發(fā)的第一個關口,實現(xiàn)的是地理級別的均衡。dns-server對一個域名配置了多個解析ip,每次DNS解析請求來訪問dns-server。通常會返回離用戶距離比較近的ip,用戶再去訪問ip。例如,北京的用戶訪問北京的機房,南京的用戶訪問南京的資源。

一般不會使用DNS來做機器級別的負載均衡,因為造不起,IP資源實在太寶貴了,例如百度搜索可能需要數(shù)萬臺機器,不可能給每個機器都配置公網(wǎng)IP。一般只會有有限的公網(wǎng)IP的節(jié)點,然后再在這些節(jié)點上做機器級別的負載均衡,這樣各個機房的機器只需要配置局域網(wǎng)IP就行了。

DNS負載均衡的優(yōu)點是通用(全球通用)、成本低(申請域名,注冊DNS即可)。

缺點也比較明顯,主要體現(xiàn)在:

  • DNS 緩存的時間比較長,即使將某臺業(yè)務機器從 DNS 服務器上刪除,由于緩存的原因,還是有很多用戶會繼續(xù)訪問已經(jīng)被刪除的機器。
  • DNS 不夠靈活。DNS 不能感知后端服務器的狀態(tài),只能根據(jù)配置策略進行負載均衡,無法做到更加靈活的負載均衡策略。比如說某臺機器的配置比其他機器要好很多,理論上來說應該多分配一些請求給它,但 DNS 無法做到這一點。

所以對于時延和故障敏感的業(yè)務,有實力的公司可能會嘗試實現(xiàn)HTTP-DNS的功能,即使用HTTP 協(xié)議實現(xiàn)一個私有的 DNS 系統(tǒng)。HTTP-DNS 主要應用在通過 App 提供服務的業(yè)務上,因為在 App 端可以實現(xiàn)靈活的服務器訪問策略,如果是 Web 業(yè)務,實現(xiàn)起來就比較麻煩一些,因為 URL 的解析是由瀏覽器來完成的,只有 Javascript 的訪問可以像 App 那樣實現(xiàn)比較靈活的控制。

CDN

CDN是為了解決用戶網(wǎng)絡訪問時的“最后一公里”效應,本質是一種“以空間換時間”的加速策略,即將內容緩存在離用戶最近的地方,用戶訪問的是緩存的內容,而不是站點實時訪問的內容。

由于CDN部署在網(wǎng)絡運營商的機房,這些運營商又是終端用戶的網(wǎng)絡提供商,因此用戶請求路由的第一跳就到達了CDN服務器,當CDN中存在瀏覽器請求的資源時,從CDN直接返回給瀏覽器,最短路徑返回響應,加快用戶訪問速度。

下面是簡單的CDN請求流程示意圖:

高并發(fā),我把握不住啊

CDN能夠緩存的一般是靜態(tài)資源,如圖片、文件、CSS、Script腳本、靜態(tài)網(wǎng)頁等,但是這些文件訪問頻度很高,將其緩存在CDN可極大改善網(wǎng)頁的打開速度。

反向代理層

我們把這一層叫反向代理層,也可以叫接入層、或者負載層。這一層是流量的入口,是系統(tǒng)抗并發(fā)很關鍵的一層。

還是那個比喻,還是你打十個大漢,這次你叫了十九個兄弟,理想的情況是你們兩個打對面一個,但是你由于太激動,沖在了最前面,結果瞬間被十個大漢暴打……

反向代理會對流量進行分發(fā),保證最終落到每個服務上的流量是服務能扛的范圍之內。

Nginx、LVS、F5

DNS 用于實現(xiàn)地理級別的負載均衡,而 Nginx、 LVS、 F5 用于同一地點內機器級別的負載均衡。其中 Nginx 是軟件的 7 層負載均衡,LVS 是內核的 4 層負載均衡,F(xiàn)5 是硬件的 4 層負載均衡。

軟件和硬件的區(qū)別就在于性能,硬件遠遠高于軟件,Ngxin 的性能是萬級,一般的 Linux 服務器上裝個 Nginx 大概能到 5 萬 / 秒;LVS 的性能是十萬級,據(jù)說可達到 80萬 / 秒;F5 性能是百萬級,從 200 萬 / 秒到 800 萬 / 秒都有。

硬件雖然性能高,但是單臺硬件的成本也很高,一臺最便宜的 F5 都是幾十萬,但是如果按照同等請求量級來計算成本的話,實際上硬件負載均衡設備可能會更便宜,例如假設每秒處理 100 萬請求,用一臺 F5 就夠了,但用 Nginx, 可能要 20 臺,這樣折算下來用 F5 的成本反而低。因此通常情況下,如果性能要求不高,可以用軟件負載均衡;如果性能要求很髙,推薦用硬件負載均衡。

4 層和 7 層的區(qū)別就在于協(xié)議和靈活性。Nginx 支持 HTTP、 E-mail 協(xié)議,而 LVS 和 F5 是 4層負載均衡,和協(xié)議無關,幾乎所有應用都可以做,例如聊天、數(shù)據(jù)庫等。目前很多云服務商都已經(jīng)提供了負載均衡的產(chǎn)品,例如阿里云的 SLB、UCIoud 的 ULB 等,中小公司直接購買即可。

對于開發(fā)而言,一般只需要關注到Nginx這一層面就行了。

高并發(fā),我把握不住啊

負載均衡典型架構

像上面提到的負載均衡機制,在使用中,可以組合使用。

DNS負載均衡用于實現(xiàn)地理級別的負載均衡,硬件件負載均衡用于實現(xiàn)集群級別的負載均衡;軟件負載均衡用于實現(xiàn)機器級別的負載均衡。

高并發(fā),我把握不住啊

整個系統(tǒng)的負載均衡分為三層。

  • 地理級別負載均衡:www.xxx.com 部署在北京、廣州、上海三個機房,當用戶訪問時,DNS 會根據(jù)用戶的地理位置來決定返回哪個機房的 IP,圖中返回了廣州機房的 IP 地址,這樣用戶就訪問到廣州機房了。
  • 集群級別負載均衡:廣州機房的負載均衡用的是 F5 設備,F(xiàn)5 收到用戶請求后,進行集群級別的負載均衡,將用戶請求發(fā)給 3 個本地集群中的一個,我們假設 F5 將用戶請求發(fā)給了 “廣州集群 2” 。
  • 機器級別的負載均衡:廣州集群 2 的負載均衡用的是 Nginx, Nginx 收到用戶請求后,將用戶請求發(fā)送給集群里面的某臺服務器,服務器處理用戶的業(yè)務請求并返回業(yè)務響應。

Nginx負載均衡

我們主要關心是Nginx這一層的負載,通常LVS 和 F5這兩層都是由網(wǎng)絡運維工程師管控。

高并發(fā),我把握不住啊

對于負載均衡我們主要關心的幾個方面如下:

  • 上游服務器配置:使用 upstream server配置上游服務器
  • 負載均衡算法:配置多個上游服務器時的負載均衡機制。
  • 失敗重試機制:配置當超時或上游服務器不存活時,是否需要重試其他上游服務器。
  • 服務器心跳檢查:上游服務器的健康檢查/心跳檢查。

upstream server中文直接翻譯是上游服務器,意思就是負載均衡服務器設置,就是被nginx代理最后真實訪問的服務器。

負載均衡算法

負載均衡算法數(shù)量較多,Nginx主要支持以下幾種負載均衡算法:

1、輪詢(默認)

每個請求按時間順序逐一分配到不同的后端服務,如果后端某臺服務器死機,自動剔除故障系統(tǒng),使用戶訪問不受影響。

2、weight(輪詢權值)

weight的值越大分配到的訪問概率越高,主要用于后端每臺服務器性能不均衡的情況下?;蛘邇H僅為在主從的情況下設置不同的權值,達到合理有效的地利用主機資源。

3、ip_hash

每個請求按訪問IP的哈希結果分配,使來自同一個IP的訪客固定訪問一臺后端服務器,并且可以有效解決動態(tài)網(wǎng)頁存在的session共享問題。

4、fair

比 weight、ip_hash更加智能的負載均衡算法,fair算法可以根據(jù)頁面大小和加載時間長短智能地進行負載均衡,也就是根據(jù)后端服務器的響應時間 來分配請求,響應時間短的優(yōu)先分配。Nginx本身不支持fair,如果需要這種調度算法,則必須安裝upstream_fair模塊。

5、url_hash

按訪問的URL的哈希結果來分配請求,使每個URL定向到一臺后端服務器,可以進一步提高后端緩存服務器的效率。Nginx本身不支持url_hash,如果需要這種調度算法,則必須安裝Nginx的hash軟件包。

失敗重試

Nginx關于失敗重試主要有兩部分配置,upstream server 和 proxy_pass。

通過配置上游服務器的 max_fails和 fail_timeout,來指定每個上游服務器,當fail_timeout時間內失敗了max_fail次請求,則認為該上游服務器不可用/不存活,然后將會摘掉該上游服務器,fail_timeout時間后會再次將該服務器加入到存活上游服務器列表進行重試。

健康檢查

Nginx 對上游服務器的健康檢查默認采用的是惰性策略,Nginx 商業(yè)版提供了healthcheck 進 行 主 動 健 康 檢 查 。當 然 也 可 以 集 成
nginx_upstream_check_module(
https://github.com/yaoweibin/nginx_upstream_check module ) 模塊來進行主動健康檢查。


nginx_upstream_check_module 支持 TCP 心跳和 HTTP 心跳來實現(xiàn)健康檢查。

流量控制

流量分發(fā)

流量分發(fā)就不多說了,上面已經(jīng)講了,是接入層的基本功能。

流量切換

我聽朋友說過一個有意思的事情,他們公司將流量從一個機房切到另一個機房,結果翻車,所有工程師運維平臺一片飄紅,全公司集體圍觀,運維團隊就很丟面子。

流量切換就是在某些情況下,比如機房故障、光纖被挖斷、服務器故障故障情況,或者灰度發(fā)布、A/B等運維測試場景,需要將流量切到不同的機房、服務器等等。

就像我們上面提到的負載均衡典型架構,不同層級的負載負責切換不同層級的流量。

  1. DNS:切換機房入口。
  2. HttpDNS:主要 APP 場景下,在客戶端分配好流量入口,繞過運營商 LocalDNS并實現(xiàn)更精準流量調度。
  3. LVS/HaProxy:切換故障的 Nginx 接入層。
  4. Nginx:切換故障的應用層。

另外,有些應用為了更方便切換,還可以在 Nginx 接入層做切換,通過 Nginx 進行一些流量切換,而沒有通過如 LVS/HaProxy 做切換。

限流

限流是保證系統(tǒng)可用的一個重要手段,防止超負荷的流量直接打在服務上,限流算法主要有令牌桶、漏桶。

高并發(fā),我把握不住啊

可以在很多層面做限流,例如服務層網(wǎng)關限流、消息隊列限流、Redis限流,這些主要是業(yè)務上的限流。

這里我們主要討論的是接入層的限流,直接在流量入口上限流。

對于 Nginx接入層限流可以使用 Nginx自帶的兩個模塊:連接數(shù)限流模塊
ngx_http_limit_conn_module
和漏桶算法實現(xiàn)的請求限流模塊
ngx_http_limit_req_moduleo

還可以使用 OpenResty提供的 Lua限流模塊 ua-resty-limit-traffic應對更復雜的限流場景。

limmit_conn用來對某個 key 對應的總的網(wǎng)絡連接數(shù)進行限流,可以按照如 IP、域名維度進行限流。limit_req用來對某個 key對應的請求的平均速率進行限流,有兩種用法:平滑模式(delay ) 和允許突發(fā)模式(nodelay)。

流量過濾

很多時候,一個網(wǎng)站有很多流量是爬蟲流量,或者直接是惡意的流量。

可以在接入層,對請求的參數(shù)進行校驗,如果參數(shù)校驗不合法,則直接拒絕請求,或者把請求打到專門用來處理非法請求的服務。

最簡單的是使用Nginx,實際場景可能會使用OpenResty,對爬蟲 user-agent 過濾和一些惡意IP (通過統(tǒng)計 IP 訪問量來配置閾值),將它們分流到固定分組,這種情況會存在一定程度的誤殺,因為公司的公網(wǎng) IP —般情況下是同一個,大家使用同一個公網(wǎng)出口 IP 訪問網(wǎng)站,因此,可以考慮 IP+Cookie 的方式,在用戶瀏覽器種植標識用戶身份的唯一 Cookie。訪問服務前先種植 Cookie, 訪問服務時驗證該 Cookie, 如果沒有或者不正確,則可以考慮分流到固定分組,或者提示輸入驗證碼后訪問。

高并發(fā),我把握不住啊

降級

降級也是保證高可用的一把利劍,降級的思路是“棄車保帥”,在眼看著不能保證全局可用的情況下,拋棄或者限制一些不重要的服務。

降級一般分為多個層級,例如在應用層進行降級,通過配置中心設置降級的閾值,一旦達到閾值,根據(jù)不同的降級策略進行降級。

也可以把降級開關前置到接入層,在接入層配置功能降級開發(fā),然后根據(jù)情況行自動/人工降級。后端應用服務出問題時,通過接入層降級,可以避免無謂的流量再打到后端服務,從而給應用服務有足夠的時間恢復服務。

Web層

經(jīng)過一系列的負載均衡,用戶終于請求到了web層的服務。web服務開發(fā)完成,經(jīng)過部署,運行在web服務器中給用戶提供服務。

集群

一般會根據(jù)業(yè)務模塊,來劃分不同的服務,一個服務部署多個實例組成集群。

高并發(fā),我把握不住啊

為了隔離故障,可以再將集群進行分組,這樣一個分組出現(xiàn)問題,也不會影響其它分組。像比較常問的秒殺,通常會將秒殺的服務集群和普通的服務集群進行隔離。

能做到集群化部署的三個要點是無狀態(tài)、拆分、服務化。

  • 無狀態(tài):設計的應用是無狀態(tài)的,那么應用比較容易進行水平擴展。
  • 拆分:設計初期可以不用拆分,但是后期訪問量大的時候,就可以考慮按功能拆分系統(tǒng)。拆分的維度也比較靈活,根據(jù)實際情況來選擇,例如根據(jù)系統(tǒng)維度、功能維度、讀寫維度、AOP 維度、模塊維度等等。
  • 服務化:拆分更多的是設計,服務化是落地,服務化一般都得服務治理的問題。除了最基本的遠程調用,還得考慮負載均衡、服務發(fā)現(xiàn)、服務隔離、服務限流、服務訪問黑白名單等。甚至還有細節(jié)需要考慮,如超時時間、重試機制、服務路由、故障補償?shù)取?/span>

Web服務器

獨立開發(fā)一個成熟的 Web 服務器,成本非常高,況且業(yè)界又有那么多成熟的開源 Web 服務器,所以互聯(lián)網(wǎng)行業(yè)基本上都是 '拿來主義' ,挑選一個流行的開源服務器即可。大一點的公司,可能會在開源服務器的基礎上,結合自己的業(yè)務特點做二次開發(fā),例如淘寶的 Tengine,但一般公司基本上只需要將開源服務器摸透,優(yōu)化一下參數(shù),調整一下配置就差不多了。

服務器的選擇主要和開發(fā)語言相關,例如,Java 的有 Tomcat、JBoss、Resin 等,PHP/Python 的用 Nginx。

Web服務器的性能之類的一般不會成為瓶頸,例如Java最流行的Web服務器Tomcat默認配置的最大請求數(shù)是 150,但是沒有關系,集群部署就行了。

容器

容器是最近幾年才開始火起來的,其中以 Docker 為代表,在 BAT 級別的公司已經(jīng)有較多的應用。

容器化可以說給運維帶來了革命性的變化。Docker 啟動快,幾乎不占資源,隨時啟動和停止,基于Docker 打造自動化運維、智能化運維逐漸成為主流方式。

容器化技術也天生適合當前流行的微服務,容器將微服務進程和應用程序隔離到更小的實例里,使用更少的資源,更快捷地部署。結合容器編排技術,可以更方便快速地搭建服務高可用集群。

高并發(fā),我把握不住啊

服務層

開發(fā)框架

一般,互聯(lián)網(wǎng)公司都會指定一個大的技術方向,然后使用統(tǒng)一的開發(fā)框架。例如,Java 相關的開發(fā)框架 SSH、SpringBoot, Ruby 的 Ruby on Rails, PHP 的 ThinkPHP, Python 的Django 等。

框架的選擇,有一個總的原則:優(yōu)選成熟的框架,避免盲目追逐新技術!

對于一般的螺絲工而言,所做的主要工作都是在這個開發(fā)框架之下。對于開發(fā)語言和框架的使用,一定要充分了解和利用語言和框架的特性。

以Java為例,在作者的開發(fā)中,涉及到一個加密解密的服務調用,服務提供方利用了JNI的技術——簡單說就是C語言編寫代碼,提供api供Java調用,彌補了Java相對沒那么底層的劣勢,大大提高了運算的速度。

在服務開發(fā)這個日常工作的層面,可以做到這些事情來提高性能:

  • 并發(fā)處理,通過多線程將串行邏輯并行化。
  • 減少IO次數(shù),比如數(shù)據(jù)庫和緩存的批量讀寫、RPC的批量接口支持、或者通過冗余數(shù)據(jù)的方式干掉RPC調用。
  • 減少IO時的數(shù)據(jù)包大小,包括采用輕量級的通信協(xié)議、合適的數(shù)據(jù)結構、去掉接口中的多余字段、減少緩存key的大小、壓縮緩存value等。
  • 程序邏輯優(yōu)化,比如將大概率阻斷執(zhí)行流程的判斷邏輯前置、For循環(huán)的計算邏輯優(yōu)化,或者采用更高效的算法
  • 各種池化技術的使用和池大小的設置,包括HTTP請求池、線程池(考慮CPU密集型還是IO密集型設置核心參數(shù))、數(shù)據(jù)庫和Redis連接池等。
  • JVM優(yōu)化,包括新生代和老年代的大小、GC算法的選擇等,盡可能減少GC頻率和耗時。
  • 鎖選擇,讀多寫少的場景用樂觀鎖,或者考慮通過分段鎖的方式減少鎖沖突。

可以通過這些事情來提高可用性:

  • 設置合適的超時時間、重試次數(shù)及機制,必要時要及時降級,返回兜底數(shù)據(jù)等,防止把服務提方供打崩
  • 防重設計:通過防重key、防重表等方式實現(xiàn)防重
  • 冪等設計:在接口層面實現(xiàn)冪等設計

服務中心

當系統(tǒng)數(shù)量不多的時候,系統(tǒng)間的調用一般都是直接通過配置文件記錄在各系統(tǒng)內部的,但當系統(tǒng)數(shù)量多了以后,這種方式就存在問題了。

比如說總共有 10 個系統(tǒng)依賴 A 系統(tǒng)的 X 接口,A 系統(tǒng)實現(xiàn)了一個新接口 Y, 能夠更好地提供原有 X 接口的功能,如果要讓已有的 10 個系統(tǒng)都切換到 Y 接口,則這 10 個系統(tǒng)的幾十上百臺器的配置都要修改,然后重啟,可想而知這個效率是很低的。

服務中心的實現(xiàn)主要采用服務名字系統(tǒng)。

  • 服務務名字系統(tǒng) (Service Name System)

看到這個翻譯,相信你會立刻聯(lián)想到 DNS, 即 Domain Name System。沒錯,兩者的性質是基本類似的。

DNS 的作用將域名解析為 IP 地址,主要原因是我們記不住太多的數(shù)字 IP, 域名就容易記住。服務名字系統(tǒng)是為了將 Service 名稱解析為 'host + port + 接口名稱' ,但是和 DNS一樣,真正發(fā)起請求的還是請求方。

高并發(fā),我把握不住啊

在微服務的架構下,實現(xiàn)這個功能的稱之為注冊中心,例如在Java語言體系下,開源的注冊中心有Nacos、Ecuraka等。

配置中心

配置中心就是集中管理各個服務的配置。

在服務不多的時候,各個服務各自管理自己的配置,沒有問題,但是當服務成百上千,再各行其政,就是一個比較頭疼的事。

所以將配置中心抽象成公共的組件,集中配置多個系統(tǒng),操作效率高。

在微服務架構體系下,配置中心的開源方案有SpringCloud的SpringCloud Config、阿里的Nacos等。

服務框架

服務拆分最直接的影響就是本地調用的服務變成了遠程調用,服務消費者A需要通過注冊中心去查詢服務提供者B的地址,然后發(fā)起調用,這個看似簡單的過程就可能會遇到下面幾種情況,比如:

  • 注冊中心宕機;
  • 服務提供者B有節(jié)點宕機;
  • 服務消費者A和注冊中心之間的網(wǎng)絡不通;
  • 服務提供者B和注冊中心之間的網(wǎng)絡不通;
  • 服務消費者A和服務提供者B之間的網(wǎng)絡不通;
  • 服務提供者B有些節(jié)點性能變慢;
  • 服務提供者B短時間內出現(xiàn)問題。

怎么去保證服務消費者成功調用服務生產(chǎn)者?這就是服務治理框架要解決的問題。

在Java語言體系下,目前流行的服務治理框架有SpringCloud和Dubbo。

以SpringCloud為例:

高并發(fā),我把握不住啊
  • Feign封裝RestTemplate實現(xiàn)http請求方式的遠程調用
  • Feign封裝Ribbon實現(xiàn)客戶端負載均衡
  • Euraka集群部署實現(xiàn)注冊中心高可用
  • 注冊中心心跳監(jiān)測,更新服務可用狀態(tài)
  • 集成Hystrix實現(xiàn)熔斷機制
  • Zuul作為API 網(wǎng)關 ,提供路由轉發(fā)、請求過濾等功能
  • Config實現(xiàn)分布式配置管理
  • Sluth實現(xiàn)調用鏈路跟蹤
  • 集成ELK,通過Kafka隊列將日志異步寫入Elasticsearch,通過Kibana可視化查看

SpringCloud是一整套完整微服務解決方案,被稱為“SpringCloud 全家桶”。這里只是簡單地介紹一下。

Dubbo主要提供了最基礎的RPC功能。

不過SpringCloud的RPC采用了HTTP協(xié)議,可能性能會差一些。

利好的是,“SpringCloud2.0”——SpringCloud Alibaba流行了起來,Dubbo也可以完美地融入SpringCloud的生態(tài)。

消息隊列

消息隊列在高性能、高擴展、高可用的架構中扮演著很重要的角色。

消息隊列是用來解耦一些不需要同步調用的服務或者訂閱一些自己系統(tǒng)關心的變化。使用消息隊列可以實現(xiàn)服務解耦(一對多消費)、異步處理、流量削峰/緩沖等。

服務解耦

服務解耦可以降低服務間耦合,提高系統(tǒng)系統(tǒng)的擴展性。

例如一個訂單服務,有多個下游,如果不用消息隊列,那么訂單服務就要調用多個下游。如果需求要再加下游,那么訂單服務就得添加調用新下流的功能,這就比較煩。

引入消息隊列之后,訂單服務就可以直接把訂單相關消息塞到消息隊列中,下游系統(tǒng)只管訂閱就行了。

高并發(fā),我把握不住啊

異步處理

異步處理可以降低響應時間,提高系統(tǒng)性能。

隨著業(yè)務的發(fā)展項目的請求鏈路越來越長,這樣一來導致的后果就是響應時間變長,有些操作其實不用同步處理,這時候就可以考慮采用異步的方式了。

高并發(fā),我把握不住啊

流量削峰/緩沖

流量削峰/緩沖可以提高系統(tǒng)的可用性。

我們前面提到了接入層的限流,在服務層的限流可以通過消息隊列來實現(xiàn)。網(wǎng)關的請求先放入消息隊列中,后端服務盡可能去消息隊列中消費請求。超時的請求可以直接返回錯誤,也可以在消息隊列中等待。

高并發(fā),我把握不住啊

消息隊列系統(tǒng)基本功能的實現(xiàn)比較簡單,但要做到高性能、高可用、消息時序性、消息事務性則比較難。業(yè)界已經(jīng)有很多成熟的開源實現(xiàn)方案,如果要求不高,基本上拿來用即可,例如,RocketMQ、Kafka、ActiveMQ 等。

但如果業(yè)務對消息的可靠性、時序、事務性要求較高時,則要深入研究這些開源方案,提前考慮可能會遇到的問題,例如消息重復消費、消息丟失、消息堆積等等。

平臺層

當業(yè)務規(guī)模比較小、系統(tǒng)復雜度不高時,運維、測試、數(shù)據(jù)分析、管理等支撐功能主要由各系統(tǒng)或者團隊獨立完成。隨著業(yè)務規(guī)模越來越大,系統(tǒng)復雜度越來越高,子系統(tǒng)數(shù)量越來越多,如果繼續(xù)采取各自為政的方式來實現(xiàn)這些支撐功能,會發(fā)現(xiàn)重復工作非常多。所以就會自然地把相關功能抽離出來,作為公共的服務,避免重復造輪子,減少不規(guī)范帶來的溝通和協(xié)作成本。

平臺層是服務化思維下的產(chǎn)物。將公共的一些功能拆分出來,讓相關的業(yè)務服務只專注于自己的業(yè)務,這樣有利于明確服務的職責,方便服務擴展。

同時一些公共的平臺,也有利于各個服務之間的統(tǒng)籌,例如數(shù)據(jù)平臺,可以對數(shù)據(jù)進行聚合,某個服務以前需要一些整合一些數(shù)據(jù)可能要調用多個上游服務,但是引入數(shù)據(jù)平臺以后,只需要從數(shù)據(jù)平臺取數(shù)據(jù)就可以了,可以降低服務的響應時間。

運維平臺

運維平臺核心的職責分為四大塊:配置、部署、監(jiān)控、應急,每個職責對應系統(tǒng)生命周期的一個階段,如下圖所示:

高并發(fā),我把握不住啊
  • 部署:主要負責將系統(tǒng)發(fā)布到線上。例如,包管理、灰度發(fā)布管理、回滾等。
  • 監(jiān)控:主要負責收集系統(tǒng)上線運行后的相關數(shù)據(jù)并進行監(jiān)控,以便及時發(fā)現(xiàn)問題。
  • 應急:主要負責系統(tǒng)出故障后的處理。例如,停止程序、下線故障機器、切換 IP 等。

運維平臺的核心設計要素是“四化'——標準化、平臺化、自動化、可視化。

  • 標準化:要制定運維標準,規(guī)范配置管理、部署流程、監(jiān)控指標、應急能力等,各系統(tǒng)按照運維標準來實現(xiàn),避免不同的系統(tǒng)不同的處理方式。
  • 平臺化:傳統(tǒng)的手工運維方式需要投入大量人力,效率低,容易出錯,因此需要在運維標準化的基礎上,將運維的相關操作都集成到運維平臺中,通過運維平臺來完成運維工作。
  • 自動化:傳統(tǒng)手工運維方式效率低下的一個主要原因就是要執(zhí)行大量重復的操作,運維平臺可以將這些重復操作固化下來,由系統(tǒng)自動完成。
  • 可視化:運維平臺有非常多的數(shù)據(jù),如果全部通過人工去查詢數(shù)據(jù)再來判斷,則效率很低,可視化的主要目的就是為了提升數(shù)據(jù)查看效率。

測試平臺

測試平臺核心的職責當然就是測試了,包括單元測試、集成測試、接口測試、性能測試等,都可以在測試平臺來完成。

測試平臺的核心目的是提升測試效率,從而提升產(chǎn)品質量,其設計關鍵就是自動化。

高并發(fā),我把握不住啊

數(shù)據(jù)平臺

數(shù)據(jù)平臺的核心職責主要包括三部分:數(shù)據(jù)管理、數(shù)據(jù)分析和數(shù)據(jù)應用。每一部分又包含更多的細分領域,詳細的數(shù)據(jù)平臺架構如下圖所示:

高并發(fā),我把握不住啊
  1. 數(shù)據(jù)管理

數(shù)據(jù)管理包含數(shù)據(jù)采集、數(shù)據(jù)存儲、數(shù)據(jù)訪問和數(shù)據(jù)安全四個核心職責,是數(shù)據(jù)平臺的基礎功能。

  • 數(shù)據(jù)采集:從業(yè)務系統(tǒng)搜集各類數(shù)據(jù)。例如,日志、用戶行為、業(yè)務數(shù)據(jù)等,將這些數(shù)據(jù)傳送到數(shù)據(jù)平臺。
  • 數(shù)據(jù)存儲:將從業(yè)務系統(tǒng)采集的數(shù)據(jù)存儲到數(shù)據(jù)平臺,用于后續(xù)數(shù)據(jù)分析。
  • 數(shù)據(jù)訪問:負責對外提供各種協(xié)議用于讀寫數(shù)據(jù)。例如,SQL、 Hive、 Key-Value 等讀寫協(xié)議。
  • 數(shù)據(jù)安全:通常情況下數(shù)據(jù)平臺都是多個業(yè)務共享的,部分業(yè)務敏感數(shù)據(jù)需要加以保護,防止被其他業(yè)務讀取甚至修改,因此需要設計數(shù)據(jù)安全策略來保護數(shù)據(jù)。
  1. 數(shù)據(jù)分析

數(shù)據(jù)分析包括數(shù)據(jù)統(tǒng)計、數(shù)據(jù)挖掘、機器學習、深度學習等幾個細分領域。

  • 數(shù)據(jù)挖掘:數(shù)據(jù)挖掘這個概念本身含義可以很廣,為了與機器學習和深度學習區(qū)分開,這里的數(shù)據(jù)挖掘主要是指傳統(tǒng)的數(shù)據(jù)挖掘方式。例如,有經(jīng)驗的數(shù)據(jù)分析人員基于數(shù)據(jù)倉庫構建一系列規(guī)則來對數(shù)據(jù)進行分析從而發(fā)現(xiàn)一些隱含的規(guī)律、現(xiàn)象、問題等,經(jīng)典的數(shù)據(jù)挖掘案例就是沃爾瑪?shù)钠【婆c尿布的關聯(lián)關系的發(fā)現(xiàn)。
  • 機器學習、深度學習:機器學習和深度學習屬于數(shù)據(jù)挖掘的一種具體實現(xiàn)方式,由于其實現(xiàn)方式與傳統(tǒng)的數(shù)據(jù)挖掘方式差異較大,因此數(shù)據(jù)平臺在實現(xiàn)機器學習和深度學習時,需要針對機器學習和深度學習獨立進行設計。
  1. 數(shù)據(jù)應用

數(shù)據(jù)應用很廣泛,既包括在線業(yè)務,也包括離線業(yè)務。例如,推薦、廣告等屬于在線應用,報表、欺詐檢測、異常檢測等屬于離線應用。數(shù)據(jù)應用能夠發(fā)揮價值的前提是需要有 '大數(shù)據(jù)' ,只有當數(shù)據(jù)的規(guī)模達到一定程度,基于數(shù)據(jù)的分析、挖掘才能發(fā)現(xiàn)有價值的規(guī)律、現(xiàn)象、問題等。如果數(shù)據(jù)沒有達到一定規(guī)模,通常情況下做好數(shù)據(jù)統(tǒng)計就足夠了,尤其是很多初創(chuàng)企業(yè),無須一開始就參考 BAT 來構建自己的數(shù)據(jù)平臺。

管理平臺

管理平臺的核心職責就是權限管理,無論是業(yè)務系統(tǒng)(例如,淘寶網(wǎng)) 、中間件系統(tǒng)(例如,消息隊列 Kafka) , 還是平臺系統(tǒng)(例如,運維平臺) ,都需要進行管理。如果每個系統(tǒng)都自己來實現(xiàn)權限管理,效率太低,重復工作很多,因此需要統(tǒng)一的管理平臺來管理所有的系統(tǒng)的權限。

說到“平臺”,不由地想起這幾年一會兒被人猛吹,一會兒被人唱衰的“中臺”。在平臺里的數(shù)據(jù)平臺,其實已經(jīng)和所謂的“數(shù)據(jù)中臺”類似了?!爸信_”是個概念性的東西,具體怎么實現(xiàn),沒有統(tǒng)一的標準方案。作者所在的公司,也跟風建了中臺,以“數(shù)據(jù)中臺”為例,我們數(shù)據(jù)中臺的建設主要為了數(shù)據(jù)共享和數(shù)據(jù)可視化,簡單說就是把各個業(yè)務模塊的一些數(shù)據(jù)匯聚起來。說起來簡單,落地很難,數(shù)據(jù)匯聚的及時性、數(shù)據(jù)共享的快速響應……最終的解決方案是采購了阿里的一些商業(yè)化組件,花了老鼻子錢,但是效果,不能說一地雞毛,也差不多吧。

緩存層

雖然我們可以通過各種手段來提升存儲系統(tǒng)的性能,但在某些復雜的業(yè)務場景下,單純依靠存儲系統(tǒng)的性能提升不夠的。

絕大部分在線業(yè)務都是讀多寫少。例如,微博、淘寶、微信這類互聯(lián)網(wǎng)業(yè)務,讀業(yè)務占了整體業(yè)務量的 90%以上。以微博為例:一個明星發(fā)一條微博,可能幾千萬人來瀏覽。

如果直接從DB中取數(shù)據(jù),有兩個問題,一個是DB查詢的速度有瓶頸,會增加系統(tǒng)的響應時間,一個是數(shù)據(jù)庫本身的并發(fā)瓶頸。緩存就是為了彌補讀多寫少場景下存儲系統(tǒng)的不足。

在前面我們提到的CDN可以說是緩存的一種,它緩存的是靜態(tài)資源。

從整個架構來看,一般采用多級緩存的架構,在不同層級對數(shù)據(jù)進行緩存,來提升訪問效率。

高并發(fā),我把握不住啊

簡單說一下整體架構和流程,緩存一級一級地去讀取,沒有命中再去讀取下一級,先讀取本地緩存,再去讀取分布式緩存,分布式緩存也沒有命中,最后就得去讀取DB。

分布式緩存

為了提高緩存的可用性,一般采用分布式緩存。分布式緩存一般采用分片實現(xiàn),即將數(shù)據(jù)分散到多個實例或多臺服務器。算法一般釆用取模和一致性哈希。

要采用不過期緩存機制,可以考慮取模機制,擴容時一般是新建一個集群。

高并發(fā),我把握不住啊

而對于可以丟失的緩存數(shù)據(jù),可以考慮一致性哈希,即使其中一個實例出問題只是丟一小部分。

高并發(fā),我把握不住啊

對于分片實現(xiàn)可以考慮客戶端實現(xiàn),或者使用如Twemproxy 中間件進行代理(分片對客戶端是透明的)。

如果使用 Redis, 則 可 以考慮使用 redis-cluster 分布式集群方案。

高并發(fā),我把握不住啊

熱點本地緩存

對于那些訪問非常頻繁的熱點緩存,如果每次都去遠程緩存系統(tǒng)中獲取,可能會因為訪問量太大導致遠程緩存系統(tǒng)請求過多、負載過高或者帶寬過高等問題,最終可能導致緩存響應慢,使客戶端請求超時。

一種解決方案是通過掛更多的從緩存,客戶端通過負載均衡機制讀取從緩存系統(tǒng)數(shù)據(jù)。不過也可以在客戶端所在的應用/代理層本地存儲一份,從而避免訪問遠程緩存,即使像庫存這種數(shù)據(jù),在有些應用系統(tǒng)中也可以進行幾秒鐘的本地緩存,從而降低遠程系統(tǒng)的壓力。

緩存的引入雖然提高了系統(tǒng)的性能,但同時也增加了系統(tǒng)的復雜度,帶來了一些運維的成本。

緩存穿透

緩存穿透是指緩存沒有發(fā)揮作用,業(yè)務系統(tǒng)雖然去緩存查詢數(shù)據(jù),但緩存中沒有數(shù)據(jù),業(yè)務系統(tǒng)需要再次去存儲系統(tǒng)查詢數(shù)據(jù),結果存儲系統(tǒng)也沒有數(shù)據(jù)。

緩存穿透的示意圖:

高并發(fā),我把握不住啊

一般情況下,如果存儲系統(tǒng)中沒有某個數(shù)據(jù),則不會在緩存中存儲相應的數(shù)據(jù),這樣就導致用戶查詢的時候,在緩存中找不到對應的數(shù)據(jù),每次都要去存儲系統(tǒng)中再查詢一遍,然后返回數(shù)據(jù)不存在。緩存在這個場景中并沒有起到分擔存儲系統(tǒng)訪問壓力的作用。

通常情況下,業(yè)務上讀取不存在的數(shù)據(jù)的請求量并不會太大,但如果出現(xiàn)一些異常情況,例如被黑客攻擊,故意大量訪問某些讀取不存在數(shù)據(jù)的業(yè)務,有可能會將存儲系統(tǒng)拖垮。

這種情況的解決辦法有兩種:

一種比較簡單,如果查詢存儲系統(tǒng)的數(shù)據(jù)沒有找到,則直接設置一個默認值(可以是空值,也可以是具體的值) 存到緩存中,這樣第二次讀取緩存時就會獲取到默認值,而不會繼續(xù)訪問存儲系統(tǒng)。

一種需要引入布隆過濾器,它的原理也很簡單就是利用高效的數(shù)據(jù)結構和算法,快速判斷出查詢的Key是否在數(shù)據(jù)庫中存在,不存在直接返回空,存在就去查了DB,刷新KV再返回值。

緩存擊穿

緩存擊穿和緩存穿透也有點難以區(qū)分,緩存穿透表示的是緩存和數(shù)據(jù)庫中都沒有數(shù)據(jù),緩存擊穿表示緩存中沒有數(shù)據(jù)而數(shù)據(jù)庫中有數(shù)據(jù)。緩存擊穿是某個熱點的key失效,大并發(fā)集中對其進行請求,就會造成大量請求讀緩存沒讀到數(shù)據(jù),從而導致高并發(fā)訪問數(shù)據(jù)庫,引起數(shù)據(jù)庫壓力劇增。這種現(xiàn)象就叫做緩存擊穿。

緩存擊穿示意圖:

高并發(fā),我把握不住啊

關鍵在于某個熱點的key失效了,導致大并發(fā)集中打在數(shù)據(jù)庫上。所以要從兩個方面解決,第一是否可以考慮熱點key不設置過期時間,第二是否可以考慮降低打在數(shù)據(jù)庫上的請求數(shù)量。

主要有兩個解決辦法:

  • 利用互斥鎖保證同一時刻只有一個客戶端可以查詢底層數(shù)據(jù)庫的這個數(shù)據(jù),一旦查到數(shù)據(jù)就緩存至Redis內,避免其他大量請求同時穿過Redis訪問底層數(shù)據(jù)庫。這種方式會阻塞其他的線程,此時系統(tǒng)的吞吐量會下降
  • 熱點數(shù)據(jù)緩存永遠不過期。

永不過期有兩種方式:

  • 物理不過期,針對熱點key不設置過期時間
  • 邏輯過期,把過期時間存在key對應的value里,如果發(fā)現(xiàn)要過期了,通過一個后臺的異步線程進行緩存的構建

緩存雪崩

緩存雪崩,指的是是緩存不可用,或者同一時刻是大量熱點key失效。

兩種情況導致的同樣的后果就是大量的請求直接落在數(shù)據(jù)庫上,對于一個高并發(fā)的業(yè)務系統(tǒng)來說,幾百毫秒內可能會接到幾百上千個請求,最嚴重的后果就是直接導致數(shù)據(jù)庫宕機,可能會引起連鎖反應,導致系統(tǒng)崩潰。

高并發(fā),我把握不住啊

緩存雪崩的解決方案可以分為三個維度:

  • 事前:

① 均勻過期:設置不同的過期時間,讓緩存失效的時間盡量均勻,避免相同的過期時間導致緩存雪崩,造成大量數(shù)據(jù)庫的訪問。

② 分級緩存:第一級緩存失效的基礎上,訪問二級緩存,每一級緩存的失效時間都不同。

③ 熱點數(shù)據(jù)緩存永遠不過期。

④ 保證Redis緩存的高可用,防止Redis宕機導致緩存雪崩的問題??梢允褂?Redis集群等方式來避免 Redis 全盤崩潰的情況。

  • 事中:

① 互斥鎖:在緩存失效后,通過互斥鎖或者隊列來控制讀數(shù)據(jù)寫緩存的線程數(shù)量,比如某個key只允許一個線程查詢數(shù)據(jù)和寫緩存,其他線程等待。這種方式會阻塞其他的線程,此時系統(tǒng)的吞吐量會下降

② 使用熔斷機制,限流降級。當流量達到一定的閾值,直接返回“系統(tǒng)擁擠”之類的提示,防止過多的請求打在數(shù)據(jù)庫上將數(shù)據(jù)庫擊垮,至少能保證一部分用戶是可以正常使用,其他用戶多刷新幾次也能得到結果。

  • 事后:

① 開啟Redis持久化機制,盡快恢復緩存數(shù)據(jù),一旦重啟,就能從磁盤上自動加載數(shù)據(jù)恢復內存中的數(shù)據(jù)。

存儲層

不管是為了滿足業(yè)務發(fā)展的需要,還是為了提升自己的競爭力,關系數(shù)據(jù)庫廠商(Oracle、DB2、MySQL 等)在優(yōu)化和提升單個數(shù)據(jù)庫服務器的性能方面也做了非常多的技術優(yōu)化和改進。但業(yè)務發(fā)展速度和數(shù)據(jù)增長速度,遠遠超出數(shù)據(jù)庫廠商的優(yōu)化速度,尤其是互聯(lián)網(wǎng)業(yè)務興起之后,海量用戶加上海量數(shù)據(jù)的特點,單個數(shù)據(jù)庫服務器已經(jīng)難以滿足業(yè)務需要,必須考慮數(shù)據(jù)庫集群的方式來提升性能。

讀寫分離

讀寫分離的基本原理是將數(shù)據(jù)庫讀寫操作分散到不同的節(jié)點上,下面是其基本架構圖:

高并發(fā),我把握不住啊

讀寫分離的基本實現(xiàn)是:

  • 數(shù)據(jù)庫服務器搭建主從集群,一主一從、一主多從都可以。
  • 數(shù)據(jù)庫主機負責讀寫操作,從機只負責讀操作。
  • 數(shù)據(jù)庫主機通過復制將數(shù)據(jù)同步到從機,每臺數(shù)據(jù)庫服務器都存儲了所有的業(yè)務數(shù)據(jù)。
  • 業(yè)務服務器將寫操作發(fā)給數(shù)據(jù)庫主機,將讀操作發(fā)給數(shù)據(jù)庫從機。
高并發(fā),我把握不住啊

讀寫分離的實現(xiàn)邏輯并不復雜,但有兩個細節(jié)點將引入設計復雜度:主從復制延遲和分配機制。

復制延遲

以 MySQL 為例,主從復制延遲可能達到 1 秒,如果有大量數(shù)據(jù)同步,延遲 1 分鐘也是有可能的。

主從復制延遲會帶來一個問題:如果業(yè)務服務器將數(shù)據(jù)寫入到數(shù)據(jù)庫主服務器后立刻 (1 秒 內)進行讀取,此時讀操作訪問的是從機,主機還沒有將數(shù)據(jù)復制過來,到從機讀取數(shù)據(jù)是讀不到最新數(shù)據(jù)的,業(yè)務上就可能出現(xiàn)問題。

比如說將微博的信息同步給審核系統(tǒng),所以我們在更新完主庫之后,會將微博的 ID 寫入消息隊列,再由隊列處理機依據(jù) ID 在從庫中 獲取微博信息再發(fā)送給審核系統(tǒng)。此時如果主從數(shù)據(jù)庫存在延遲,會導致在從庫中獲取不到微博信息,整個流程會出現(xiàn)異常。

高并發(fā),我把握不住啊

解決主從復制延遲的常見方法:

  1. 數(shù)據(jù)的冗余

我們可以在發(fā)送消息隊列時不僅僅發(fā)送微博 ID,而是發(fā)送隊列處理機需要的所有微博信息,借此避免從數(shù)據(jù)庫中重新查詢數(shù)據(jù)。

  1. 使用緩存

我們可以在同步寫數(shù)據(jù)庫的同時,也把微博的數(shù)據(jù)寫入到緩存里面,隊列處理機在獲取微博信息的時候會優(yōu)先查詢緩存,這樣也可以保證數(shù)據(jù)的一致性。

  1. 二次讀取

我們可以對底層數(shù)據(jù)庫訪問的API進行封裝,一次讀取從庫發(fā)現(xiàn)不實時之后再讀取一次,例如我們通過微博ID沒有在從庫里讀到微博,那么第二次就直接去主庫讀取。

  1. 查詢主庫

我們可以把關鍵業(yè)務,或者對實時性有要求的業(yè)務讀寫操作全部指向主機,非關鍵業(yè)務或者實時性要求不高的業(yè)務采用讀寫分離。

分配機制

將讀寫操作區(qū)分開來,然后訪問不同的數(shù)據(jù)庫服務器,一般有兩種方式:程序代碼封裝和中間件封裝。

  1. 程序代碼封裝

程序代碼封裝指在代碼中抽象一個數(shù)據(jù)訪問層(所以有的文章也稱這種方式為 '中間層封裝' ) ,實現(xiàn)讀寫操作分離和數(shù)據(jù)庫服務器連接的管理。例如,基于 Hibernate 進行簡單封裝,就可以實現(xiàn)讀寫分離,基本架構是:

高并發(fā),我把握不住啊

程序代碼封裝的方式具備幾個特點:

  • 實現(xiàn)簡單,而且可以根據(jù)業(yè)務做較多定制化的功能。
  • 每個編程語言都需要自己實現(xiàn)一次,無法通用,如果一個業(yè)務包含多個編程語言寫的多個子系統(tǒng),則重復開發(fā)的工作量比較大。
  • 故障情況下,如果主從發(fā)生切換,則可能需要所有系統(tǒng)都修改配置并重啟。

如果不想自己造輪子,也可以用開源的方案,淘寶的TDDL是比較出名的一個。

高并發(fā),我把握不住啊
  1. 中間件封裝

中間件封裝指的是獨立一套系統(tǒng)出來,實現(xiàn)讀寫操作分離和數(shù)據(jù)庫服務器連接的管理。中間件對業(yè)務服務器提供 SQL 兼容的協(xié)議,業(yè)務服務器無須自己進行讀寫分離。對于業(yè)務服務器來說,訪問中間件和訪問數(shù)據(jù)庫沒有區(qū)別,事實上在業(yè)務服務器看來,中間件就是一個數(shù)據(jù)庫服務器。

其基本架構是:

高并發(fā),我把握不住啊

數(shù)據(jù)庫中間件的方式具備的特點是:

  • 能夠支持多種編程語言,因為數(shù)據(jù)庫中間件對業(yè)務服務器提供的是標準 SQL 接口。
  • 數(shù)據(jù)庫中間件要支持完整的 SQL 語法和數(shù)據(jù)庫服務器的協(xié)議(例如,MySQL 客戶端和服務器的連接協(xié)議) ,實現(xiàn)比較復雜,細節(jié)特別多,很容易出現(xiàn) bug, 需要較長的時間才能穩(wěn)定。
  • 數(shù)據(jù)庫中間件自己不執(zhí)行真正的讀寫操作,但所有的數(shù)據(jù)庫操作請求都要經(jīng)過中間件,中間件的性能要求也很高。
  • 數(shù)據(jù)庫主從切換對業(yè)務服務器無感知,數(shù)據(jù)庫中間件可以探測數(shù)據(jù)庫服務器的主從狀態(tài)。例如,向某個測試表寫入一條數(shù)據(jù),成功的就是主機,失敗的就是從機。

目前開源的數(shù)據(jù)庫中間件有基于 MySQL Proxy 開發(fā)的奇虎 360 的 Atlas 、阿 里 的Cobar、基于 Cobar 開發(fā)的 Mycat 等。

分庫分表

讀寫分離分散了數(shù)據(jù)庫讀寫操作的壓力,但沒有分散存儲壓力,當數(shù)據(jù)量達到干萬甚至上億條的時候,單臺數(shù)據(jù)庫服務器的存儲能力會成為系統(tǒng)的瓶頸,主要體現(xiàn)在這幾個方面:

  • 數(shù)據(jù)量太大,讀寫的性能會下降,即使有索引,索引也會變得很大,性能同樣會下降。
  • 數(shù)據(jù)文件會變得很大,數(shù)據(jù)庫備份和恢復需要耗費很長時間。
  • 數(shù)據(jù)文件越大,極端情況下丟失數(shù)據(jù)的風險越高(例如,機房火災導致數(shù)據(jù)庫主備機都發(fā)生故障)。

基于上述原因,單個數(shù)據(jù)庫服務器存儲的數(shù)據(jù)量不能太大,需要控制在一定的范圍內。為了滿足業(yè)務數(shù)據(jù)存儲的需求,就需要將存儲分散到多臺數(shù)據(jù)庫服務器上。

業(yè)務分庫

業(yè)務分庫指的是按照業(yè)務模塊將數(shù)據(jù)分散到不同的數(shù)據(jù)庫服務器。例如,一個簡單的電商網(wǎng)站,包括用戶、商品、訂單三個業(yè)務模塊,我們可以將用戶數(shù)據(jù)、商品數(shù)據(jù)、訂單數(shù)據(jù)分開放到三臺不同的數(shù)據(jù)庫服務器上,而不是將所有數(shù)據(jù)都放在一臺數(shù)據(jù)庫服務器上。

高并發(fā),我把握不住啊

雖然業(yè)務分庫能夠分散存儲和訪問壓力,但同時也帶來了新的問題,接下來我們詳細分析一下。

  1. join 操作問題

業(yè)務分庫后,原本在同一個數(shù)據(jù)庫中的表分散到不同數(shù)據(jù)庫中,導致無法使用 SQL 的 join 查 詢。

例如: '查詢購買了化妝品的用戶中女性用戶的列表〃 這個功能,雖然訂單數(shù)據(jù)中有用戶的 ID信息,但是用戶的性別數(shù)據(jù)在用戶數(shù)據(jù)庫中,如果在同一個庫中,簡單的 join 查詢就能完成;但現(xiàn)在數(shù)據(jù)分散在兩個不同的數(shù)據(jù)庫中,無法做 join 查詢,只能采取先從訂單數(shù)據(jù)庫中查詢購買了化妝品的用戶 ID 列表,然后再到用戶數(shù)據(jù)庫中查詢這批用戶 ID 中的女性用戶列表,這樣實現(xiàn)就比簡單的 join 查詢要復雜一些。

  1. 事務問題

原本在同一個數(shù)據(jù)庫中不同的表可以在同一個事務中修改,業(yè)務分庫后,表分散到不同的數(shù)據(jù)庫中,無法通過事務統(tǒng)一修改。雖然數(shù)據(jù)庫廠商提供了一些分布式事務的解決方案(例如,MySQL 的 XA) , 但性能實在太低,與高性能存儲的目標是相違背的。

例如,用戶下訂單的時候需要扣商品庫存,如果訂單數(shù)據(jù)和商品數(shù)據(jù)在同一個數(shù)據(jù)庫中,我們可訂單,如果因為訂單數(shù)據(jù)庫異常導致生成訂單失敗,業(yè)務程序又需要將商品庫存加上;而如果因為業(yè)務程序自己異常導致生成訂單失敗,則商品庫存就無法恢復了,需要人工通過曰志等方式來手工修復庫存異常。

  1. 成本問題

業(yè)務分庫同時也帶來了成本的代價,本來 1 臺服務器搞定的事情,現(xiàn)在要 3 臺,如果考慮備份,那就是 2 臺變成了 6 臺。

基于上述原因,對于小公司初創(chuàng)業(yè)務,并不建議一開始就這樣拆分,主要有幾個原因:初創(chuàng)業(yè)務存在很大的不確定性,業(yè)務不一定能發(fā)展起來,業(yè)務開始的時候并沒有真正的存儲和訪問壓力,業(yè)務分庫并不能為業(yè)務帶來價值。業(yè)務分庫后,表之間的 join 查詢、數(shù)據(jù)庫事務無法簡單實現(xiàn)了。

業(yè)務分庫后,因為不同的數(shù)據(jù)要讀寫不同的數(shù)據(jù)庫,代碼中需要增加根據(jù)數(shù)據(jù)類型映射到不同數(shù)據(jù)庫的邏輯,增加了工作量。而業(yè)務初創(chuàng)期間最重要的是快速實現(xiàn)、快速驗證,業(yè)務分庫會拖慢業(yè)務節(jié)奏。

單表拆分

將不同業(yè)務數(shù)據(jù)分散存儲到不同的數(shù)據(jù)庫服務器,能夠支撐百萬甚至千萬用戶規(guī)模的業(yè)務,但如果業(yè)務繼續(xù)發(fā)展,同一業(yè)務的單表數(shù)據(jù)也會達到單臺數(shù)據(jù)庫服務器的處理瓶頸。例如,淘寶的幾億用戶數(shù)據(jù),如果全部存放在一臺數(shù)據(jù)庫服務器的一張表中,肯定是無法滿足性能要求的,此時就需要對單表數(shù)據(jù)進行拆分。

單表數(shù)據(jù)拆分有兩種方式:垂直分表和水平分表。示意圖如下:

高并發(fā),我把握不住啊

分表能夠有效地分散存儲壓力和帶來性能提升,但和分庫一樣,也會引入各種復雜性。

兩種分表方式可以用一個例子比喻,我們很多人可能都看過這么一篇文章,怎么把蘋果切出星星來,答案是橫著切。

高并發(fā),我把握不住啊
  1. 垂直分表

垂直分表適合將表中某些不常用且占了大量空間的列拆分出去。例如,前面示意圖中的nickname 和 desc 字段,假設我們是一個婚戀網(wǎng)站,用戶在篩選其他用戶的時候,主要是用 age 和 sex 兩個字段進行查詢,而 nickname 和 description 兩個字段主要用于展示,一般不會在業(yè)務查詢中用到。description 本身又比較長,因此我們可以將這兩個字段獨立到另外—張表中,這樣在查詢 age 和 sex 時,就能帶來一定的性能提升。垂直分表引入的復雜性主要體現(xiàn)在表操作的數(shù)量要增加。例如,原來只要一次查詢就可以獲取name、age、sex、nickname、description, 現(xiàn)在需要兩次查詢,—次查詢獲取 name、age、 sex, 另一次查詢獲取 nickname、desc。

不過相比接下來要講的水平分表,這個復雜性就是小巫見大巫了。

  1. 水平分表

水平分表適合表行數(shù)特別大的表,有的公司要求單表行數(shù)超過 5000 萬就必須進行分表,這個數(shù)字可以作為參考,但并不是絕對標準,關鍵還是要看表的訪問性能。對于一些比較復雜的表,可能超過 1000 萬就要分表了;而對于一些簡單的表,即使存儲數(shù)據(jù)超過 1 億行,也可以不分表。但不管怎樣,當看到表的數(shù)據(jù)量達到干萬級別時,這很可能是架構的性能瓶頸或者隱患。

水平分表相比垂直分表,會引入更多的復雜性,主要表現(xiàn)在下面幾個方面:

  • 路由

水平分表后,某條數(shù)據(jù)具體屬于哪個切分后的子表,需要增加路由算法進行計算,這個算法會引入一定的復雜性。

常見的路由算法有:

高并發(fā),我把握不住啊

范圍路由:選取有序的數(shù)據(jù)列 (例如,整形、時間戳等) 作為路由的條件,不同分段分散到不同的數(shù)據(jù)庫表中。以訂單 Id 為例,路由算法可以按照 1000萬 的范圍大小進行分段。范圍路由設計的復雜點主要體現(xiàn)在分段大小的選取上,分段太小會導致切分后子表數(shù)量過多,增加維護復雜度;分段太大可能會導致單表依然存在性能問題,一般建議分段大小在 100 萬至2000 萬之間,具體需要根據(jù)業(yè)務選取合適的分段大小。

范圍路由的優(yōu)點是可以隨著數(shù)據(jù)的增加平滑地擴充新的表。例如,現(xiàn)在的用戶是 100 萬,如果增加到 1000 萬,只需要增加新的表就可以了,原有的數(shù)據(jù)不需要動。范圍路由的一個比較隱含的缺點是分布不均勻,假如按照 1000 萬來進行分表,有可能某個分段實際存儲的數(shù)據(jù)量只有 1000 條,而另外一個分段實際存儲的數(shù)據(jù)量有 900 萬條。

Hash 路由:選取某個列 (或者某幾個列組合也可以) 的值進行 Hash 運算,然后根據(jù) Hash 結果分散到不同的數(shù)據(jù)庫表中。同樣以訂單 id 為例,假如我們一開始就規(guī)劃了 4個數(shù)據(jù)庫表,路由算法可以簡單地用 id % 4 的值來表示數(shù)據(jù)所屬的數(shù)據(jù)庫表編號,id 為 12的訂單放到編號為 50的子表中,id為 13的訂單放到編號為 61的字表中。

Hash 路由設計的復雜點主要體現(xiàn)在初始表數(shù)量的選取上,表數(shù)量太多維護比較麻煩,表數(shù)量太少又可能導致單表性能存在問題。而用了 Hash 路由后,增加字表數(shù)量是非常麻煩的,所有數(shù)據(jù)都要重分布。

Hash 路由的優(yōu)缺點和范圍路由基本相反,Hash 路由的優(yōu)點是表分布比較均勻,缺點是擴充新的表很麻煩,所有數(shù)據(jù)都要重分布。

配置路由:配置路由就是路由表,用一張獨立的表來記錄路由信息。

同樣以訂單id 為例,我們新增一張 order_router 表,這個表包含 orderjd 和 tablejd 兩列 , 根據(jù) orderjd 就可以查詢對應的 table_id。

配置路由設計簡單,使用起來非常靈活,尤其是在擴充表的時候,只需要遷移指定的數(shù)據(jù),然后修改路由表就可以了。

配置路由的缺點就是必須多查詢一次,會影響整體性能;而且路由表本身如果太大(例如,幾億條數(shù)據(jù)) ,性能同樣可能成為瓶頸,如果我們再次將路由表分庫分表,則又面臨一個死循環(huán)式的路由算法選擇問題。

  • join 操作

水平分表后,數(shù)據(jù)分散在多個表中,如果需要與其他表進行 join 查詢,需要在業(yè)務代碼或者數(shù)據(jù)庫中間件中進行多次 join 查詢,然后將結果合并。

  • count()操作

分表后就沒那么簡單了。常見的處理方式有下面兩種:

count() 相加:具體做法是在業(yè)務代碼或者數(shù)據(jù)庫中間件中對每個表進行 count操作,然后將結果相加。這種方式實現(xiàn)簡單,缺點就是性能比較低。例如,水平分表后切分為 20 張表,則要進行 2 0 次 count()操作,如果串行的話,可能需要幾秒鐘才能得到結果。

記錄數(shù)表:具體做法是新建一張表,假如表名為 '記錄數(shù)表” ,包含 table_name、 row_count兩個字段,每次插入或者刪除子表數(shù)據(jù)成功后,都更新 '記錄數(shù)表“。這種方式獲取表記錄數(shù)的性能要大大優(yōu)于 count()相加的方式,因為只需要一次簡單查詢就可以獲取數(shù)據(jù)。缺點是復雜度增加不少,對子表的操作要同步操作 '記錄數(shù)表' ,如果有一個業(yè)務邏輯遺漏了,數(shù)據(jù)就會不一致;且針對 '記錄數(shù)表' 的操作和針對子表的操作無法放在同一事務中進行處理,異常的情況下會出現(xiàn)操作子表成功了而操作記錄數(shù)表失敗,同樣會導致數(shù)據(jù)不一致。

此外,記錄數(shù)表的方式也增加了數(shù)據(jù)庫的寫壓力,因為每次針對子表的 insert 和 delete 操作都要 update 記錄數(shù)表,所以對于一些不要求記錄數(shù)實時保持精確的業(yè)務,也可以通過后臺定時更新記錄數(shù)表。定時更新實際上就是 'count()相加' 和 '記錄數(shù)表' 的結合,即定時通過count()相加計算表的記錄數(shù),然后更新記錄數(shù)表中的數(shù)據(jù)。

  • order by 操作

水平分表后,數(shù)據(jù)分散到多個子表中,排序操作無法在數(shù)據(jù)庫中完成,只能由業(yè)務代碼或者數(shù)據(jù)庫中間件分別查詢每個子表中的數(shù)據(jù),然后匯總進行排序。

實現(xiàn)方法

和數(shù)據(jù)庫讀寫分離類似,分庫分表具體的實現(xiàn)方式也是 '程序代碼封裝' 和 '中間件封裝' ,但實現(xiàn)會更復雜。讀寫分離實現(xiàn)時只要識別 SQL 操作是讀操作還是寫操作,通過簡單的判斷SELECT、UPDATE、 INSERT、DELETE 幾個關鍵字就可以做到,而分庫分表的實現(xiàn)除了要判斷操作類型外,還要判斷 SQL 中具體需要操作的表、操作函數(shù)(例如 count 函數(shù))、order by、group by 操作等,然后再根據(jù)不同的操作進行不同的處理。例如 order by 操作,需要先從多個庫查詢到各個庫的數(shù)據(jù),然后再重新 order by 才能得到最終的結果。

數(shù)據(jù)異構

完成分庫分表以后,我們看到存在一些問題,除了'程序代碼封裝' 和 '中間件封裝'之外,我們還有一種辦法,就是數(shù)據(jù)異構。數(shù)據(jù)異構就是將數(shù)據(jù)進行異地存儲,比如業(yè)務上將MySQL的數(shù)據(jù),寫一份到Redis中,這就是實現(xiàn)了數(shù)據(jù)在集群中的異地存儲,也就是數(shù)據(jù)異構。

在數(shù)據(jù)量和訪問量雙高時使用數(shù)據(jù)異構是非常有效的,但增加了架構的復雜度。異構時可以通過雙寫、訂閱 MQ 或者 binlog 并解析實現(xiàn)。

  • 雙寫:在寫入數(shù)據(jù)的時候,同時將數(shù)據(jù)寫入MySQL和異構存儲系統(tǒng);
  • MQ:寫入MySQL成功后,發(fā)一個mq消息,緩存讀取mq消息并將消息寫入異構存儲系統(tǒng);
  • binlog:寫入MySQL后,緩存系統(tǒng)x消費binlog,將變動寫入異構存儲系統(tǒng)。

這是一個異構的數(shù)據(jù)架構示意圖:

高并發(fā),我把握不住啊

在圖中用到了ES搜索集群來處理搜索業(yè)務,同樣也可以我們前面提到的跨庫join的問題。

在設計異構的時候,我們可以充分利用一些流行的NoSQL數(shù)據(jù)庫。NoSQL盡管已經(jīng)被證明不能取代關系型數(shù)據(jù)庫,但是在很多場景下是關系型數(shù)據(jù)庫的有力補充。

舉幾個例子,像我們熟悉的Redis這樣的KV存儲,有極高的讀寫性能,在讀寫性能有要求的場景可以使用;

Hbase、Cassandra 這樣的列式存儲數(shù)據(jù)庫。這種數(shù)據(jù)庫的特點是數(shù)據(jù)不像傳統(tǒng)數(shù)據(jù)庫以行為單位來存儲,而是以列來存儲,適用于一些離線數(shù)據(jù)統(tǒng)計的場景;

MongoDB、CouchDB 這樣的文檔型數(shù)據(jù)庫,具備 Schema Free(模式自由)的特點,數(shù)據(jù)表中的字段可以任意擴展,可以用于數(shù)據(jù)字段不固定的場景。

查詢維度異構

比如對于訂單庫,當對其分庫分表后,如果想按照商家維度或者按照用戶維度進行查詢,那么是非常困難的,因此可以通過異構數(shù)據(jù)庫來解決這個問題??梢圆捎孟聢D的架構。

高并發(fā),我把握不住啊

或者采用下圖的ES異構:

高并發(fā),我把握不住啊

異構數(shù)據(jù)主要存儲數(shù)據(jù)之間的關系,然后通過查詢源庫查詢實際數(shù)據(jù)。不過,有時可以通過數(shù)據(jù)冗余存儲來減少源庫查詢量或者提升查詢性能。

聚合據(jù)異構

商品詳情頁中一般包括商品基本信息、商品屬性、商品圖片,在前端展示商品詳情頁時,是按照商品 ID 維度進行查詢,并且需要查詢 3 個甚至更多的庫才能查到所有展示數(shù)據(jù)。此時,如果其中一個庫不穩(wěn)定,就會導致商品詳情頁出現(xiàn)問題,因此,我們把數(shù)據(jù)聚合后異構存儲到 KV 存儲集群(如存儲 JSON ), 這樣只需要一次查詢就能得到所有的展示數(shù)據(jù)。這種方式也需要系統(tǒng)有了一定的數(shù)據(jù)量和訪問量時再考慮。

高并發(fā),我把握不住啊

高并發(fā)架構要點

通過前面的內容,已經(jīng)差不多了解高并發(fā)的架構是一個什么樣,接下來做一些總結和補充。

高性能要點

高并發(fā),我把握不住啊

高可用要點

高并發(fā),我把握不住啊

除了從技術的角度來考慮,保證高可用同樣需要良好的組織制度,來保證服務出現(xiàn)問題的快速恢復。

高擴展要點

1、合理的分層架構:比如上面談到的互聯(lián)網(wǎng)最常見的分層架構,另外還能進一步按照數(shù)據(jù)訪問層、業(yè)務邏輯層對微服務做更細粒度的分層(但是需要評估性能,會存在網(wǎng)絡多一跳的情況)。

2、存儲層的拆分:按照業(yè)務維度做垂直拆分、按照數(shù)據(jù)特征維度進一步做水平拆分(分庫分表)。

3、業(yè)務層的拆分:最常見的是按照業(yè)務維度拆(比如電商場景的商品服務、訂單服務等),也可以按照核心請求和非核心請求拆分,還可以按照請求源拆(比如To C和To B,APP和H5 )。

原文鏈接:
https://www.cnblogs.com/three-fighter/p/14757813.html#!comments

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多