<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>

          「從小白到架構(gòu)師」:揭開高并發(fā)系統(tǒng)面紗

          共 6678字,需瀏覽 14分鐘

           ·

          2024-04-02 16:03

          作者:finley 出處:https://www.cnblogs.com/Finley/p/16774643.html

          版權(quán):本作品采用「署名-非商業(yè)性使用-相同方式共享 4.0 國際」許可協(xié)議進(jìn)行許可。

          「從小白到架構(gòu)師」系列努力以淺顯易懂、圖文并茂的方式向各位讀者朋友介紹 WEB 服務(wù)端從單體架構(gòu)到今天的大型分布式系統(tǒng)、微服務(wù)架構(gòu)的演進(jìn)歷程。本文是「從小白到架構(gòu)師」系列的第一篇,主要講述提升網(wǎng)站吞吐量、應(yīng)對更高并發(fā)量的主要技術(shù)手段

          從個人博客開始

          相信很多朋友都搭建過個人博客之類的后端系統(tǒng),這類系統(tǒng)的架構(gòu)非常簡單:

          首先購買一臺云服務(wù)器,并在上面安裝 MySQL 數(shù)據(jù)庫,然后部署一個 node.js 之類的 HTTP 服務(wù)器監(jiān)聽 80 和 443 端口,在 node.js 中連接數(shù)據(jù)庫并實(shí)現(xiàn)業(yè)務(wù)邏輯。

          最后購買一個域名并配置 DNS 記錄指向我們的服務(wù)器 IP 地址,這個網(wǎng)站就算搭建完成了。

          隨著不斷的努力,我們網(wǎng)站的訪問量越來越多。某天早晨當(dāng)你美滋滋打開網(wǎng)站想要看一眼最新評論時(shí),卻發(fā)現(xiàn)網(wǎng)站打不開了。。。

          登錄服務(wù)器查看日志后發(fā)現(xiàn)因?yàn)樵L問人數(shù)過多,MySQL 已經(jīng)無法及時(shí)響應(yīng)所有的查詢請求,看來有必要進(jìn)行一波優(yōu)化了。

          緩存

          在博客、新聞、微博、(短)視頻、電商等大多數(shù)業(yè)務(wù)場景下讀取請求的次數(shù)要遠(yuǎn)遠(yuǎn)大于寫入請求的次數(shù),且讀取集中在少數(shù)熱門數(shù)據(jù)上而長尾數(shù)據(jù)很少被訪問。

          在這樣的場景中我們可以通過加緩存的方式來提高網(wǎng)站處理讀取請求的并發(fā)量。

          Redis 是一種比較常用的緩存系統(tǒng),它是 Key-Value 結(jié)構(gòu)的內(nèi)存緩存。Redis 作為獨(dú)立進(jìn)程運(yùn)行并通過 TCP 協(xié)議提供服務(wù),這意味著不同服務(wù)器上的業(yè)務(wù)進(jìn)程(如 node.js) 可以連接到同一個 Redis 實(shí)例并共享其中的數(shù)據(jù)。

          由于數(shù)據(jù)在內(nèi)存中 Redis 的訪問速度要遠(yuǎn)遠(yuǎn)大于基于磁盤存儲的數(shù)據(jù)庫(單個 Redis 實(shí)例可以達(dá)到 每秒10萬次讀寫,而 MySQL 只能達(dá)到每秒百次寫入或千次查詢)。

          但是內(nèi)存的價(jià)格比 SSD 昂貴很多,可用的內(nèi)存空間非常有限,這要求我們妥善設(shè)計(jì)緩存方案以及淘汰策略,在緩存命中率和內(nèi)存消耗之間取得合理的平衡。

          使用緩存是一種有效的提高系統(tǒng)吞吐量的方案,但要注意處理緩存一致性、緩存穿透、緩存雪崩等問題。

          Redis 官方提供了 Redis Cluster 作為集群解決方案,社區(qū)中也有 Codis 等優(yōu)秀的代理式集群解決方案,AWS、阿里云、騰訊云等云服務(wù)商都提供了商業(yè)化的 Redis 集群。在單機(jī)版 Redis 吞吐量不夠用時(shí),我們可以方便的遷移到 Redis 集群上。

          負(fù)載均衡

          緩存抗住了大部分的訪問請求,隨著用戶數(shù)的增長,現(xiàn)在并發(fā)壓力主要落在單機(jī)的業(yè)務(wù)服務(wù)器上。

          一種升級思路是提高單臺服務(wù)器的配置比如4核8GB內(nèi)存升級到8核16GB內(nèi)存,這種思路稱為縱向擴(kuò)容;

          另一種思路是提高服務(wù)器的數(shù)量,使用多臺服務(wù)器同時(shí)處理請求,這種思路稱為橫向擴(kuò)容。相對于不斷增加的訪問量,單機(jī)性能的提升空間卻極其有限,所以在實(shí)際工作中更多的采用橫向擴(kuò)容的思路。

          我們使用反向代理軟件 Nginx 代替業(yè)務(wù)服務(wù)器監(jiān)聽端口,在多臺云服務(wù)器上部署業(yè)務(wù)服務(wù)器,并將這些業(yè)務(wù)服務(wù)器配置為 Nginx 的后端服務(wù)器組。

          來自用戶瀏覽器的 HTTP 請求首先到達(dá) Nginx, Nginx 根據(jù)我們配置的規(guī)則將請求轉(zhuǎn)發(fā)給負(fù)載較低的一臺業(yè)務(wù)服務(wù)器,在收到業(yè)務(wù)服務(wù)器響應(yīng)之后將其返回給用戶。

          業(yè)務(wù)服務(wù)器的數(shù)量可以根據(jù)當(dāng)前的訪問量隨時(shí)增加或減少,在高峰期增加服務(wù)器保證質(zhì)量,低谷期減少服務(wù)器節(jié)約成本。

          我們都知道在電腦 A 上「復(fù)制」一個文件是不能在電腦 B 上進(jìn)行「粘貼」的,同理一個用戶的第一次請求被路由到業(yè)務(wù)服務(wù)器 A 第二次請求被路由到業(yè)務(wù)服務(wù)器 B 也會產(chǎn)生類似的問題。

          抽象一點(diǎn)說,第一次請求改變了業(yè)務(wù)服務(wù)器 A 的狀態(tài),而第二次請求的正確響應(yīng)依賴于業(yè)務(wù)服務(wù)器的狀態(tài),在「復(fù)制-粘貼」這個例子中「粘貼板」的狀態(tài)決定了是否能夠正確處理「粘貼」請求。

          聰明的你可能會說:那么同一個用戶的請求始終路由到同一臺業(yè)務(wù)服務(wù)器就可以了?

          我們復(fù)習(xí)一下上面這句話:「業(yè)務(wù)服務(wù)器的數(shù)量可以根據(jù)當(dāng)前的訪問量隨時(shí)增加或減少」,也就是說保存了用戶狀態(tài)的業(yè)務(wù)服務(wù)器可能會被我們回收掉,在高峰期某個用戶可能會被分流到新的服務(wù)器。

          這是一個非常難以解決的問題,所以業(yè)界通常的思路是解決問題本身,即:業(yè)務(wù)服務(wù)器無狀態(tài)化。

          業(yè)務(wù)服務(wù)器應(yīng)該像純函數(shù)一樣,輸出完全由輸入決定,自身不存儲任何數(shù)據(jù),也不維護(hù)任何狀態(tài)。

          無狀態(tài)的服務(wù)器可以隨時(shí)啟動和停止,服務(wù)器的數(shù)量也可以隨時(shí)增加或減少。某臺服務(wù)器故障后,它未完成的請求也可以轉(zhuǎn)移到其它服務(wù)器上重試。當(dāng)然業(yè)務(wù)服務(wù)器無狀態(tài)不代表業(yè)務(wù)邏輯無狀態(tài),所有的狀態(tài)都應(yīng)存儲在數(shù)據(jù)庫

          單臺 Nginx 的性能雖然很高但仍是有極限的,同樣的思路我們可以將負(fù)載分布在多臺 Nginx 上。

          Linux Virtual Server 是工作在 TCP 層(OSI 四層)的負(fù)載均衡器,是業(yè)界常用的 Nginx 負(fù)載均衡方案。

          由于 LVS 是單機(jī)版的軟件,若 LVS 所在服務(wù)器宕機(jī)則會導(dǎo)致整個后端系統(tǒng)都無法訪問,因此需要有備用節(jié)點(diǎn)。

          可使用 keepalived 軟件模擬出虛擬 IP,然后把虛擬 IP 綁定到多臺 LVS 服務(wù)器上,瀏覽器訪問虛擬 IP 時(shí),會被路由器重定向到真實(shí)的 LVS 服務(wù)器,當(dāng)主 LVS 服務(wù)器宕機(jī)時(shí),keepalived 軟件會自動更新路由器中的路由表,把虛擬 IP 重定向到另外一臺正常的 LVS 服務(wù)器,從而達(dá)到 LVS 服務(wù)器高可用的效果

          如果 LVS 也扛不住了呢?不用著急,在 DNS 服務(wù)器中可配置一個域名對應(yīng)多個 IP 地址。

          DNS 服務(wù)器可以按照負(fù)載均衡策略將域名解析到其中一臺 LVS 的 ip 地址,從此系統(tǒng)可自由的進(jìn)行橫向擴(kuò)容:

          在上面這張架構(gòu)圖中除了數(shù)據(jù)庫外的組件都不是單機(jī)運(yùn)行的,單臺機(jī)器故障不會導(dǎo)致整個系統(tǒng)宕機(jī),任何一個組件容量不足時(shí)都可以通過加機(jī)器迅速擴(kuò)容。這是分布式系統(tǒng)中另一個重要的原則:消除服務(wù)器內(nèi)單點(diǎn)

          如果想學(xué)Java項(xiàng)目的,強(qiáng)烈推薦我的??項(xiàng)目消息推送平臺Austin10K+ stars),可以用作畢業(yè)設(shè)計(jì),可以用作校招,可以看看生產(chǎn)環(huán)境是怎么推送消息的。 

          倉庫地址(可點(diǎn)擊閱讀原文跳轉(zhuǎn)):https://gitee.com/zhongfucheng/austin

          數(shù)據(jù)庫篇

          經(jīng)過緩存和橫向擴(kuò)容,我們的網(wǎng)站已經(jīng)可以應(yīng)對高并發(fā)的讀請求以及業(yè)務(wù)邏輯計(jì)算的開銷。但是我們寫入的吞吐量仍然受限于單機(jī)數(shù)據(jù)庫,那么有沒有辦法解決數(shù)據(jù)庫的單點(diǎn)問題呢?

          讀寫分離

          包括 MySQL 在內(nèi)的絕大多數(shù)主流數(shù)據(jù)庫均支持主從復(fù)制,從庫會監(jiān)聽主庫的更新并將更新同步到本地,從而始終保持與主庫的數(shù)據(jù)集一致。

          從庫除了作為備份之外也可以像緩存一樣分擔(dān)主庫的讀取壓力,即數(shù)據(jù)更新寫入主庫而查詢操作則在從庫上進(jìn)行,我們將這種技術(shù)稱為讀寫分離。

          一些復(fù)雜的查詢會消耗數(shù)據(jù)庫大量的 IO 和 CPU 資源,舉例來說:我們將關(guān)注關(guān)系存儲在 MySQL 中,而計(jì)算用戶粉絲數(shù)的 select count 查詢非常耗時(shí),我們可以將這樣的查詢移到從庫上進(jìn)行,主庫的資源則可以用來處理更多寫請求。

          分庫分表

          在讀寫分離一節(jié)中我們配置了多個用于處理讀取請求的從庫,但是處理寫入請求的主庫始終只有一個,主庫仍然是制約整個網(wǎng)站的吞吐量的瓶頸。

          那我們能否像讀庫一樣配置多個主庫,以此來提升網(wǎng)站寫入的吞吐量呢?

          答案是肯定的,使用多個主庫的核心問題在于如何決定某一條數(shù)據(jù)應(yīng)該寫入哪一個節(jié)點(diǎn)中

          比如用戶 A 發(fā)表了一篇文章我們將它存入了數(shù)據(jù)庫 1,后續(xù)查詢時(shí)我們卻到數(shù)據(jù)庫 2 中進(jìn)行搜索,自然一無所獲;又或者用戶 A 的第一篇文章存入了數(shù)據(jù)庫 1,第二篇文章卻存入了數(shù)據(jù)庫 2,在我們按時(shí)間查詢用戶 A 的文章時(shí)就不得不搜索每一個數(shù)據(jù)庫然后費(fèi)時(shí)費(fèi)力的將結(jié)果重新排序。

          決定數(shù)據(jù)寫入哪個節(jié)點(diǎn)的策略我們通常稱為分表的路由策略,選擇路由策略的原則是盡可能的將需要一起使用的數(shù)據(jù)放到同一個數(shù)據(jù)庫中,避免跨庫帶來的額外復(fù)雜度。比如在博客系統(tǒng)的場景中,我們通常會將同一個用戶的文章放入同一個數(shù)據(jù)庫。

          接下來的事情就是如何將用戶 ID 映射到某個表上了。最簡單的方法是 hash(user_id) % db_num, 但實(shí)際場景中節(jié)點(diǎn)的數(shù)量會發(fā)生變化(即擴(kuò)縮容),此時(shí)幾乎所有數(shù)據(jù)的 db_id 都會發(fā)生改變,在擴(kuò)縮容過程中需要遷移大量的數(shù)據(jù)。

          因此,在實(shí)際使用更多的是一致性哈希算法,它的目標(biāo)是在數(shù)據(jù)庫節(jié)點(diǎn)數(shù)量變化時(shí)盡可能的減少需要遷移的數(shù)據(jù)量。

          無論如何選擇分表路由策略我們都無法完全避免進(jìn)行跨表讀寫,這時(shí)有一些額外的工作需要處理,比如將多個數(shù)據(jù)庫返回的結(jié)果重新進(jìn)行排序和分頁,或者需要保證跨庫寫入的 ACID (事務(wù))性。

          此時(shí)就要使用諸如 MyCat 這樣的數(shù)據(jù)庫中間件來幫我們處理這些麻煩事了。

          和單機(jī)數(shù)據(jù)庫一樣,分庫分表架構(gòu)下同樣可以為數(shù)據(jù)庫節(jié)點(diǎn)配置從庫,一是可以用作備份,二是用來實(shí)現(xiàn)讀寫分離。

          NewSQL

          MySQL 以數(shù)據(jù)頁為單位進(jìn)行存儲,每個數(shù)據(jù)頁內(nèi)按主鍵順序存儲著多行數(shù)據(jù)。在寫入新數(shù)據(jù)時(shí)首先需要讀取主鍵索引找到對應(yīng)的數(shù)據(jù)頁,然后將新的數(shù)據(jù)行插入進(jìn)去。

          必要時(shí)還需要要將原來一頁中的數(shù)據(jù)轉(zhuǎn)移到其它數(shù)據(jù)頁上才能滿足頁內(nèi)按主鍵順序排列的要求。

          這種由于一次數(shù)據(jù)庫寫入請求導(dǎo)致的多次磁盤寫的現(xiàn)象被稱為寫放大,隨機(jī)讀寫和寫放大是制約 MySQL 寫入性能的主要瓶頸。

          本文描述基于 MySQL 默認(rèn)的 InnoDB 存儲引擎, InnoDB 同時(shí)也是 MySQL 中應(yīng)用最廣的存儲引擎。本文不強(qiáng)調(diào) MySQL 和 InnoDB 的區(qū)分。

          LSM-Tree 是一種日志式的存儲結(jié)構(gòu),對數(shù)據(jù)的增刪改都是通過在日志尾部追加一條新記錄實(shí)現(xiàn)的。由于不需要尋找數(shù)據(jù)頁和維護(hù)頁結(jié)構(gòu)只需要進(jìn)行順序?qū)懀罩臼酱鎯Y(jié)構(gòu)的寫入性能大大優(yōu)于 MySQL 這類面向頁的存儲結(jié)構(gòu)。

          LSM-Tree 結(jié)構(gòu)數(shù)據(jù)庫的經(jīng)典代表是 RocksDB 和 LevelDB, 很多新一代的數(shù)據(jù)庫(NewSQL)的底層均使用 RocksDB 或 LevelDB 作為存儲引擎。

          比如大名鼎鼎的 TiDB 便是以 RocksDB 作為存儲引擎,在其上通過 Multi-Raft 協(xié)議構(gòu)造高一致性、高可用、支持快速擴(kuò)縮容的分布式數(shù)據(jù)系統(tǒng)。

          圖片源自 tidb 官網(wǎng):https://docs.pingcap.com/zh/tidb/dev/tidb-storage

          直接使用 TiDB 之類的分布式數(shù)據(jù)庫可能是比自行分庫分表更簡單高效的方案。除了 TiDB 外還有各類 NewSQL 活躍在業(yè)界解決著傳統(tǒng)關(guān)系型數(shù)據(jù)庫難以解決的問題。

          比如用于進(jìn)行復(fù)雜統(tǒng)計(jì)查詢的 Hive、用于進(jìn)行模糊搜索的 ElasticSearch、用于存儲和分析海量日志的 ClickHouse 等時(shí)序數(shù)據(jù)庫、用于計(jì)算共同好友等場景的 Dgraph 等圖數(shù)據(jù)庫…… 這些新型數(shù)據(jù)必將極大的提高開發(fā)效率和系統(tǒng)性能。

          消息隊(duì)列

          消息隊(duì)列在應(yīng)對高并發(fā)上也是一種非常有用的技術(shù),這里消息隊(duì)列有兩種用途:第一是用作限流,用戶請求先進(jìn)入消息隊(duì)列排隊(duì),然后慢慢送到業(yè)務(wù)服務(wù)器進(jìn)行處理,起到削峰填谷的作用,可以用來應(yīng)對秒殺等瞬間峰值的場景;

          第二是異步處理任務(wù),比如訂單創(chuàng)建成功后立即返回,通知發(fā)貨等邏輯通過消息隊(duì)列進(jìn)行異步處理,從而減少請求處理時(shí)間。

          總結(jié)

          應(yīng)對高并發(fā)

          我們從最簡單的單服務(wù)器+單數(shù)據(jù)庫架構(gòu)開始,通過緩存和讀寫分離技術(shù)提高讀取吞吐量,通過橫向擴(kuò)容提高業(yè)務(wù)服務(wù)器容量,通過使用分庫分表技術(shù)提高數(shù)據(jù)庫寫入能力。最后兼具高性能、高一致性的新一代的分布式數(shù)據(jù)庫系統(tǒng) ———— TiDB。

          緩存、橫向擴(kuò)容、通過 MQ 異步執(zhí)行是在業(yè)務(wù)開發(fā)中最常用、成本最低的提高吞吐量的方案。

          新一代的分布式數(shù)據(jù)庫系統(tǒng)替我們解決了傳統(tǒng)關(guān)系型數(shù)據(jù)庫單點(diǎn)運(yùn)行、吞吐量有限、難以橫向擴(kuò)容、應(yīng)用場景局限等問題,各大廠商正在越來越多的將 NewSQL 應(yīng)用于生產(chǎn)環(huán)境中, 學(xué)習(xí)使用新一代數(shù)據(jù)庫技術(shù)必將極大的提高開發(fā)效率和系統(tǒng)性能。

          走向分布式系統(tǒng)

          在本文中我們應(yīng)對吞吐量不足的核心思路是將單機(jī)系統(tǒng)改造為分布式系統(tǒng),很多同學(xué)一提到分布式系統(tǒng)便想到 CAP 理論、Paxos 算法、Hadoop 等嚇人的名詞,然后就沒有然后了。

          在本文中我們提到了兩種分布式系統(tǒng),第一種是在「負(fù)載均衡」一節(jié)中提到的無狀態(tài)分布式系統(tǒng),這類系統(tǒng)結(jié)構(gòu)比較簡單通常由負(fù)載均衡+業(yè)務(wù)服務(wù)器組成,由于無狀態(tài)的特性可以隨意擴(kuò)縮容。

          第二種便是比較復(fù)雜的有狀態(tài)分布式系統(tǒng),具體的講就是各種分布式數(shù)據(jù)庫(包括內(nèi)存數(shù)據(jù)庫),幸運(yùn)的是廠商準(zhǔn)備好了開箱即用的方案,倒也不必為此花費(fèi)過多心力。

          本文中提到的「分庫分表 + 主從復(fù)制」是大多數(shù)分布式數(shù)據(jù)庫的基本思想,分布式數(shù)據(jù)庫面臨的主要難點(diǎn)是系統(tǒng)內(nèi)的拓?fù)涫莿討B(tài)變化的:現(xiàn)在數(shù)據(jù)庫中有幾個主節(jié)點(diǎn)在正常工作?這些主節(jié)點(diǎn)的地址是什么?那些節(jié)點(diǎn)發(fā)生了主從切換?

          分布式數(shù)據(jù)庫需要讓系統(tǒng)內(nèi)所有節(jié)點(diǎn)對系統(tǒng)的拓?fù)浣Y(jié)構(gòu)的認(rèn)知始終保持一致,否則便會出現(xiàn)應(yīng)該寫入節(jié)點(diǎn) A 實(shí)際上寫入了節(jié)點(diǎn) B 這樣的錯誤情況。


          END


          Java項(xiàng)目訓(xùn)練營

          我開通了項(xiàng)目股東服務(wù),已經(jīng)有不少消息推送平臺項(xiàng)目股東拿了阿里/vivo等大廠offer了。我是沒找到網(wǎng)上有跟我提供相同的服務(wù),價(jià)格還比我低的。

          ??一對一周到的服務(wù):有很多人的自學(xué)能力和基礎(chǔ)確實(shí)不太行,不知道怎么開始學(xué)習(xí),從哪開始看起,學(xué)習(xí)項(xiàng)目的過程中會走很多彎路,很容易就迷茫了。付費(fèi)最跟自學(xué)最主要的區(qū)別就是我的服務(wù)會更周到。我會告訴你怎么開始學(xué)這個開源項(xiàng)目,哪些是重點(diǎn)需要掌握的,如何利用最短的時(shí)間把握整個系統(tǒng)架構(gòu)和編碼的設(shè)計(jì),把時(shí)間節(jié)省下來去做其他事情。學(xué)習(xí)經(jīng)驗(yàn)/路線/簡歷編寫/面試經(jīng)驗(yàn)知無不言

          ??本地直連遠(yuǎn)程服務(wù):生產(chǎn)環(huán)境的應(yīng)用系統(tǒng)肯定會依賴各種中間件,我專門買了兩臺服務(wù)器已經(jīng)搭建好必要的環(huán)境??,在本地就可以直接啟動運(yùn)行體驗(yàn)和學(xué)習(xí),無須花額外的時(shí)間自行搭建調(diào)試。

          ??細(xì)致的文檔&視頻:巨細(xì)致的語雀文檔11W+ 字,共106個文檔,項(xiàng)目視頻還在持續(xù)制作更新中(20個),不怕你學(xué)不會。

          ??付費(fèi)社群優(yōu)質(zhì)的社群里需篩選過濾,學(xué)習(xí)氛圍是很重要的,多跟同輩或前輩聊聊,會少走很多彎路??

          ??清爽干練commit:專屬股東倉庫,一步一步從零復(fù)現(xiàn)austin,每個commit都帶著文檔&視頻學(xué)習(xí)。

          如果想獲取上面的權(quán)益,可以看看??Java項(xiàng)目訓(xùn)練營

          瀏覽 45
          點(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婷婷综合在线网 | 色五月国产 | 乱伦熟女av | 乱伦日韩欧美 | 怕怕怕怕免费视频 |