「万字干货」高并发系统分析与大型互联网架构介绍
(篇幅較長,建議大家先收藏再看哦~)
在初步地學(xué)習(xí)并掌握了基礎(chǔ)的編程之后,如何提高編程能力是每個開發(fā)者關(guān)心的問題。對于 Java 的學(xué)習(xí)者來說,高并發(fā)是每個開發(fā)者技術(shù)進(jìn)階的必經(jīng)之路。但是高并發(fā)的技術(shù)要求和業(yè)務(wù)場景本身就是比較復(fù)雜的,這就會給大家的學(xué)習(xí)之路帶來一定的難度。
下面我們帶大家了解高并發(fā)系統(tǒng)的應(yīng)用場景、市場需求以及目前市面上比較成熟的大型互聯(lián)網(wǎng)的系統(tǒng)架構(gòu)基線是怎樣的,讓大家對于高并發(fā)有一個初步地認(rèn)識,了解一些復(fù)雜業(yè)務(wù)場景需求下的高并發(fā)定制化應(yīng)對策略,以及一些常見的高并發(fā)環(huán)境下的解決方案。
要點
-
大型系統(tǒng)的技術(shù)基石-高并發(fā)
-
系統(tǒng)分析原則
-
系統(tǒng)設(shè)計要點
-
大型系統(tǒng)架構(gòu)設(shè)計
大型系統(tǒng)的技術(shù)基石-高并發(fā)
目前互聯(lián)網(wǎng)分布式系統(tǒng)架構(gòu)設(shè)計中必須得考慮高并發(fā)因素的影響,我們給它一個定義的話它通常是指通過設(shè)計來保證系統(tǒng)能夠同時并行處理很多的請求。小到門戶網(wǎng)站的并發(fā)閱讀量、在線聊天功能,大到春運(yùn)期間 12306 官網(wǎng)的并發(fā)購票業(yè)務(wù)、每年的雙十一電商大促時的并發(fā)交易量、電商秒殺、除夕夜微信紅包的并發(fā)等,這些都體現(xiàn)了現(xiàn)實業(yè)務(wù)開發(fā)的高并發(fā)技術(shù)的剛性需求。
在雙十一等電商大促活動之后,除了屢創(chuàng)新高的交易額外,還有一個看點就是各大電商平臺整點交易的并發(fā)量。很顯然,對于高并發(fā)的掌握能力在一定程度上反映了一個電商平臺的技術(shù)水平,往往我們都對于一個能抗得住高并發(fā)的系統(tǒng)格外的青睞。一些國內(nèi)外的互聯(lián)網(wǎng)企業(yè)都在頻繁地推出各種高并發(fā)相關(guān)的新技術(shù)框架,由此可見對于高并發(fā)的極致追求一直是各大互聯(lián)網(wǎng)企業(yè)不斷挑戰(zhàn)、樂此不疲的研究方向。
“系統(tǒng)繁忙,請稍后再試”等異常反饋相信大家都在生活中碰到過,高并發(fā)技術(shù)是很重要的,但是也存在著巨大的技術(shù)挑戰(zhàn)和提升的價值空間。它是一個廣義的概念可以包含架構(gòu)設(shè)計、SOA 面向服務(wù)的架構(gòu)、分布式、微服務(wù)、數(shù)據(jù)處理、多線程等很多細(xì)分出來的知識。
現(xiàn)在給大家從技術(shù)的角度簡要地介紹一下如何處理高并發(fā)的請求。
例如,電商的秒殺活動會帶來非常大的高并發(fā)請求,為了避免超額的高并發(fā)請求沖垮電商的服務(wù)器,就需要對所有的并發(fā)請求進(jìn)行處理。一般而言,可以先通過驗證碼和 IP 限制等手段攔截非法的用戶請求,然后搭建服務(wù)集群,將合法的并發(fā)請求進(jìn)行分流。之后還可以在服務(wù)器內(nèi)部設(shè)置最大的連接數(shù)、最大并發(fā)數(shù)等服務(wù)參數(shù),并通過消息隊列對海量的并發(fā)請求進(jìn)行削峰填谷處理。此外,為了讓數(shù)據(jù)庫穩(wěn)定地處理高并發(fā)請求,還需要通過緩存中間件減少用戶請求數(shù)據(jù)庫的次數(shù),并通過服務(wù)降級等策略減輕高并發(fā)峰值期間對系統(tǒng)的訪問壓力。最后,為了在極端情況下仍然能夠保障數(shù)據(jù)的安全性能,還需要搭建數(shù)據(jù)庫集群并設(shè)置合理的隔離機(jī)制。由此可見,高并發(fā)貫穿在項目設(shè)計的方方面面,從網(wǎng)關(guān)到服務(wù)器開發(fā)再到數(shù)據(jù)設(shè)計等環(huán)節(jié)都需要考慮高并發(fā)情況下的應(yīng)對策略。
大家在學(xué)習(xí)過程中一定要在看懂文案、讀懂代碼的基礎(chǔ)上多實踐,多輸出,這樣子才可以后期自己實現(xiàn)“寫出來”。不要只是動眼、動耳但是不動手。希望大家參與到實踐中來,多練習(xí),多總結(jié)。
系統(tǒng)分析原則
在市面上存在各種優(yōu)秀的軟件系統(tǒng),他們都遵循著相同的設(shè)計原則。大型的系統(tǒng)在設(shè)計的時候需要重點考慮一些原則和設(shè)計要點,這也是我們在這里想要強(qiáng)調(diào)的,這些原則會對系統(tǒng)架構(gòu)的演進(jìn)方案和具體的架構(gòu)設(shè)計提供基礎(chǔ)的支持,這也是我們作為系統(tǒng)開發(fā)設(shè)計人員在設(shè)計一個系統(tǒng)剛開始需要掌握的理論基礎(chǔ)。
開發(fā)大型系統(tǒng)時,我們除了根據(jù)業(yè)務(wù)需求實現(xiàn)相應(yīng)的功能模塊外,還需要從高性能和高可用等多個維度來思考如何對系統(tǒng)進(jìn)行設(shè)計,遵循高并發(fā)、容錯性和可擴(kuò)展等多個設(shè)計分析原則是我們重要需要考慮的。
高并發(fā)原則
高并發(fā)是每個大型項目都無法回避的問題,保證項目在高并發(fā)的環(huán)境下正常運(yùn)行可以通過垂直擴(kuò)展和水平擴(kuò)展來實現(xiàn),我們簡述一下垂直和水平擴(kuò)展。
垂直擴(kuò)展:
直接通過提升單機(jī)的性能的層面去升級硬件或者軟件技術(shù),可以用一個比喻來說明,比如當(dāng)一個小牛無法拉動貨物的時候,就可以將小牛換成體能強(qiáng)壯的大牛。
水平擴(kuò)展:
通過增加服務(wù)器的節(jié)點個數(shù)來橫向擴(kuò)展系統(tǒng)的性能,這也是分布式的一個說明方式,還是上面的這個比喻,當(dāng)一頭牛無法拉動貨物的時候,就可以把貨物進(jìn)行拆分,然后用多頭牛去拉這些貨物,或者用多頭牛一起去拉這批貨物。
對比上面這兩種方案,我們可以很顯然地發(fā)現(xiàn),處置擴(kuò)展是最快的方法,只需要購買性能更加強(qiáng)大的硬件設(shè)備就可以迅速地提升性能,但是單機(jī)的能力還是有限的,如果貨物很多的話,比如我們用一座山來類比,一頭牛再厲害也是拉不動一座小山的,因此,大型互聯(lián)網(wǎng)系統(tǒng)對于高并發(fā)的最終解決方案是水平擴(kuò)展。
從技術(shù)層面講,可以使用緩存來減少對數(shù)據(jù)庫的訪問,用熔斷或者降級來提高響應(yīng)的速度,通過流量削峰等手段在項目的入口限流,先拆分項目或者使用微服務(wù)技術(shù)快速構(gòu)建功能模塊,然后再用 Spring Cloud 或 Dubbo 等來統(tǒng)一治理這些模塊,通過中間件搭建基于讀寫分離的高可用的數(shù)據(jù)庫集群等。在系統(tǒng)的測試階段還可以使用 JProfiler 等工具進(jìn)行性能分析,尋找性能瓶頸,從而有針對性地優(yōu)化。
衡量高并發(fā)的常見指標(biāo)包括:響應(yīng)時間、吞吐量或 QPS(Query per Second,每秒查詢率)、并發(fā)用戶數(shù)等。
容錯原則
如果高并發(fā)使用不當(dāng)?shù)脑?#xff0c;就容易造成各種邏輯混亂的情景,因此我們就需要對各種潛在的問題做好預(yù)案,也就是要確保系統(tǒng)要擁有一定的容錯性。比如使用 Spring Boot 和 Redis 來實現(xiàn)分布式緩存,使用 MQ 實現(xiàn)分布式場景下的事務(wù)一致性,使用 MQ、PRG 模式、Token 等解決重復(fù)提交的問題,使用“去重表”實現(xiàn)操作的冪等性,使用集群或 Zookeeper 解決失敗遷移的問題。
在分布式系統(tǒng)中,網(wǎng)絡(luò)時延也是不可避免的,一般情況可以在長連接的情況下通過“心跳監(jiān)測機(jī)制”來處理,我們下面簡單闡述一下心跳監(jiān)測機(jī)制。
在正常的網(wǎng)絡(luò)環(huán)境下面,當(dāng)用戶點擊了手機(jī)上某個 App 的“退出”按鈕后,就會調(diào)用服務(wù)端的 exit()等退出方法,從而注銷掉用戶的狀態(tài),如果用戶的手機(jī)信號中斷、關(guān)機(jī)或者處于飛行模式話該如何判斷用戶的狀態(tài)呢?除了通過 Session 有效時長來進(jìn)行判斷外,就可以通過心跳檢測來判斷,客戶端每隔 60s 向服務(wù)端發(fā)送一次心跳,如果服務(wù)端能夠接收到的話就說明客戶端的狀態(tài)正常,如果服務(wù)端沒有接收到就等待客戶端下一個 60s 發(fā)來的心跳,如果連續(xù) N 次都沒有接收到來自某個客戶端的心跳的話就可以認(rèn)定此客戶端已經(jīng)斷線了。
為了進(jìn)一步提高系統(tǒng)的容錯性,還可以預(yù)先采用“隔離的手段”,對于秒殺等可預(yù)知時間的流量暴增情況,就可以提前將秒殺隔離成獨立的服務(wù),防止秒殺帶來的流量問題影響到系統(tǒng)中的其他服務(wù),當(dāng)然如果預(yù)計的流量的增加不是很多的話,也可以使用多級緩存來解決高并發(fā)問題。
CAP 原則
分布式系統(tǒng)包含了多個節(jié)點,多個節(jié)點的數(shù)據(jù)之間應(yīng)該如何實現(xiàn)同步,在數(shù)據(jù)同步的時候需要考慮哪些因素呢?CAP 原則是理解和設(shè)計分布式系統(tǒng)的基礎(chǔ),包含了 C(Consistency)一致性、A(Availability)可用性、P(Partition tolerance)分區(qū)容錯性三個部分。
一致性 C:
在同一時刻所有節(jié)點中的數(shù)據(jù)都是相同的,當(dāng)客戶端發(fā)出讀請求之后,立刻能從分布式的所有節(jié)點中讀取到相同的數(shù)據(jù)。
可用性 A:
在合理的時間范圍內(nèi),系統(tǒng)能夠提供正常的服務(wù),不會出現(xiàn)異常或者超時等不可用的現(xiàn)象。
分區(qū)容錯性 P:
當(dāng)分布式系統(tǒng)中的一個或者多個節(jié)點發(fā)生網(wǎng)絡(luò)故障(網(wǎng)絡(luò)分區(qū)),從而脫離整個系統(tǒng)的網(wǎng)絡(luò)環(huán)境時,系統(tǒng)仍然能夠提供可靠的服務(wù),也就是說,當(dāng)部分節(jié)點故障時,系統(tǒng)還是可以正常運(yùn)行的。
著名的 CAP 原則是在任何一個分布式系統(tǒng)中,C、A、P 三者不可兼得,最多只能同時滿足兩個。為什么這么說呢,我們可以看一下下面的這個圖。
如果客戶端發(fā)出了寫的請求,成功更新了服務(wù) A,但由于網(wǎng)絡(luò)故障沒有更新服務(wù) B,那么下一次客戶端的讀請求如何處理呢?此時要么犧牲數(shù)據(jù)的一致性,要么在設(shè)計階段就嚴(yán)格要求數(shù)據(jù)必須得一致,當(dāng)像任何一個服務(wù)中寫失敗的時候,就撤銷全部的寫操作并提示失敗,即犧牲了寫操作的可用性。
一般而言分布式肯定會遇到網(wǎng)絡(luò)問題的,分區(qū)容錯性是最基本的要求,因此在實際設(shè)計的時候往往是在一致性和可用性之間根據(jù)具體的業(yè)務(wù)來權(quán)衡。
冪等性原則
上面提到了網(wǎng)絡(luò)問題,網(wǎng)絡(luò)問題會對用戶的調(diào)用服務(wù)的次數(shù)造成影響,我們看下面的由于返回失敗而導(dǎo)致用戶重復(fù)支付的例子:
在分布式系統(tǒng)中,如果模塊 A(如商品服務(wù))已經(jīng)成功調(diào)用了模塊 B(如支付服務(wù)),但是由于網(wǎng)絡(luò)故障等問題造成模塊 B 在返回時出錯,就可能導(dǎo)致用戶因為無法感知模塊 A 是否成功執(zhí)行從而多次主動執(zhí)行模塊 A,造成模塊 A 的重復(fù)執(zhí)行。例如,當(dāng)用戶在購買商品你時,如果在點擊“支付”按鈕之后不能看到“支付成功”等提示,就可能再次點擊“支付”按鈕造成用戶多次支付的異常行為。然而實際上,“支付”操作在用戶第一次點擊時就已經(jīng)成功執(zhí)行了,只是在給用戶返回結(jié)果時出了錯。所以我們需要考慮某個觸發(fā)的動作會不會被重復(fù)執(zhí)行的問題,使用冪等性原則就是對調(diào)用服務(wù)的次數(shù)進(jìn)行的一種限制,即無論對某個服務(wù)提供的接口調(diào)用多次或者是一次,其結(jié)果都是相同的。
對于分布式或者微服務(wù)系統(tǒng),為了實現(xiàn)冪等性,可以在寫操作之前,先通過執(zhí)行讀操作來實現(xiàn)。還是以上面提到的案例為例,當(dāng)商品服務(wù)調(diào)用支付服務(wù)時,嚴(yán)格按照蝦米阿尼的步驟執(zhí)行就可以實現(xiàn)冪等性:
-
讀操作:查詢支付服務(wù)中的支付狀態(tài)(已支付或未支付)。
-
在“去重表”中查詢‘“1”中的 ID 是否存在。
-
如果存在,直接返回結(jié)果,如果不存在,再執(zhí)行核心操作如支付,并將“1”中的 ID 存入“去重表”中,最后返回結(jié)果。
實現(xiàn)冪等性的方法還可以通過 CAS 算法、分布式鎖、悲觀鎖等方式。
特殊的是查詢和刪除是不會出現(xiàn)冪等性問題的,查詢一次或者多次,刪除一次或者多次的結(jié)果都是一樣的。
除了冪等性外,還需要注意表單重復(fù)提交的問題,冪等性是由于網(wǎng)絡(luò)等故障,用戶不知道第一次操作是否成功,因此發(fā)送了多次重復(fù)的操作,意圖在于確保第一次的操作成功,而表單重復(fù)提交是指用戶已經(jīng)看到了第一次操作成功的結(jié)果,但是由于誤操作或者其他原因點擊了“刷新頁面”等功能按鈕,導(dǎo)致多次發(fā)送相同的請求。可通過 Token 令牌機(jī)制、PRG 模式、數(shù)據(jù)庫唯一約束等方法避免表單的重復(fù)提交問題。
可擴(kuò)展原則
項目的規(guī)模會隨著用戶的數(shù)量的增加而增大,因此大型系統(tǒng)務(wù)必在設(shè)計的時候就要考慮到項目的擴(kuò)展解決方案,可擴(kuò)展原則要從項目架構(gòu)、數(shù)據(jù)庫設(shè)計、技術(shù)選型和編碼規(guī)范等多方面考量。例如,我們可以使用面向接口、前后端分離以及模塊化的編程風(fēng)格,采用無狀態(tài)化服務(wù),使用高內(nèi)聚低耦合的編程規(guī)范等,力爭在設(shè)計時預(yù)留一些后期擴(kuò)展時可能會使用到的接口,或者提前設(shè)計好項目擴(kuò)展的解決方案。下面我們給大家列舉了一些可擴(kuò)展原則實現(xiàn)的具體措施:
-
定義項目插件的統(tǒng)一頂級接口,在擴(kuò)展功能時使用方便擴(kuò)展的集成自定義插件。
-
使用無狀態(tài)化的應(yīng)用服務(wù),避免開發(fā)后期遇到 Session 共享等數(shù)據(jù)同步問題。
-
使用 HDFS 等分布式文件系統(tǒng),在存儲容量不足時迅速通過增加設(shè)備來擴(kuò)容。
-
合理地設(shè)計了數(shù)據(jù)庫的分庫分表策略及數(shù)據(jù)異構(gòu)方式,就能快速進(jìn)行數(shù)據(jù)庫擴(kuò)容。
-
使用分布式或微服務(wù)架構(gòu),快速開發(fā)并增加新的功能模塊。
如果在系統(tǒng)設(shè)計時就考慮了可擴(kuò)展的各種手段,就能在系統(tǒng)想遇到瓶頸或業(yè)務(wù)需求改變時快速做出更改,從而大幅提高開發(fā)的效率。
可維護(hù)原則與可監(jiān)控原則
一個設(shè)計優(yōu)良的項目不僅能夠加速項目的研發(fā),而且還可以在項目竣工后提供良好的可維護(hù)性與可監(jiān)控性,因此在系統(tǒng)的設(shè)計階段,要考慮項目的可維護(hù)原則與可監(jiān)控原則。
可維護(hù)原則是指系統(tǒng)在開發(fā)完畢后,維護(hù)人員能夠方便地改進(jìn)或者修復(fù)系統(tǒng)中存在的問題。可維護(hù)原則包含了可理解性、可修改性和可移植性等多方面因素。通常可以通過以下 5 個方面來實現(xiàn)項目的可維護(hù)原則。
-
項目的日志記錄功能完善,易于追溯問題,統(tǒng)計操作的情況。
-
有 BugFree 等 Bug 管理工具。
-
有豐富的項目文檔和注釋。
-
統(tǒng)一的開發(fā)規(guī)范。例如變量名盡量做到見名知意、少用縮寫、代碼縮進(jìn)規(guī)范、面向接口編程、編碼與配置分離、適當(dāng)?shù)厥褂迷O(shè)計模式等。
-
使用模塊化的編程模式。
可監(jiān)控原則是指對系統(tǒng)中的流量、性能、服務(wù)、異常等情況進(jìn)行實時的監(jiān)控。理想的狀態(tài)是既有儀表盤形狀的圖形化全局監(jiān)控數(shù)據(jù),又有基線型用于顯示各個時間段的歷史軌跡,還有一些關(guān)鍵業(yè)務(wù)的變動對比圖(對比業(yè)務(wù)變更前后,用戶流量的變動情況等)。
此外還需要對項目中的一些關(guān)鍵技術(shù)做性能監(jiān)控,確保新技術(shù)的引入的確能帶來性能的提升。
系統(tǒng)設(shè)計階段提前規(guī)避問題
上面闡述了關(guān)于系統(tǒng)設(shè)計時的一些需要注意的原則,如果能夠預(yù)先看到的話,就需要我們在設(shè)計層面提前解決,這樣子就會給后期的開發(fā)帶來很大的便捷。我們在這里簡單給大家介紹幾個景點的問題,如果不注意的話將會導(dǎo)致后期的開發(fā)工作十分艱難,甚至?xí)斐伞巴频怪貋怼钡那樾巍?/p>
Session 共享問題
在 Web 項目中,Session 是服務(wù)端用于保存客戶端信息的重要對象。單系統(tǒng)中的 Session 對象可以直接保存在內(nèi)存中,但是在分布式的或集群環(huán)境下,多個不同的節(jié)點就需要采取措施來共享 Session 對象,一般可以使用下面三種方式。
Session Replication
Session Replication 是指在客戶端第一次發(fā)出請求后,處理該請求的服務(wù)端就會創(chuàng)建一個與之對應(yīng)的 Session 對象,用于保存客戶端的狀態(tài)信息,之后為了讓其他服務(wù)器也能保存一份此 Session 對象,就需要將此 Session 對象在各個服務(wù)端節(jié)點之間進(jìn)行同步。
但是這個也會造成一定的問題就是嚴(yán)重的冗余,如果有多個用戶在同時訪問,那么每個服務(wù)節(jié)點中都會保存很多用戶的 Session 對象。服務(wù)節(jié)點的個數(shù)越多,Session 冗余的問題就越嚴(yán)重,因此這個方式只適用于服務(wù)節(jié)點比較少的場景。
Session Sticky
Session Sticky 是通過 Nginx 等負(fù)載均衡工具對各個用戶進(jìn)行標(biāo)記比如對 Cookie 進(jìn)行標(biāo)記,使每個用戶在經(jīng)過負(fù)載均衡工具之后都請求固定的服務(wù)節(jié)點。
客戶端 A 和客戶端 B 的請求都被分別轉(zhuǎn)發(fā)到了不同的應(yīng)用服務(wù)上面。因此各個服務(wù)中的 Session 就無需同步。但是這種做法也會有弊端,如果某個服務(wù)節(jié)點宕機(jī),那么該節(jié)點上的所有 Session 對象都會丟失。
獨立 Session 服務(wù)器
除了 Session Replication 和 Session Sticky 兩種方式外,還可以將系統(tǒng)中所有的 Session 對象都存放到一個獨立的 Session 服務(wù)中,之后各個應(yīng)用服務(wù)再分別從這個 Session 服務(wù)中獲取需要的 Session 對象。在大規(guī)模分布式系統(tǒng)中,推薦使用這種獨立的 Session 服務(wù)方式。這種方式在存儲 Session 對象時,既可以用數(shù)據(jù)庫,又可以使用各種分布式或集群存儲系統(tǒng)。
在使用了獨立的 Session 服務(wù)器之后,應(yīng)用服務(wù)就是一種無狀態(tài)服務(wù)了,換句話說,此時的應(yīng)用服務(wù)與用戶的狀態(tài)是無關(guān)的,無論是哪個用戶在什么時間發(fā)出的請求,所有的應(yīng)用服務(wù)都會進(jìn)行相同的處理。先從 Session 服務(wù)中獲取 Session 對象,再進(jìn)行相同的業(yè)務(wù)處理。
有狀態(tài)服務(wù)是指不同的應(yīng)用服務(wù)與用戶的狀態(tài)有著密切的關(guān)系,例如假設(shè)應(yīng)用服務(wù) A 中保存著用戶的 Session,應(yīng)用服務(wù) B 中沒有保存,之后如果用戶發(fā)出一個請求,經(jīng)過 Nginx 轉(zhuǎn)發(fā)到了應(yīng)用服務(wù) A 中,那么就可以直接進(jìn)行下單、結(jié)算等業(yè)務(wù),而如果用戶的請求被 Nginx 轉(zhuǎn)發(fā)到了應(yīng)用服務(wù) B 中,就會提示用戶“請先登錄······”,類似這種不同應(yīng)用服務(wù)因為對用戶狀態(tài)的持有情況不同,從而導(dǎo)致的執(zhí)行方式不同就可以理解為“有狀態(tài)服務(wù)”。
“無狀態(tài)服務(wù)”具有數(shù)據(jù)同步、快速部署的優(yōu)勢,但是也不能盲目地將其作為唯一的選擇,任何技術(shù)或者架構(gòu)的選擇都得看具體的業(yè)務(wù)場景,如在小型項目中或僅有一個服務(wù)的項目中,就可以采用有狀態(tài)的服務(wù)來降低開發(fā)的難度,縮短開發(fā)的周期。
技術(shù)選型原則與數(shù)據(jù)庫設(shè)計
在做技術(shù)選型時,既要注意技術(shù)的性能,又要注意安全性,并預(yù)估這些技術(shù)是否有足夠長的生命力。我們在這里以 MySQL 數(shù)據(jù)庫為例,介紹一種數(shù)據(jù)庫的選型的思路。
各種版本的 MySQL 默認(rèn)的并發(fā)連接數(shù)是一到兩百,單機(jī)可配置的最大連接數(shù)為 16384(一般情況下,由于計算機(jī)自身硬件的限制,單機(jī)實際能夠負(fù)載的并發(fā)數(shù)最多為一千左右)。因此高并發(fā)系統(tǒng)面臨的最大的性能瓶頸就是數(shù)據(jù)庫可,我們之前設(shè)計各種緩存的目的就是盡可能地減少對數(shù)據(jù)庫的訪問的。
除了在頁面、應(yīng)用程序中增加緩存以外,我們還可以在應(yīng)用程序和數(shù)據(jù)庫之間加一層 Redis 高速緩存,從而提高數(shù)據(jù)的訪問速度并且減少對數(shù)據(jù)庫的訪問次數(shù),具體如下:
-
搭建高可用 Redis 集群,并通過主從同步進(jìn)行數(shù)據(jù)備份、通過讀寫分離降低并發(fā)寫操作的沖突、通過哨兵模式在 Master 掛掉之后選舉新的 Master;
-
搭建雙 Master 的 MySQL 集群,并通過主從同步做數(shù)據(jù)備份;
-
通過 MyCat 對大容量的數(shù)據(jù)進(jìn)行分庫/分表,并控制 MySQL 的讀寫分離;
-
通過 Haproxy 搭建 MyCat 集群;
-
通過 Keepalived 搭建 Haproxy 集群,通過心跳機(jī)制防止單節(jié)點故障;并且 Keepalived 可以生成一個 VIP,并用此 VIP 與 Redis 建立連接。
緩存穿透與緩存雪崩問題
緩存可以在一定程度上緩解高并發(fā)造成的性能問題,但在一些特定場景下緩存自身也會帶來一些問題,比較典型的就是緩存穿透與緩存雪崩問題。為了講解的方便,我們使用 MySQL 代指所有的關(guān)系型數(shù)據(jù)庫,用 Redis 代指所有數(shù)據(jù)庫的緩存組件。
緩存穿透
緩存穿透是指大量查詢一些數(shù)據(jù)庫中不存在的數(shù)據(jù),從而影響數(shù)據(jù)庫的性能。例如 Redis 等 KV 存儲結(jié)構(gòu)的中間件可以作為 MySQL 等數(shù)據(jù)庫的緩存組件,但如果某些數(shù)據(jù)沒有被 Redis 緩存卻被大量的查詢,就會對 MySQL 帶來巨大壓力。
我們在前面介紹過,單機(jī) MySQL 最大能夠承受的并發(fā)連接數(shù)只有一千左右,因此無論是設(shè)計失誤(例如某個高頻訪問的緩存對象過期)、惡意攻擊(例如頻繁查詢某個不存在的數(shù)據(jù)),還是偶然事件(例如由于社會新聞導(dǎo)致某個熱點的搜索量大增)等,都可能讓 MySQL 遭受緩存穿透,從而宕機(jī)。
相信大家在理解了緩存穿透的原因后,解決思路就已經(jīng)明確了,舉例如下。
-
攔截非法的查詢請求,僅將合理的請求發(fā)送給 MySQL。如,可以使用驗證碼、IP 限制等手段限制惡意攻擊,并用敏感詞過濾器等攔截不合理的非法查詢。
-
緩存空對象。如,假設(shè)在 iphone9 上市后,可能會導(dǎo)致大量用戶搜索 iphone9,但此時 Redis 和 MySQL 中還沒有 iphone9 這個詞。一種解決辦法就是,將數(shù)據(jù)庫中不存在的 iphone9 也緩存在 Redis 中,如 Key=iphone9,value=””。之后,當(dāng)用戶再次搜索 iphone9 時,就可以直接從 Redis 中拿到結(jié)果,從而避免對 MySQL 的訪問,為了減少 Redis 對大量空對象的緩存,可以適當(dāng)減少空對象的過期時間。
-
建立數(shù)據(jù)標(biāo)識倉庫。將 MySQL 中的所有數(shù)據(jù)的 name 值都映射成 hash 值,例如可以將“商品表”中的商品名“iphone8”映射成 MD5 計算出來的 hash 值 b2dd48ff3e52d0796675693d08fb192e,然后再將全部 name 的 hash 值放入 Redis 中,從而構(gòu)建出一個“數(shù)據(jù)庫中所有可查數(shù)據(jù)的 hash 倉庫”。之后,每次在查詢 MySQL 之前都會先查詢這個 hash 倉庫,如果要查詢數(shù)據(jù)的 hash 值存在于倉庫中,再進(jìn)入 MySQL 做真實的查詢,如果不存在則直接返回。
緩存雪崩
除了緩存穿透以外,在使用緩存時還需要考慮緩存雪崩的情況。緩存雪崩是指由于某種原因造成 Redis 突然失效,從而造成 MySQL 瞬間壓力驟增,進(jìn)而嚴(yán)重影響 MySQL 性能甚至造成 MySQL 服務(wù)宕機(jī)。以下是造成緩存雪崩的兩個常見原因:
-
Redis 重啟;
-
Redis 中的大量緩存對象都設(shè)置了相同的過期時間。
為了避免緩存雪崩的發(fā)生,可參考使用以下解決方案:
-
搭建 Redis 集群,保證高可用;
-
避免大量緩存對象的 key 集中失效,盡力讓過期時間分配均勻一些,例如,可以給各個緩存的過期時間乘一個隨機(jī)數(shù);
-
通過隊列、鎖機(jī)制等控制并發(fā)訪問 MySQL 的線程數(shù)。
不同類型服務(wù)器的選擇
對于大型項目來說,為了保證較高的性能,至少需要三臺服務(wù)器:
-
應(yīng)用服務(wù)器。
-
數(shù)據(jù)庫服務(wù)器。
-
文件服務(wù)器。
并且,不同類型的服務(wù)器對硬件的需求也各不相同,例如,應(yīng)用服務(wù)器主要是處理業(yè)務(wù)邏輯,因此需要強(qiáng)大的 CPU 支持;數(shù)據(jù)庫服務(wù)器的性能依賴于磁盤檢索的速度和數(shù)據(jù)緩存的容量,因此需要速度較快的硬盤(如固態(tài)硬盤)和大容量的內(nèi)存;文件服務(wù)器主要是儲存文件,因此需要大容量的硬盤。
集群服務(wù)與動靜分離
現(xiàn)在,雖然將服務(wù)器分成了應(yīng)用服務(wù)器,數(shù)據(jù)庫服務(wù)器和文件服務(wù)器。但是每個子服務(wù)器僅僅只有一個,并且單一應(yīng)用服務(wù)器能夠處理的請求連接是有限的,例如單個 Tomcat 最佳的并發(fā)量是 250 左右,如果并發(fā)的請求大于 250 就要考慮搭建 Tomcat 集群了(這里我們推薦可以使用 docker 快速搭建)。使用集群除了能解決負(fù)載均衡以外,還有另一個優(yōu)勢:失敗遷移。當(dāng)某一個應(yīng)用服務(wù)器宕機(jī)時,其他應(yīng)用服務(wù)器可以繼續(xù)處理客戶請求。
集群雖然有了,是否就適合存放全部數(shù)據(jù)并處理全部請求了呢?其實我們發(fā)現(xiàn)還可以進(jìn)一步優(yōu)化為“動靜分離”。下面闡述一下“動”與“靜”的概念。
靜:
如果是觀看電視劇/電影、微視頻、高清圖片等這種大文件的靜態(tài)請求,最好使用 CDN 將這些靜態(tài)資源部署在各地的邊緣服務(wù)器上,讓用戶可以就近獲取所需內(nèi)容,以此降低網(wǎng)絡(luò)擁塞,提高用戶訪問響應(yīng)速度。如果是 html、css、js 或小圖片等這類小文件的靜態(tài)資源,就可以直接緩存在反向代理服務(wù)器上,當(dāng)用戶請求時直接給予響應(yīng)。還可以用 webpack 等工具將 css、javascript、html 進(jìn)行壓縮、組合,并將多個圖片合成一張雪碧圖,最終將靜態(tài)資源進(jìn)行統(tǒng)一打包,從而優(yōu)化前端資源的體積。
動:
如果是搜索商品、增、刪、改等動態(tài)請求,就需要訪問我們自己編寫并搭建的應(yīng)用服務(wù)器。
之后,如果并發(fā)數(shù)繼續(xù)增加、項目繼續(xù)擴(kuò)大,就可以使用分布式對項目進(jìn)行拆分,接下來我們給大家具體介紹一下分布式系統(tǒng)的概念。
分布式系統(tǒng)
分布式系統(tǒng)可以理解為將一個系統(tǒng)拆分為多個模塊并部署到不同的計算機(jī)上,然后通過網(wǎng)絡(luò)將這些模塊進(jìn)行整合,從而形成的一個完整系統(tǒng)。在實際應(yīng)用時,分布式系統(tǒng)包含了分布式應(yīng)用、分布式文件系統(tǒng)和分布式數(shù)據(jù)庫等類型,具體如下。
分布式應(yīng)用:
將項目根據(jù)業(yè)務(wù)拆分成不同的模塊,各個模塊之間再通過 dubbo、Spring Cloud 等微服務(wù)架構(gòu)或 SOA 技術(shù)進(jìn)行整合。像這種根據(jù)業(yè)務(wù)功能,將項目拆分成多個模塊的方式,也成為垂直拆分。此外,我們也可以對項目進(jìn)行水平拆分,例如可以將項目根據(jù)三層架構(gòu)拆分成 View 層、Service 層、Dao 層等,然后將各層部署在不同的機(jī)器上。
分布式文件系統(tǒng):
將所有文件分散存儲到多個計算機(jī)上。
分布式數(shù)據(jù)庫系統(tǒng):
將所有數(shù)據(jù)分散存儲到多個計算機(jī)上。
分布式系統(tǒng)搭建完畢后,接下來可能遇到的問題是,如何提高數(shù)據(jù)的訪問速度?一種解決方案就是在應(yīng)用服務(wù)器上使用緩存的方式,緩存分為本地緩存和遠(yuǎn)程緩存。本地緩存的訪問速度最快,但是本地機(jī)器的內(nèi)存容量、CPU 能力等有限。遠(yuǎn)程緩存就是用于緩存分布式和集群上的數(shù)據(jù),可以無限制地擴(kuò)充內(nèi)存容量、CPU 能力,但是遠(yuǎn)程緩存需要進(jìn)行遠(yuǎn)程 IO 操作,因此緩存的速度相比本地緩存慢。
如果緩存沒有命中,就需要直接請求數(shù)據(jù)庫,當(dāng)請求量較大時,就需要對數(shù)據(jù)庫進(jìn)行讀寫分離,并且用主從同步對數(shù)據(jù)庫進(jìn)行熱備份。如果是海量數(shù)據(jù),除了可以用關(guān)系型數(shù)據(jù)庫搭建分布式數(shù)據(jù)庫以外,還可以使用 redis、Hbase 等 NoSQL 數(shù)據(jù)庫。相應(yīng)的,在處理海量數(shù)據(jù)時,也推薦使用 ElasticSearch 或 Lucene 等搜索引擎,并用 Hadoop、Spark、Storm 等大數(shù)據(jù)技術(shù)進(jìn)行處理。
值得一提的是,在微服務(wù)架構(gòu)中,數(shù)據(jù)庫的使用比較靈活:每個微服務(wù)都可以有自己獨立的數(shù)據(jù)庫,或者多個微服務(wù)之間也可以交叉使用或共享同一份數(shù)據(jù)庫。并且這些服務(wù)之間可以使用 thrift、grpc 等 RPC 技術(shù)來進(jìn)行整合。但要注意,使用 RPC 會給整個系統(tǒng)增加一層跨語言的中轉(zhuǎn)結(jié)構(gòu),因此必然會帶來一定程序的性能損耗。一般來講,進(jìn)行 RPC 跨語言調(diào)用是一種不得已而為之的辦法。
大型系統(tǒng)架構(gòu)設(shè)計
在設(shè)計大型系統(tǒng)的架構(gòu)時,要特別注意對流量的控制,可以采取降級、限流和緩存等策略。在這里我們會給大家介紹一種常見的流量負(fù)載架構(gòu),然后再給出一個流行的軟件技術(shù)的選型。
服務(wù)預(yù)處理-限流與多層負(fù)載
為了保證億級流量下的高性能及高可用,除了精湛的編碼功底和巧妙的算法以外,還需要對海量請求進(jìn)行多級限流和多層負(fù)載。在這里我們給大家介紹一種處理客戶端海量請求的思路,供讀者們參考,具體如下。
-
攔截非法請求,從而進(jìn)行一定程度的限流,舉例如下。
-
加入驗證碼,防止機(jī)器人惡意攻擊。
-
隱藏秒殺入口地址,確保用戶在進(jìn)行了合理的操作后才能進(jìn)入。
-
限制 IP 操作:對于秒殺等限量業(yè)務(wù),限制某一 IP 能夠發(fā)起請求的次數(shù)。
-
延長用戶操作時間:為避免用戶刷單,可以對具體業(yè)務(wù)在操作時間上進(jìn)行限制,如同一用戶 5 秒鐘內(nèi)只能進(jìn)行一次操作。
-
通過 lvs 對客戶請求進(jìn)行分流(負(fù)載均衡),并通過 keepalived 實現(xiàn)多機(jī)部署、防止單點故障。
-
通過 Nginx 將請求進(jìn)行動靜分離:將靜態(tài)請求部署到 CDN 上進(jìn)行加速(或直接在 Nginx 中緩存),將動態(tài)請求發(fā)送給 MQ 進(jìn)行流量削峰(當(dāng)有大量請求時,為減輕服務(wù)端壓力,可以先在 MQ 中設(shè)置可接收的最大請求量;超過最大值的請求將被直接丟棄)。
-
可以將同一個服務(wù)多次部署到多個節(jié)點上,形成集群服務(wù)(例如圖中有兩個“服務(wù) A”),并用 Nginx 進(jìn)行整合,用來實現(xiàn)并發(fā)請求的負(fù)載均衡和失敗遷移。
-
項目中的所有服務(wù)可以通過 Maven 或 Gradle 進(jìn)行依賴管理,并使用 Git/GitHub 進(jìn)行版本控制和團(tuán)隊協(xié)作開發(fā)。
我們接下來要做的事情是進(jìn)一步擴(kuò)容,以上架構(gòu)已經(jīng)能夠扛得住千萬級的流量了,但如果遇到“雙十一購物”、“12306 搶票”、“大型電商秒殺”等億級流量,還需要做進(jìn)一步改進(jìn)。可以進(jìn)行以下兩點擴(kuò)充,搭建最終的 Lvs+Nginx+DNS 負(fù)載架構(gòu):
-
通過 DNS 綁定多個 LVS 組成的集群,進(jìn)一步負(fù)載均衡。
-
通過 Nginx 對動態(tài)請求的路徑轉(zhuǎn)發(fā)到特定的服務(wù)地址上。例如,當(dāng)用戶訪問 lanqiao.org/payment/時,Nginx 將該請求分發(fā)到服務(wù) A 上,當(dāng)用戶訪問 lanqiao.org/goods/時,Nginx 將該請求分發(fā)到服務(wù) B 上。
值得注意的是,如果系統(tǒng)本身不是很大,那么是沒有必要使用以上所有組件的。例如軟負(fù)載均衡 LVS 的確可以減輕后面單個組件的壓力,但 LVS 自身也存在著不足:LVS 會使原本直接到達(dá) Nginx 的請求在 LVS 自身中轉(zhuǎn)了一次,因此會增加系統(tǒng)中的網(wǎng)絡(luò)流量,并且?guī)硪欢ǖ难舆t。簡言之,任何一個中間件都可能會增加系統(tǒng)的穩(wěn)定性及性能,但同時也會存在著一定的弊端,在實際使用時一定要結(jié)合項目的實際情況慎重權(quán)衡。
后面會針對這樣的架構(gòu)模型給大家引進(jìn)復(fù)雜的大型系統(tǒng)采用分布式或者微服務(wù)架構(gòu)是如何搭建具體的應(yīng)用服務(wù)的,針對高并發(fā)系統(tǒng)和大型互聯(lián)網(wǎng)架構(gòu)的介紹就是這些,大家多體會一下解決問題時層層遞進(jìn)的方案是如何提出來的。
總結(jié)
高并發(fā)是每個大型項目和公司實際開發(fā)過程中都無法回避的一個問題,我們通過給大家高并發(fā)需求的業(yè)務(wù)場景,帶大家初始并了解了實際業(yè)務(wù)中并發(fā)性能提升對于整個系統(tǒng)的重要性。并通過如何設(shè)計一個大型系統(tǒng)方案全局掌控,給大家分析了設(shè)計原則并給出了一個實際的架構(gòu)案例供大家學(xué)習(xí),希望大家下來多琢磨,多研究和回顧。
同時為大家還整理總結(jié)了幾十個實戰(zhàn)項目,感興趣的同學(xué)可以在評論區(qū)留言或者私信我獲得更多資料。
總結(jié)
以上是生活随笔為你收集整理的「万字干货」高并发系统分析与大型互联网架构介绍的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Codeforces Round #73
- 下一篇: window.showModalDial