<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          高并發(fā),你把握不住??!

          共 28862字,需瀏覽 58分鐘

           ·

          2021-06-12 00:54


          本文總共2萬多字,建議先收藏,然后慢慢閱讀。

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

          高并發(fā)課程廣告

          我承認(rèn)我有賭的成分,點(diǎn)進(jìn)去一看,果然是廣告。說真的,內(nèi)容看起來還是很有吸引力的,但是貧窮阻止了我消費(fèi)的沖動(dòng)。

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

          什么是高并發(fā)

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

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

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

          img

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

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

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

          高并發(fā)目標(biāo)

          宏觀目標(biāo)

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

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

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

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

          三高

          這3個(gè)目標(biāo)是需要通盤考慮的,因?yàn)樗鼈兓ハ嚓P(guān)聯(lián)、甚至也會(huì)相互影響。

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

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

          具體目標(biāo)

          性能指標(biāo)

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

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

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

          并發(fā)數(shù) = QPS?平均響應(yīng)時(shí)間
          1. 響應(yīng)時(shí)間:從請求發(fā)出到收到響應(yīng)花費(fèi)的時(shí)間,例如一個(gè)系統(tǒng)處理一個(gè)HTTP請求需要100ms,這個(gè)100ms就是系統(tǒng)的響應(yīng)時(shí)間。

          2. 平均響應(yīng)時(shí)間:最常用,但是缺陷很明顯,對于慢請求不敏感。比如 1 萬次請求,其中 9900 次是 1ms,100 次是 100ms,則平均響應(yīng)時(shí)間為 1.99ms,雖然平均耗時(shí)僅增加了 0.99ms,但是 1%請求的響應(yīng)時(shí)間已經(jīng)增加了 100 倍。

          3. TP90、TP99 等分位值:將響應(yīng)時(shí)間按照從小到大排序,TP90 表示排在第 90 分位的響應(yīng)時(shí)間, 分位值越大,對慢請求越敏感。

          img
          1. RPS(吞吐量):單位時(shí)間內(nèi)處理的請求量,通常由QPS和并發(fā)數(shù)決定。通常,設(shè)定性能目標(biāo)時(shí)會(huì)兼顧吞吐量和響應(yīng)時(shí)間,比如這樣表述:在每秒 1 萬次請求下,AVG 控制在 50ms 以下,TP99 控制在 100ms 以下。對于高并發(fā)系統(tǒng),AVG 和 TP 分位值必須同時(shí)要考慮。

            另外,從用戶體驗(yàn)角度來看,200 毫秒被認(rèn)為是第一個(gè)分界點(diǎn),用戶感覺不到延遲,1 秒是第二個(gè)分界點(diǎn),用戶能感受到延遲,但是可以接受。因此,對于一個(gè)健康的高并發(fā)系統(tǒng),TP99 應(yīng)該控制在 200 毫秒以內(nèi),TP999 或者 TP9999 應(yīng)該控制在 1 秒以內(nèi)。

          2. PV:綜合瀏覽量,即頁面瀏覽量或者點(diǎn)擊量,一個(gè)訪客在24小時(shí)內(nèi)訪問的頁面數(shù)量。

          3. UV:獨(dú)立訪客 ,即一定時(shí)間范圍內(nèi)相同訪客多次訪問網(wǎng)站,只計(jì)算為一個(gè)獨(dú)立的訪客。

          4. 帶寬:計(jì)算帶寬大小需要關(guān)注兩個(gè)指標(biāo),峰值流量和頁面的平均大小。

            日網(wǎng)站帶寬可以使用下面的公式來粗略計(jì)算:

            日網(wǎng)站帶寬=pv/統(tǒng)計(jì)時(shí)間(換算到秒)*平均頁面大?。▎挝籯B)*8

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

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

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

          可用性指標(biāo)

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

          可用性指標(biāo)

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

          可擴(kuò)展性指標(biāo)

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

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

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

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

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

          高并發(fā)架構(gòu)演進(jìn)

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

          以下是來自《淘寶技術(shù)這十年》描述的淘寶2003—2012年的架構(gòu)演進(jìn)。

          個(gè)人網(wǎng)站

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

          ——買一個(gè)。

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

          初代架構(gòu)

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

          初代淘寶網(wǎng)站

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

          Oracle/支付寶/旺旺

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

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

          引入Oracle之后的淘寶架構(gòu)

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

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

          Java 時(shí)代 1.0

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

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

          當(dāng)時(shí),由于struts1.x存在很多問題,所以淘寶自研了一套MVC框架。Sun當(dāng)時(shí)在推EJB,所以這套架構(gòu)里也引入了EJB。

          Java 時(shí)代 1.0

          Java 時(shí)代 2.0

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

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

          Java時(shí)代2.0

          Java時(shí)代3.0

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

          Java時(shí)代3.0

          分布式時(shí)代1.0

          到了2008年的時(shí)候,淘寶的業(yè)務(wù)進(jìn)一步發(fā)展。

          ?

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

          ?

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

          面向服務(wù)架構(gòu)

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

          轉(zhuǎn)眼理2012又快過了十年,這十年,阿里巴巴逐漸進(jìn)入極盛時(shí)代,技術(shù)上也是風(fēng)起云涌,才人輩出。粒度更細(xì)的微服務(wù)、隔離差距的容器化技術(shù)、快速伸縮的云平臺(tái)技術(shù)……  如果《淘寶技術(shù)這十年》的作者能再寫一個(gè)十年,一定也是非常精彩。

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

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

          容器化時(shí)代

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

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

          容器化時(shí)代

          云平臺(tái)時(shí)代

          在服務(wù)化的時(shí)候,淘寶已經(jīng)演進(jìn)到了云平臺(tái)架構(gòu)。

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

          clipboard.png

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

          高并發(fā)架構(gòu)實(shí)現(xiàn)

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

          • 「縱向擴(kuò)展」

            1、提升單機(jī)的硬件性能:通過增加內(nèi)存、 CPU核數(shù)、存儲(chǔ)容量、或者將磁盤 升級成SSD等堆硬件的方式來提升

            2、提升單機(jī)的軟件性能:使用緩存減少IO次數(shù),使用并發(fā)或者異步的方式增加吞吐量。

          • 「橫向擴(kuò)展」:單機(jī)性能總會(huì)存在極限,所以最終還需要引入橫向擴(kuò)展,通過集群部署以進(jìn)一步提高并發(fā)處理能力。

            1、做好分層架構(gòu):這是橫向擴(kuò)展的前提,因?yàn)楦卟l(fā)系統(tǒng)往往業(yè)務(wù)復(fù)雜,通過分層處理可以簡化復(fù)雜問題,更容易做到橫向擴(kuò)展。

            2、各層進(jìn)行水平擴(kuò)展:無狀態(tài)水平擴(kuò)容,有狀態(tài)做分片路由。業(yè)務(wù)集群通常能設(shè)計(jì)成無狀態(tài)的,而數(shù)據(jù)庫和緩存往往是有狀態(tài)的,因此需要設(shè)計(jì)分區(qū)鍵做好存儲(chǔ)分片,當(dāng)然也可以通過主從同步、讀寫分離的方案提升讀性能。

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

          我要打十個(gè)

          我們看一下一個(gè)大概的支持三高的典型架構(gòu):

          高并發(fā)典型架構(gòu)

          接下來,我們從上往下,看一下,各層的一些關(guān)鍵技術(shù)。

          網(wǎng)絡(luò)層

          多機(jī)器

          「堆機(jī)器不是萬能的,不堆機(jī)器是萬萬不能的?!?/strong>

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

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

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

          • 通過QPS和PV計(jì)算部署服務(wù)器的臺(tái)數(shù)

          單臺(tái)服務(wù)器每天PV計(jì)算:

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

          服務(wù)器計(jì)算:

          服務(wù)器數(shù)量 =   ceil( 每天總PV / 單臺(tái)服務(wù)器每天總PV )
          • 峰值QPS和機(jī)器計(jì)算公式

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

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

          機(jī)器:峰值時(shí)間每秒QPS / 單臺(tái)機(jī)器的QPS   = 需要的機(jī)器。

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

          DNS

          DNS簡單示意圖

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

          一般不會(huì)使用DNS來做機(jī)器級別的負(fù)載均衡,因?yàn)樵觳黄?,IP資源實(shí)在太寶貴了,例如百度搜索可能需要數(shù)萬臺(tái)機(jī)器,不可能給每個(gè)機(jī)器都配置公網(wǎng)IP。一般只會(huì)有有限的公網(wǎng)IP的節(jié)點(diǎn),然后再在這些節(jié)點(diǎn)上做機(jī)器級別的負(fù)載均衡,這樣各個(gè)機(jī)房的機(jī)器只需要配置局域網(wǎng)IP就行了。

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

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

          • DNS 緩存的時(shí)間比較長,即使將某臺(tái)業(yè)務(wù)機(jī)器從 DNS 服務(wù)器上刪除,由于緩存的原因,還是有很多用戶會(huì)繼續(xù)訪問已經(jīng)被刪除的機(jī)器。

          • DNS 不夠靈活。DNS 不能感知后端服務(wù)器的狀態(tài),只能根據(jù)配置策略進(jìn)行負(fù)載均衡,無法做到更加靈活的負(fù)載均衡策略。比如說某臺(tái)機(jī)器的配置比其他機(jī)器要好很多,理論上來說應(yīng)該多分配一些請求給它,但 DNS 無法做到這一點(diǎn)。

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

          CDN

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

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

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

          CDN請求流程圖

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

          反向代理層

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

          還是那個(gè)比喻,還是你打十個(gè)大漢,這次你叫了十九個(gè)兄弟,理想的情況是你們兩個(gè)打?qū)γ嬉粋€(gè),但是你由于太激動(dòng),沖在了最前面,結(jié)果瞬間被十個(gè)大漢暴打……

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

          Nginx、LVS、F5

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

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

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

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

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

          Nginx負(fù)載均衡架構(gòu)示意圖

          負(fù)載均衡典型架構(gòu)

          像上面提到的負(fù)載均衡機(jī)制,在使用中,可以組合使用。

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

          三層負(fù)載均衡

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

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

          Nginx負(fù)載均衡

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

          Nginx負(fù)載均衡/反向代理示意圖

          對于負(fù)載均衡我們主要關(guān)心的幾個(gè)方面如下:

          • 上游服務(wù)器配置:使用 upstream server配置上游服務(wù)器

          • 負(fù)載均衡算法:配置多個(gè)上游服務(wù)器時(shí)的負(fù)載均衡機(jī)制。

          • 失敗重試機(jī)制:配置當(dāng)超時(shí)或上游服務(wù)器不存活時(shí),是否需要重試其他上游服務(wù)器。

          • 服務(wù)器心跳檢查:上游服務(wù)器的健康檢查/心跳檢查。

          ?

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

          ?

          負(fù)載均衡算法

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

          1、輪詢(默認(rèn))

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

          2、weight(輪詢權(quán)值)

          weight的值越大分配到的訪問概率越高,主要用于后端每臺(tái)服務(wù)器性能不均衡的情況下。或者僅僅為在主從的情況下設(shè)置不同的權(quán)值,達(dá)到合理有效的地利用主機(jī)資源。

          3、ip_hash

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

          4、fair

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

          5、url_hash

          按訪問的URL的哈希結(jié)果來分配請求,使每個(gè)URL定向到一臺(tái)后端服務(wù)器,可以進(jìn)一步提高后端緩存服務(wù)器的效率。Nginx本身不支持url_hash,如果需要這種調(diào)度算法,則必須安裝Nginx的hash軟件包。

          失敗重試

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

          通過配置上游服務(wù)器的 max_fails和 fail_timeout,來指定每個(gè)上游服務(wù)器,當(dāng)fail_timeout時(shí)間內(nèi)失敗了max_fail次請求,則認(rèn)為該上游服務(wù)器不可用/不存活,然后將會(huì)摘掉該上游服務(wù)器,fail_timeout時(shí)間后會(huì)再次將該服務(wù)器加入到存活上游服務(wù)器列表進(jìn)行重試。

          健康檢查

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

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

          流量控制

          流量分發(fā)

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

          流量切換

          我聽朋友說過一個(gè)有意思的事情,他們公司將流量從一個(gè)機(jī)房切到另一個(gè)機(jī)房,結(jié)果翻車,所有工程師運(yùn)維平臺(tái)一片飄紅,全公司集體圍觀,運(yùn)維團(tuán)隊(duì)就很丟面子。

          幸災(zāi)樂禍

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

          就像我們上面提到的負(fù)載均衡典型架構(gòu),不同層級的負(fù)載負(fù)責(zé)切換不同層級的流量。

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

          另外,有些應(yīng)用為了更方便切換,還可以在 「Nginx」 接入層做切換,通過 「Nginx」 進(jìn)行一些流量切換,而沒有通過如 「LVS」/「HaProxy」 做切換。

          限流

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

          漏桶和令牌桶

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

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

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

          還可以使用 「OpenResty」提供的 「Lua」限流模塊 ua-resty**-**limit-traffic應(yīng)對更復(fù)雜的限流場景。

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

          流量過濾

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

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

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

          流量過濾

          降級

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

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

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

          Web層

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

          集群

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

          集群

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

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

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

          Web服務(wù)器

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

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

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

          容器

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

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

          容器化技術(shù)也天生適合當(dāng)前流行的微服務(wù),容器將微服務(wù)進(jìn)程和應(yīng)用程序隔離到更小的實(shí)例里,使用更少的資源,更快捷地部署。結(jié)合容器編排技術(shù),可以更方便快速地搭建服務(wù)高可用集群。

          服務(wù)層

          開發(fā)框架

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

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

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

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

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

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

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

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

          服務(wù)中心

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

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

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

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

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

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

          image-20210501154424095

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

          配置中心

          配置中心就是集中管理各個(gè)服務(wù)的配置。

          在服務(wù)不多的時(shí)候,各個(gè)服務(wù)各自管理自己的配置,沒有問題,但是當(dāng)服務(wù)成百上千,再各行其政,就是一個(gè)比較頭疼的事。

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

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

          服務(wù)框架

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

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

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

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

          以SpringCloud為例:

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

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

          Dubbo主要提供了最基礎(chǔ)的RPC功能。

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

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

          消息隊(duì)列

          消息隊(duì)列在高性能、高擴(kuò)展、高可用的架構(gòu)中扮演著很重要的角色。

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

          服務(wù)解耦

          「服務(wù)解耦可以降低服務(wù)間耦合,提高系統(tǒng)系統(tǒng)的擴(kuò)展性?!?/strong>

          例如一個(gè)訂單服務(wù),有多個(gè)下游,如果不用消息隊(duì)列,那么訂單服務(wù)就要調(diào)用多個(gè)下游。如果需求要再加下游,那么訂單服務(wù)就得添加調(diào)用新下流的功能,這就比較煩。

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

          服務(wù)結(jié)構(gòu)

          異步處理

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

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

          圖片

          流量削峰/緩沖

          「流量削峰/緩沖可以提高系統(tǒng)的可用性?!?/strong>

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

          流量削峰/緩沖

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

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

          平臺(tái)層

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

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

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

          運(yùn)維平臺(tái)

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

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

          運(yùn)維平臺(tái)的核心設(shè)計(jì)要素是“四化"——標(biāo)準(zhǔn)化、平臺(tái)化、自動(dòng)化、可視化。

          • 標(biāo)準(zhǔn)化:要制定運(yùn)維標(biāo)準(zhǔn),規(guī)范配置管理、部署流程、監(jiān)控指標(biāo)、應(yīng)急能力等,各系統(tǒng)按照運(yùn)維標(biāo)準(zhǔn)來

            實(shí)現(xiàn),避免不同的系統(tǒng)不同的處理方式。

          • 平臺(tái)化:傳統(tǒng)的手工運(yùn)維方式需要投入大量人力,效率低,容易出錯(cuò),因此需要在運(yùn)維標(biāo)準(zhǔn)化的基礎(chǔ)上,

            將運(yùn)維的相關(guān)操作都集成到運(yùn)維平臺(tái)中,通過運(yùn)維平臺(tái)來完成運(yùn)維工作。

          • 自動(dòng)化:傳統(tǒng)手工運(yùn)維方式效率低下的一個(gè)主要原因就是要執(zhí)行大量重復(fù)的操作,運(yùn)維平臺(tái)可以將這些重

            復(fù)操作固化下來,由系統(tǒng)自動(dòng)完成。

          • 可視化:運(yùn)維平臺(tái)有非常多的數(shù)據(jù),如果全部通過人工去查詢數(shù)據(jù)再來判斷,則效率很低,可視化的主要目的就是為了提升數(shù)據(jù)查看效率。

          測試平臺(tái)

          測試平臺(tái)核心的職責(zé)當(dāng)然就是測試了,包括單元測試、集成測試、接口測試、性能測試等,都可以在測試平臺(tái)來完成。

          測試平臺(tái)的核心目的是提升測試效率,從而提升產(chǎn)品質(zhì)量,其設(shè)計(jì)關(guān)鍵就是自動(dòng)化。

          測試平臺(tái)架構(gòu)

          數(shù)據(jù)平臺(tái)

          數(shù)據(jù)平臺(tái)的核心職責(zé)主要包括三部分:數(shù)據(jù)管理、數(shù)據(jù)分析和數(shù)據(jù)應(yīng)用。每一部分又包含更多的細(xì)分領(lǐng)域,詳細(xì)的數(shù)據(jù)平臺(tái)架構(gòu)如下圖所示:

          數(shù)據(jù)平臺(tái)
          1. 數(shù)據(jù)管理

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

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

          數(shù)據(jù)分析包括數(shù)據(jù)統(tǒng)計(jì)、數(shù)據(jù)挖掘、機(jī)器學(xué)習(xí)、深度學(xué)習(xí)等幾個(gè)細(xì)分領(lǐng)域。

          • 數(shù)據(jù)挖掘:數(shù)據(jù)挖掘這個(gè)概念本身含義可以很廣,為了與機(jī)器學(xué)習(xí)和深度學(xué)習(xí)區(qū)分開,這里的數(shù)據(jù)挖掘主要是指傳統(tǒng)的數(shù)據(jù)挖掘方式。例如,有經(jīng)驗(yàn)的數(shù)據(jù)分析人員基于數(shù)據(jù)倉庫構(gòu)建一系列規(guī)則來對數(shù)據(jù)進(jìn)行分析從而發(fā)現(xiàn)一些隱含的規(guī)律、現(xiàn)象、問題等,經(jīng)典的數(shù)據(jù)挖掘案例就是沃爾瑪?shù)钠【婆c尿布的關(guān)聯(lián)關(guān)系的發(fā)現(xiàn)。

          • 機(jī)器學(xué)習(xí)、深度學(xué)習(xí):機(jī)器學(xué)習(xí)和深度學(xué)習(xí)屬于數(shù)據(jù)挖掘的一種具體實(shí)現(xiàn)方式,由于其實(shí)現(xiàn)方式與傳統(tǒng)的數(shù)據(jù)挖掘方式差異較大,因此數(shù)據(jù)平臺(tái)在實(shí)現(xiàn)機(jī)器學(xué)習(xí)和深度學(xué)習(xí)時(shí),需要針對機(jī)器學(xué)習(xí)和深度學(xué)習(xí)獨(dú)立進(jìn)行設(shè)計(jì)。

          1. 數(shù)據(jù)應(yīng)用

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

          管理平臺(tái)

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

          ?

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

          ?

          緩存層

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

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

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

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

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

          多級緩存

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

          分布式緩存

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

          要采用不過期緩存機(jī)制,可以考慮取模機(jī)制,擴(kuò)容時(shí)一般是新建一個(gè)集群。

          取模算法

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

          一致性hash算法

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

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

          Redis Cluster

          熱點(diǎn)本地緩存

          對于那些訪問非常頻繁的熱點(diǎn)緩存,如果每次都去遠(yuǎn)程緩存系統(tǒng)中獲取,可能會(huì)因?yàn)樵L問量太大導(dǎo)致遠(yuǎn)程緩存系統(tǒng)請求過多、負(fù)載過高或者帶寬過高等問題,最終可能導(dǎo)致緩存響應(yīng)慢,使客戶端請求超時(shí)。

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

          ?

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

          ?

          緩存穿透

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

          緩存穿透的示意圖:

          緩存穿透

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

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

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

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

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

          緩存擊穿

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

          緩存擊穿示意圖:

          緩存擊穿

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

          主要有兩個(gè)解決辦法:

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

          • 熱點(diǎn)數(shù)據(jù)緩存永遠(yuǎn)不過期。

          永不過期有兩種方式:

          • 物理不過期,針對熱點(diǎn)key不設(shè)置過期時(shí)間
          • 邏輯過期,把過期時(shí)間存在key對應(yīng)的value里,如果發(fā)現(xiàn)要過期了,通過一個(gè)后臺(tái)的異步線程進(jìn)行緩存的構(gòu)建

          緩存雪崩

          緩存雪崩,指的是是緩存不可用,或者同一時(shí)刻是大量熱點(diǎn)key失效。

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

          緩存雪崩

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

          • 事前:

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

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

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

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

          • 事中:

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

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

          • 事后:

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

          存儲(chǔ)層

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

          讀寫分離

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

          讀寫分離

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

          • 數(shù)據(jù)庫服務(wù)器搭建主從集群,一主一從、一主多從都可以。

          • 數(shù)據(jù)庫主機(jī)負(fù)責(zé)讀寫操作,從機(jī)只負(fù)責(zé)讀操作。

          • 數(shù)據(jù)庫主機(jī)通過復(fù)制將數(shù)據(jù)同步到從機(jī),每臺(tái)數(shù)據(jù)庫服務(wù)器都存儲(chǔ)了所有的業(yè)務(wù)數(shù)據(jù)。

          • 業(yè)務(wù)服務(wù)器將寫操作發(fā)給數(shù)據(jù)庫主機(jī),將讀操作發(fā)給數(shù)據(jù)庫從機(jī)。

          主從異步復(fù)制原理

          讀寫分離的實(shí)現(xiàn)邏輯并不復(fù)雜,但有兩個(gè)細(xì)節(jié)點(diǎn)將引入設(shè)計(jì)復(fù)雜度:主從復(fù)制延遲和分配機(jī)制。

          復(fù)制延遲

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

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

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

          復(fù)制延遲

          解決主從復(fù)制延遲的常見方法:

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

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

          1. 使用緩存

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

          1. 二次讀取

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

          1. 查詢主庫

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

          分配機(jī)制

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

          1. 程序代碼封裝

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

          代碼封裝讀寫分離分配

          程序代碼封裝的方式具備幾個(gè)特點(diǎn):

          • 實(shí)現(xiàn)簡單,而且可以根據(jù)業(yè)務(wù)做較多定制化的功能。

          • 每個(gè)編程語言都需要自己實(shí)現(xiàn)一次,無法通用,如果一個(gè)業(yè)務(wù)包含多個(gè)編程語言寫的多個(gè)子系統(tǒng),則重復(fù)開發(fā)的工作量比較大。

          • 故障情況下,如果主從發(fā)生切換,則可能需要所有系統(tǒng)都修改配置并重啟。

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

          TDDL
          1. 中間件封裝

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

          其基本架構(gòu)是:

          數(shù)據(jù)庫中間件

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

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

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

          分庫分表

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

          • 數(shù)據(jù)量太大,讀寫的性能會(huì)下降,即使有索引,索引也會(huì)變得很大,性能同樣會(huì)下降。

          • 數(shù)據(jù)文件會(huì)變得很大,數(shù)據(jù)庫備份和恢復(fù)需要耗費(fèi)很長時(shí)間。

          • 數(shù)據(jù)文件越大,極端情況下丟失數(shù)據(jù)的風(fēng)險(xiǎn)越高(例如,機(jī)房火災(zāi)導(dǎo)致數(shù)據(jù)庫主備機(jī)都發(fā)生故障)。

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

          業(yè)務(wù)分庫

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

          業(yè)務(wù)分庫

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

          1. join 操作問題

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

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

          1. 事務(wù)問題

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

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

          1. 成本問題

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

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

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

          單表拆分

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

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

          image-20210509213156925

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

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

          蘋果切分
          1. 垂直分表

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

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

          1. 水平分表

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

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

          • 路由

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

          常見的路由算法有:

          范圍路由Hash路由

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

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

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

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

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

          「配置路由」:配置路由就是路由表,用一張獨(dú)立的表來記錄路由信息。

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

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

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

          • join 操作

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

          • count()操作

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

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

          記錄數(shù)表:具體做法是新建一張表,假如表名為 "記錄數(shù)表” ,包含 table_name、 row_count兩個(gè)字段,每次插入或者刪除子表數(shù)據(jù)成功后,都更新 "記錄數(shù)表“。這種方式獲取表記錄數(shù)的性能要大大優(yōu)于 count()相加的方式,因?yàn)橹恍枰淮魏唵尾樵兙涂梢垣@取數(shù)據(jù)。缺點(diǎn)是復(fù)雜度增加不少,對子表的操作要同步操作 "記錄數(shù)表" ,如果有一個(gè)業(yè)務(wù)邏輯遺漏了,數(shù)據(jù)就會(huì)不一致;且針對 "記錄數(shù)表" 的操作和針對子表的操作無法放在同一事務(wù)中進(jìn)行處理,異常的情況下會(huì)出現(xiàn)操作子表成功了而操作記錄數(shù)表失敗,同樣會(huì)導(dǎo)致數(shù)據(jù)不一致。

          此外,記錄數(shù)表的方式也增加了數(shù)據(jù)庫的寫壓力,因?yàn)槊看吾槍ψ颖淼?insert 和 delete 操作都要 update 記錄數(shù)表,所以對于一些不要求記錄數(shù)實(shí)時(shí)保持精確的業(yè)務(wù),也可以通過后臺(tái)定時(shí)更新記錄數(shù)表。定時(shí)更新實(shí)際上就是 "count()相加" 和 "記錄數(shù)表" 的結(jié)合,即定時(shí)通過count()相加計(jì)算表的記錄數(shù),然后更新記錄數(shù)表中的數(shù)據(jù)。

          • order by 操作

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

          「實(shí)現(xiàn)方法」

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

          數(shù)據(jù)異構(gòu)

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

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

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

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

          異構(gòu)數(shù)據(jù)架構(gòu)

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

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

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

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

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

          查詢維度異構(gòu)

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

          異構(gòu)1

          或者采用下圖的ES異構(gòu):

          異構(gòu)2

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

          聚合據(jù)異構(gòu)

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

          聚合據(jù)異構(gòu)

          高并發(fā)架構(gòu)要點(diǎn)

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

          高性能要點(diǎn)

          高性能要點(diǎn)

          高可用要點(diǎn)

          高可用要點(diǎn)

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

          高擴(kuò)展要點(diǎn)

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

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

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


          ?

          好了,攢的這一篇終于完事了,更深入學(xué)習(xí)建議閱讀書籍參考【11】。祝各位架構(gòu)師能真的如江似海,把握高并發(fā),多掙達(dá)不溜。

          ?





          由于作者對于高并發(fā)的認(rèn)識(shí)是從零開始,所以參考了很多經(jīng)典資料!

          參考與感謝:

          【1】:極客時(shí)間 [《從零開始學(xué)架構(gòu)》](https://time.geekbang.org/column/article/6605)
          【2】:[知乎問答:我沒有高并發(fā)項(xiàng)目經(jīng)驗(yàn),但是面試的時(shí)候經(jīng)常被問到高并發(fā)、性能調(diào)優(yōu)方面的問題,有什么辦法可以解決嗎?](https://www.zhihu.com/question/421237964)
          【3】:[什么是高并發(fā) ,詳細(xì)講解](https://www.huaweicloud.com/articles/c3c849bea765da72934c2b09f6805895.html)
          【4】:[【高并發(fā)】如何設(shè)計(jì)一個(gè)支撐高并發(fā)大流量的系統(tǒng)?這次我將設(shè)計(jì)思路分享給大家!](https://www.cnblogs.com/binghe001/p/13381993.html)
          【5】:《淘寶技術(shù)這十年》
          【6】:[知乎問答:如何獲得高并發(fā)的經(jīng)驗(yàn)?](https://www.zhihu.com/question/40609661)
          【7】:[服務(wù)端高并發(fā)分布式架構(gòu)演進(jìn)之路](https://segmentfault.com/a/1190000018626163)
          【8】:[七年磨一劍,獨(dú)家揭秘淘寶技術(shù)發(fā)展歷程和架構(gòu)經(jīng)驗(yàn) ](https://mp.weixin.qq.com/s?__biz=MzA4MjA0MTc4NQ==&mid=2651573322&idx=1&sn=2092e183f829ff2129e36b1b5487797b&scene=21#wechat_redirect)
          【9】:《大型網(wǎng)站技術(shù)架構(gòu)核心原理與案例分析》
          【10】:[阿里技術(shù)專家:日活5億的淘寶技術(shù)發(fā)展歷程和架構(gòu)經(jīng)驗(yàn)分享!18頁ppt詳解](https://www.it610.com/article/1290457868750888960.htm)
          【11】:《億級流量網(wǎng)站架構(gòu)技術(shù)》
          【12】:極客時(shí)間《從零開始學(xué)微服務(wù)》
          【13】:[面試題:如何保證消息不丟失?處理重復(fù)消息?消息有序性?消息堆積處理?](https://mp.weixin.qq.com/s/1r1x-Irbatvzdc90haaecA)
          【14】:[極客時(shí)間 高并發(fā)系統(tǒng)設(shè)計(jì)40問](https://time.geekbang.org/column/intro/230)
          【15】:《Redis深度歷險(xiǎn):核心原理和應(yīng)用實(shí)踐》
          【16】:[Redis的緩存雪崩、緩存擊穿、緩存穿透與緩存預(yù)熱、緩存降級](https://blog.csdn.net/a745233700/article/details/88088669)
          【17】:[如何優(yōu)雅的設(shè)計(jì)和使用緩存?](https://juejin.cn/post/6844903665845665805?spm=a2c6h.12873639.0.0.3bdf415cQIkL6j%3Fspm%3Da2c6h.12873639.0.0.3bdf415cQIkL6j)
          【18】:[緩存穿透、緩存擊穿、緩存雪崩,看這篇就夠了](https://xie.infoq.cn/article/a035f12e5590385ac578778b0)
          【19】:[數(shù)據(jù)異構(gòu)](https://jun-wang.gitbook.io/learnjava/ji-shu-xue-xi/jia-gou-xue-xi/shu-ju-yi-gou)
          【20】:[分庫分表?如何做到永不遷移數(shù)據(jù)和避免熱點(diǎn)?](https://cloud.tencent.com/developer/article/1441250)



          你好,我是四猿外。

          一家上市公司的技術(shù)總監(jiān),管理的技術(shù)團(tuán)隊(duì)一百余人。

          我從一名非計(jì)算機(jī)專業(yè)的畢業(yè)生,轉(zhuǎn)行到程序員,一路打拼,一路成長。

          我會(huì)通過公眾號(hào),
          把自己的成長故事寫成文章,
          把枯燥的技術(shù)文章寫成故事。

          我建了一個(gè)讀者交流群,里面大部分是程序員,一起聊技術(shù)、工作、八卦。歡迎加我微信,拉你入群。

          瀏覽 58
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    亚洲逼视频 | 黄片网站在线播放 | 西欧超碰在线 | 先锋影音亚洲无码av | 在线免费观看黄色小视频 |