如何設(shè)計(jì)一個(gè)支撐數(shù)億用戶的系統(tǒng)

要設(shè)計(jì)出一套能支撐幾十億人的系統(tǒng)是很困難的。對(duì)于軟件架構(gòu)師來說,這一直是一項(xiàng)很大的挑戰(zhàn),但是,從現(xiàn)在開始,看完我的文章,你就會(huì)覺得容易很多了。
下面是我在本文中提到的幾個(gè)話題:
從最簡(jiǎn)單的開始:萬事合一。
可擴(kuò)展性的藝術(shù):縱向擴(kuò)展,橫向擴(kuò)展。
擴(kuò)展關(guān)系型數(shù)據(jù)庫(kù):主-從復(fù)制、主-主復(fù)制、聯(lián)合、分片、非規(guī)范化和 SQL 調(diào)優(yōu)。
使用哪種數(shù)據(jù)庫(kù):NoSQL 還是 SQL?
先進(jìn)概念:緩存、CDN、geoDNS 等。
在這篇文章里,我不打算談?wù)撝T如容錯(cuò)、可靠性、高可用性等高性能計(jì)算的通用術(shù)語。
廢話不多說,言歸正傳。
從頭開始
在下圖中,我要先設(shè)計(jì)一個(gè)有一些用戶的基本應(yīng)用。最容易的方式是在一臺(tái)服務(wù)器上部署整個(gè)應(yīng)用。我們中的大部分人可能都是這樣開始的。win10激活密鑰
一個(gè)網(wǎng)站(包括 API)在 Apache(或 Tomcat)等網(wǎng)絡(luò)服務(wù)器上運(yùn)行。
一個(gè) Oracle(或 MySQL)之類的數(shù)據(jù)庫(kù)。

我們?cè)谕慌_(tái)物理機(jī)上同時(shí)擁有 Web 服務(wù)器和數(shù)據(jù)庫(kù)服務(wù)器。
但是,當(dāng)前的架構(gòu)存在下列缺陷:
如果數(shù)據(jù)庫(kù)出現(xiàn)故障,則系統(tǒng)將失效。
一旦網(wǎng)絡(luò)服務(wù)器出現(xiàn)故障,則會(huì)導(dǎo)致整個(gè)系統(tǒng)的癱瘓。
在這種情況下,我們沒有故障轉(zhuǎn)移和冗余。如果一個(gè)服務(wù)器出現(xiàn)故障,所有的都將會(huì)失效。
使用 DNS 服務(wù)器來解析主機(jī)名和 IP 地址
在上圖中,用戶(或客戶端)連接到 DNS 系統(tǒng),以獲得我們系統(tǒng)所在的服務(wù)器的互聯(lián)網(wǎng)協(xié)議(IP)地址。一旦獲得 IP 地址,請(qǐng)求就會(huì)直接發(fā)送到我們的系統(tǒng)。
每次訪問網(wǎng)站時(shí),計(jì)算機(jī)都會(huì)執(zhí)行 DNS 查詢。
通常情況下,域名系統(tǒng)(DNS)服務(wù)器是作為托管公司提供的付費(fèi)服務(wù)使用的,并不在你自己的服務(wù)器上運(yùn)行。
可擴(kuò)展性的藝術(shù)
由于很多原因,我們的系統(tǒng)可能需要進(jìn)行擴(kuò)展,例如數(shù)據(jù)量的增加、工作量的增加(如事務(wù)的數(shù)目),以及用戶的增加。win10企業(yè)版激活
可擴(kuò)展性一般是指添加更多的資源,在不影響用戶體驗(yàn)的情況下處理更多的用戶、客戶機(jī)、數(shù)據(jù)、事務(wù)或請(qǐng)求。
我們必須決定怎樣才能擴(kuò)大這個(gè)系統(tǒng)的規(guī)模。在這種情況下,有以下兩種類型的擴(kuò)展:縱向擴(kuò)展(scale up) 和橫向擴(kuò)展(scale out)。

縱向擴(kuò)展 vs 橫向擴(kuò)展
縱向擴(kuò)展:在現(xiàn)有服務(wù)器上增加更多的內(nèi)存和 CPU
這也被稱為“垂直擴(kuò)展”,是指為了提高系統(tǒng)處理日益增長(zhǎng)的負(fù)載的能力而使系統(tǒng)能夠最大限度地利用資源——例如,通過增加內(nèi)存和 CPU 來增加服務(wù)器的能力。
如果我們運(yùn)行的服務(wù)器有 8G 的內(nèi)存,那么只要更換或者增加硬件,就可以輕松地提升到 32G,甚至 128G。
有很多方法可以進(jìn)行縱向擴(kuò)展,具體如下:
通過在 RAID 陣列中增加更多的硬盤來增加 I/O 容量。
通過切換到固態(tài)硬盤(SSD)來改善 I/O 訪問時(shí)間。
切換到具有更多處理器的服務(wù)器。
通過升級(jí)網(wǎng)絡(luò)接口或安裝額外的網(wǎng)絡(luò)接口來提高網(wǎng)絡(luò)吞吐量。
通過增加內(nèi)存來減少 I/O 操作。
對(duì)于小型系統(tǒng)來說,縱向擴(kuò)展是一個(gè)很好的選擇,可以負(fù)擔(dān)得起硬件升級(jí),但也存在一些嚴(yán)重的限制,具體如下:
“不可能在一臺(tái)服務(wù)器上增加無限的能力”。這主要取決于操作系統(tǒng)和服務(wù)器的內(nèi)存總線寬度。
給系統(tǒng)升級(jí)內(nèi)存時(shí),必須關(guān)掉服務(wù)器,因此,如果系統(tǒng)只有一臺(tái)服務(wù)器,停機(jī)是不可避免的。
強(qiáng)大的機(jī)器往往要比流行的硬件昂貴很多。
縱向擴(kuò)展不僅適用于硬件方面,也適用于軟件方面,例如,它包括優(yōu)化查詢和應(yīng)用程序代碼。
相比之下,縱向減縮(scale down)是指從現(xiàn)有的服務(wù)器中移除現(xiàn)有的資源,如 CPU、內(nèi)存和磁盤。
您需要多臺(tái)服務(wù)器嗎?
當(dāng)用戶數(shù)量不斷增加時(shí),一臺(tái)服務(wù)器將無法滿足需求。我們需要考慮將一臺(tái)單獨(dú)的服務(wù)器分離到多臺(tái)服務(wù)器上。

當(dāng)用戶數(shù)量不斷增加時(shí),一臺(tái)服務(wù)器將無法滿足需求。
采用該架構(gòu)有如下優(yōu)勢(shì):
可對(duì) Web 服務(wù)器進(jìn)行不同于數(shù)據(jù)庫(kù)服務(wù)器的調(diào)優(yōu)。
網(wǎng)絡(luò)服務(wù)器需要更好的 CPU,而數(shù)據(jù)庫(kù)服務(wù)器需要更多的內(nèi)存。
為 Web 層和數(shù)據(jù)層提供單獨(dú)的服務(wù)器,允許它們彼此獨(dú)立地進(jìn)行擴(kuò)展。
橫向擴(kuò)展:添加任意數(shù)量的硬件和軟件實(shí)體
這也被稱為“水平擴(kuò)展”,是指向資源池中添加更多的實(shí)體(如機(jī)器、服務(wù)等)。橫向擴(kuò)展要比縱向擴(kuò)展更難實(shí)現(xiàn),因?yàn)槲覀儽仨氃诮⒁粋€(gè)系統(tǒng)之前就把這個(gè)問題考慮進(jìn)去。
開始時(shí),為了滿足最基本的需求,我們需要更多的服務(wù)器,因此橫向擴(kuò)展最初往往花費(fèi)更多,但是到了最后,我們將獲得更多的收益。我們需要權(quán)衡利弊。
服務(wù)器數(shù)量的增長(zhǎng)意味著更多的資源需要維護(hù)。同時(shí),還必須對(duì)系統(tǒng)代碼進(jìn)行修改,以便實(shí)現(xiàn)在多臺(tái)服務(wù)器間進(jìn)行并行和分配工作。win10激活
與此相反,橫向減縮(Scale in)指的是刪除現(xiàn)有服務(wù)器的過程。
使用負(fù)載均衡器來均衡所有節(jié)點(diǎn)上的流量
負(fù)載均衡器是一種專門的硬件或軟件組件,它可以幫助分散流量到服務(wù)器集群,從而改善系統(tǒng)的響應(yīng)能力和可用性,包括但不限于應(yīng)用程序、網(wǎng)站或數(shù)據(jù)庫(kù)。

使用負(fù)載均衡器來均衡所有節(jié)點(diǎn)之間的流量。
負(fù)載均衡器一般都是在客戶端與服務(wù)器之間,接受傳入的網(wǎng)絡(luò)及應(yīng)用程序的流量,并利用各種算法,將流量分配到多個(gè)后端服務(wù)器。所以,它也可以用于各種場(chǎng)合,比如 Web 服務(wù)器與數(shù)據(jù)庫(kù)服務(wù)器之間,以及客戶端和 Web 服務(wù)器之間。
HAProxy 和 Nginx 是目前比較受歡迎的開源負(fù)載均衡軟件。
負(fù)載均衡器技術(shù)是一種能夠改善系統(tǒng)可用性的容錯(cuò)保護(hù)方法,具體如下:
如果服務(wù)器 1 脫機(jī),則所有的流量將被路由到服務(wù)器 2 和服務(wù)器 3。網(wǎng)站就不會(huì)脫機(jī)。你還需要在服務(wù)器池中添加一個(gè)新的健康服務(wù)器來均衡負(fù)載。
當(dāng)流量快速增長(zhǎng)時(shí),你只需要向網(wǎng)站服務(wù)器池添加更多的服務(wù)器,負(fù)載均衡器將為你路由流量。
負(fù)載均衡器通過不同的策略和任務(wù)分配算法對(duì)負(fù)載進(jìn)行了最優(yōu)分配,具體如下:
循環(huán):在這種情況下,每個(gè)服務(wù)器按順序接收請(qǐng)求,類似于先進(jìn)先出(FIFO)。
最少的連接數(shù):連接數(shù)最少的服務(wù)器將被引導(dǎo)到請(qǐng)求。
最快的響應(yīng)時(shí)間:具有最快響應(yīng)時(shí)間的服務(wù)器(最近或經(jīng)常)將被引導(dǎo)到請(qǐng)求。
加權(quán):較強(qiáng)大的服務(wù)器將比較弱的服務(wù)器收到更多的請(qǐng)求加權(quán)策略。
IP 哈希:在這種情況下,計(jì)算客戶的 IP 地址的哈希值,將請(qǐng)求重定向到服務(wù)器。
在多個(gè)服務(wù)器之間均衡請(qǐng)求的最直接方法是使用一個(gè)硬件設(shè)備。
從共享 IP 中添加和刪除真正的服務(wù)器,將會(huì)立即發(fā)生。
負(fù)載均衡可以根據(jù)需要進(jìn)行。
軟件負(fù)載均衡是硬件負(fù)載均衡器的一個(gè)廉價(jià)替代品。其操作于第 4 層(網(wǎng)絡(luò)層)和第 7 層(應(yīng)用層)。
第 4 層:負(fù)載均衡器使用網(wǎng)絡(luò)層的 TCP 提供的信息。在這一層,它一般不會(huì)查看所請(qǐng)求的內(nèi)容,而是選擇一臺(tái)服務(wù)器。
第 7 層:請(qǐng)求可以根據(jù)查詢字符串、cookies 或我們選擇的任何頭的信息,以及包括源和目標(biāo)地址在內(nèi)的常規(guī)層信息進(jìn)行均衡。
擴(kuò)展關(guān)系數(shù)據(jù)庫(kù)
對(duì)于一個(gè)簡(jiǎn)單的系統(tǒng),我們可以通過 RDBMS,如 Oracle 或者 MySQL 來存儲(chǔ)數(shù)據(jù)項(xiàng)。然而,關(guān)系數(shù)據(jù)庫(kù)系統(tǒng)也存在著一些問題,尤其是在我們需要擴(kuò)展的時(shí)候。
有很多技術(shù)可以擴(kuò)展關(guān)系型數(shù)據(jù)庫(kù):主-從復(fù)制、主-主復(fù)制、聯(lián)合、分片、非規(guī)范化和 SQL 調(diào)優(yōu)。
復(fù)制通常指的是一種技術(shù),可以讓我們?cè)诓煌臋C(jī)器上存儲(chǔ)同一數(shù)據(jù)的多個(gè)副本。
聯(lián)合(或功能分區(qū))將數(shù)據(jù)庫(kù)按功能進(jìn)行劃分。
分片是一種與分區(qū)相關(guān)的數(shù)據(jù)庫(kù)架構(gòu)模式,它將數(shù)據(jù)的不同部分放到不同的服務(wù)器上,不同的用戶將訪問數(shù)據(jù)集的不同部分。
非規(guī)范化試圖以犧牲一些寫入性能為代價(jià)來提高讀取性能,將數(shù)據(jù)寫入多個(gè)表中以避免昂貴的連接。
SQL 調(diào)優(yōu)。
主-從復(fù)制
主-從復(fù)制技術(shù)使一個(gè)數(shù)據(jù)庫(kù)服務(wù)器(主服務(wù)器)的數(shù)據(jù)被復(fù)制到一個(gè)或多個(gè)其他數(shù)據(jù)庫(kù)服務(wù)器(從服務(wù)器),如下圖所示:

對(duì)主服務(wù)器進(jìn)行的所有更新。
客戶端將連接到主服務(wù)器,并更新數(shù)據(jù)。
數(shù)據(jù)隨后會(huì)在從服務(wù)器上進(jìn)行傳輸,直到所有的數(shù)據(jù)在服務(wù)器上都是一致的。
在實(shí)踐中,還是存在一些瓶頸。
如果主服務(wù)器由于某種原因宕機(jī)了,數(shù)據(jù)仍然可以通過從服務(wù)器獲得,但是將無法再進(jìn)行新的寫入。
我們還需要一種新的算法,把一臺(tái)從服務(wù)器提升到主服務(wù)器。
下面是實(shí)現(xiàn)僅一臺(tái)服務(wù)器能處理更新請(qǐng)求的一些解決方案。
同步解決方案:只有當(dāng)所有的服務(wù)器都接受了修改數(shù)據(jù)的事務(wù)(分布式事務(wù))之后,才會(huì)被提交,因此,當(dāng)發(fā)生故障切換時(shí),數(shù)據(jù)不會(huì)丟失。
異步解決方案:提交 → 延遲 → 傳播到集群中的其他服務(wù)器,因此,當(dāng)發(fā)生故障切換時(shí),某些數(shù)據(jù)更新會(huì)丟失。win11激活密鑰
請(qǐng)記住,如果同步解決方案過慢,那就改成異步解決方案。
主-主復(fù)制
每個(gè)數(shù)據(jù)庫(kù)服務(wù)器都可以在其他服務(wù)器被當(dāng)作主服務(wù)器的同時(shí)充當(dāng)主服務(wù)器。在某個(gè)時(shí)間點(diǎn)上,所有的這服務(wù)器都會(huì)同步,以確保它們的數(shù)據(jù)是正確的、最新的。

所有節(jié)點(diǎn)讀寫所有數(shù)據(jù)。
以下是主-主復(fù)制的一些優(yōu)勢(shì):
當(dāng)一臺(tái)主服務(wù)器發(fā)生故障時(shí),其他數(shù)據(jù)庫(kù)服務(wù)器可以正常運(yùn)行,并接替其工作。當(dāng)數(shù)據(jù)庫(kù)服務(wù)器重新上線時(shí),它將利用復(fù)制的方式趕上來。
主服務(wù)器可以位于幾個(gè)物理站點(diǎn),也可以分布在網(wǎng)絡(luò)上。
受限于主服務(wù)器處理更新的能力。
聯(lián)合
聯(lián)合(或功能分區(qū))將數(shù)據(jù)庫(kù)按功能劃分。例如,你可以有三個(gè)數(shù)據(jù)庫(kù):Forum、users 和 products,而不是一個(gè)單一的單體數(shù)據(jù)庫(kù),這樣就能降低對(duì)各個(gè)數(shù)據(jù)庫(kù)的讀寫流量,因此減少了復(fù)制滯后。

聯(lián)合按功能劃分?jǐn)?shù)據(jù)庫(kù)。
數(shù)據(jù)庫(kù)越小,可以容納在內(nèi)存中的數(shù)據(jù)就越多,這反過來會(huì)導(dǎo)致緩存點(diǎn)擊率的增加,這是由于緩存命中的改進(jìn)。因?yàn)椴恍枰獑我坏闹醒胫骺仄餍蛄谢瘜懖僮?,所以你可以進(jìn)行并行寫入,這樣就可以提高吞吐量。
分片
分片(也被稱為數(shù)據(jù)分區(qū)),是一種將大數(shù)據(jù)庫(kù)分成許多小部分的技術(shù),這樣每個(gè)數(shù)據(jù)庫(kù)只能管理數(shù)據(jù)的一個(gè)子集。
在理想情況下,我們有不同的用戶都與不同的數(shù)據(jù)庫(kù)節(jié)點(diǎn)對(duì)話。它有助于提高系統(tǒng)的可管理性、性能、可用性和負(fù)載均衡。
每個(gè)用戶只需要和一個(gè)服務(wù)器對(duì)話,所以可以從該服務(wù)器得到快速的響應(yīng)。
負(fù)載在服務(wù)器之間得到了很好的均衡——例如,如果我們有五個(gè)服務(wù)器,每個(gè)服務(wù)器只需要處理 20% 的負(fù)載。
在實(shí)踐中,有許多不同的技術(shù)可以將一個(gè)數(shù)據(jù)庫(kù)分解成多個(gè)小部分。
水平分區(qū)
這種技術(shù)是將不同的行放到不同的表中。比如,如果我們?cè)谝粋€(gè)表中存儲(chǔ)用戶資料,我們可以決定將 ID 小于 1000 的用戶存儲(chǔ)在一個(gè)表中,而將 ID 大于 1001 小于 2000 的用戶存儲(chǔ)在另一個(gè)表中。

我們將不同的行放入不同的表中。
垂直分區(qū)
在這種情況下,我們對(duì)數(shù)據(jù)進(jìn)行劃分,將與特定特性相關(guān)的表存儲(chǔ)在它們自己的服務(wù)器上。例如,如果我們正在建立一個(gè)類似于 Instagram 的系統(tǒng)——需要存儲(chǔ)與用戶、他們上傳的照片以及他們所關(guān)注的人有關(guān)的數(shù)據(jù)——我們可以決定將用戶的資料信息放在一臺(tái)數(shù)據(jù)庫(kù)服務(wù)器上,好友列表放在另一臺(tái)服務(wù)器上,而照片放在第三臺(tái)服務(wù)器上。

我們將數(shù)據(jù)劃分,存儲(chǔ)與特定特性相關(guān)的表,并將其存儲(chǔ)在各自的服務(wù)器上。
基于目錄的分區(qū)
解決這個(gè)問題的一個(gè)松散耦合的方法,就是創(chuàng)建一個(gè)查詢服務(wù),它了解你當(dāng)前的分區(qū)模式,并保持每個(gè)實(shí)體以及存儲(chǔ)在哪個(gè)數(shù)據(jù)庫(kù)分片的映射關(guān)系。
當(dāng)數(shù)據(jù)存儲(chǔ)可能需要擴(kuò)展到超出單個(gè)存儲(chǔ)節(jié)點(diǎn)的可用資源時(shí),或者通過減少數(shù)據(jù)存儲(chǔ)中的爭(zhēng)用來提高性能時(shí),我們可以使用這種方法。但請(qǐng)記住,分片技術(shù)存在以下一些常見問題:
數(shù)據(jù)庫(kù)連接變得更加昂貴,在某些情況下是不可行的。
分片會(huì)破壞數(shù)據(jù)庫(kù)的引用完整性。
數(shù)據(jù)庫(kù)模式的改變會(huì)變得非常昂貴。
數(shù)據(jù)分布不均勻,而且在分片上有大量負(fù)載。