要設計出一套能支撐幾十億人的系統是很困難的。對于軟件架構師來說,這一直是一項很大的挑戰,但是,從現在開始,看完我的文章,你就會覺得容易很多了。
下面是我在本文中提到的幾個話題:
從最簡單的開始:萬事合一。
可擴展性的藝術:縱向擴展,橫向擴展。
擴展關系型數據庫:主 - 從復制、主 - 主復制、聯合、分片、非規范化和 SQL 調優。
使用哪種數據庫:NoSQL 還是 SQL?
先進概念:緩存、CDN、geoDNS 等。
在這篇文章里,我不打算談論諸如容錯、可靠性、高可用性等高性能計算的通用術語。
廢話不多說,言歸正傳。
1 從頭開始
在下圖中,我要先設計一個有一些用戶的基本應用。最容易的方式是在一臺服務器上部署整個應用。我們中的大部分人可能都是這樣開始的。
一個網站(包括 API)在 Apache(或 Tomcat)等網絡服務器上運行。
我們在同一臺物理機上同時擁有 Web 服務器和數據庫服務器。
但是,當前的架構存在下列缺陷:
如果數據庫出現故障,則系統將失效。
一旦網絡服務器出現故障,則會導致整個系統的癱瘓。
在這種情況下,我們沒有故障轉移和冗余。如果一個服務器出現故障,所有的都將會失效。
使用 DNS 服務器來解析主機名和 IP 地址
在上圖中,用戶(或客戶端)連接到 DNS 系統,以獲得我們系統所在的服務器的互聯網協議(IP)地址。一旦獲得 IP 地址,請求就會直接發送到我們的系統。
每次訪問網站時,計算機都會執行 DNS 查詢。
通常情況下,域名系統(DNS)服務器是作為托管公司提供的付費服務使用的,并不在你自己的服務器上運行。
基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
視頻教程:https://doc.iocoder.cn/video/
2 可擴展性的藝術
由于很多原因,我們的系統可能需要進行擴展,例如數據量的增加、工作量的增加(如事務的數目),以及用戶的增加。
可擴展性一般是指添加更多的資源,在不影響用戶體驗的情況下處理更多的用戶、客戶機、數據、事務或請求。
我們必須決定怎樣才能擴大這個系統的規模。在這種情況下,有以下兩種類型的擴展:縱向擴展(scale up) 和橫向擴展(scale out)。
縱向擴展 vs 橫向擴展
縱向擴展:在現有服務器上增加更多的內存和 CPU
這也被稱為“垂直擴展”,是指為了提高系統處理日益增長的負載的能力而使系統能夠最大限度地利用資源——例如,通過增加內存和 CPU 來增加服務器的能力。
如果我們運行的服務器有 8G 的內存,那么只要更換或者增加硬件,就可以輕松地提升到 32G,甚至 128G。
有很多方法可以進行縱向擴展,具體如下:
通過在 RAID 陣列中增加更多的硬盤來增加 I/O 容量。
通過切換到固態硬盤(SSD)來改善 I/O 訪問時間。
切換到具有更多處理器的服務器。
通過升級網絡接口或安裝額外的網絡接口來提高網絡吞吐量。
通過增加內存來減少 I/O 操作。
對于小型系統來說,縱向擴展是一個很好的選擇,可以負擔得起硬件升級,但也存在一些嚴重的限制,具體如下:
“不可能在一臺服務器上增加無限的能力”。這主要取決于操作系統和服務器的內存總線寬度。
給系統升級內存時,必須關掉服務器,因此,如果系統只有一臺服務器,停機是不可避免的。
強大的機器往往要比流行的硬件昂貴很多。
縱向擴展不僅適用于硬件方面,也適用于軟件方面,例如,它包括優化查詢和應用程序代碼。
相比之下,縱向減縮(scale down)是指從現有的服務器中移除現有的資源,如 CPU、內存和磁盤。
您需要多臺服務器嗎?
當用戶數量不斷增加時,一臺服務器將無法滿足需求。我們需要考慮將一臺單獨的服務器分離到多臺服務器上。
當用戶數量不斷增加時,一臺服務器將無法滿足需求。
采用該架構有如下優勢:
可對 Web 服務器進行不同于數據庫服務器的調優。
網絡服務器需要更好的 CPU,而數據庫服務器需要更多的內存。
為 Web 層和數據層提供單獨的服務器,允許它們彼此獨立地進行擴展。
橫向擴展:添加任意數量的硬件和軟件實體
這也被稱為“水平擴展”,是指向資源池中添加更多的實體(如機器、服務等)。橫向擴展要比縱向擴展更難實現,因為我們必須在建立一個系統之前就把這個問題考慮進去。
開始時,為了滿足最基本的需求,我們需要更多的服務器,因此橫向擴展最初往往花費更多,但是到了最后,我們將獲得更多的收益。我們需要權衡利弊。
服務器數量的增長意味著更多的資源需要維護。同時,還必須對系統代碼進行修改,以便實現在多臺服務器間進行并行和分配工作。
與此相反,橫向減縮(Scale in)指的是刪除現有服務器的過程。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://gitee.com/zhijiantianya/yudao-cloud
視頻教程:https://doc.iocoder.cn/video/
3 使用負載均衡器來均衡所有節點上的流量
負載均衡器是一種專門的硬件或軟件組件,它可以幫助分散流量到服務器集群,從而改善系統的響應能力和可用性,包括但不限于應用程序、網站或數據庫。
使用負載均衡器來均衡所有節點之間的流量。
負載均衡器一般都是在客戶端與服務器之間,接受傳入的網絡及應用程序的流量,并利用各種算法,將流量分配到多個后端服務器。所以,它也可以用于各種場合,比如 Web 服務器與數據庫服務器之間,以及客戶端和 Web 服務器之間。
HAProxy 和 Nginx 是目前比較受歡迎的開源負載均衡軟件。
負載均衡器技術是一種能夠改善系統可用性的容錯保護方法,具體如下:
如果服務器 1 脫機,則所有的流量將被路由到服務器 2 和服務器 3。網站就不會脫機。你還需要在服務器池中添加一個新的健康服務器來均衡負載。
當流量快速增長時,你只需要向網站服務器池添加更多的服務器,負載均衡器將為你路由流量。
負載均衡器通過不同的策略和任務分配算法對負載進行了最優分配,具體如下:
循環:在這種情況下,每個服務器按順序接收請求,類似于先進先出(FIFO)。
最少的連接數:連接數最少的服務器將被引導到請求。
最快的響應時間:具有最快響應時間的服務器(最近或經常)將被引導到請求。
加權:較強大的服務器將比較弱的服務器收到更多的請求加權策略。
IP 哈希:在這種情況下,計算客戶的 IP 地址的哈希值,將請求重定向到服務器。
在多個服務器之間均衡請求的最直接方法是使用一個硬件設備。
從共享 IP 中添加和刪除真正的服務器,將會立即發生。
負載均衡可以根據需要進行。
軟件負載均衡是硬件負載均衡器的一個廉價替代品。其操作于第 4 層(網絡層)和第 7 層(應用層)。
第 4 層:負載均衡器使用網絡層的 TCP 提供的信息。在這一層,它一般不會查看所請求的內容,而是選擇一臺服務器。
第 7 層:請求可以根據查詢字符串、cookies 或我們選擇的任何頭的信息,以及包括源和目標地址在內的常規層信息進行均衡。
4 擴展關系數據庫
對于一個簡單的系統,我們可以通過 RDBMS,如 Oracle 或者 MySQL 來存儲數據項。然而,關系數據庫系統也存在著一些問題,尤其是在我們需要擴展的時候。
有很多技術可以擴展關系型數據庫:主 - 從復制、主 - 主復制、聯合、分片、非規范化和 SQL 調優。
復制通常指的是一種技術,可以讓我們在不同的機器上存儲同一數據的多個副本。
聯合(或功能分區)將數據庫按功能進行劃分。
分片是一種與分區相關的數據庫架構模式,它將數據的不同部分放到不同的服務器上,不同的用戶將訪問數據集的不同部分。
非規范化試圖以犧牲一些寫入性能為代價來提高讀取性能,將數據寫入多個表中以避免昂貴的連接。
SQL 調優。
主 - 從復制
主 - 從復制技術使一個數據庫服務器(主服務器)的數據被復制到一個或多個其他數據庫服務器(從服務器),如下圖所示:
對主服務器進行的所有更新。
客戶端將連接到主服務器,并更新數據。
數據隨后會在從服務器上進行傳輸,直到所有的數據在服務器上都是一致的。
在實踐中,還是存在一些瓶頸。
如果主服務器由于某種原因宕機了,數據仍然可以通過從服務器獲得,但是將無法再進行新的寫入。
我們還需要一種新的算法,把一臺從服務器提升到主服務器。
下面是實現僅一臺服務器能處理更新請求的一些解決方案。
同步解決方案:只有當所有的服務器都接受了修改數據的事務(分布式事務)之后,才會被提交,因此,當發生故障切換時,數據不會丟失。
異步解決方案:提交 → 延遲 → 傳播到集群中的其他服務器,因此,當發生故障切換時,某些數據更新會丟失。
請記住,如果同步解決方案過慢,那就改成異步解決方案。
主 - 主復制
每個數據庫服務器都可以在其他服務器被當作主服務器的同時充當主服務器。在某個時間點上,所有的這服務器都會同步,以確保它們的數據是正確的、最新的。
所有節點讀寫所有數據。
以下是主 - 主復制的一些優勢:
當一臺主服務器發生故障時,其他數據庫服務器可以正常運行,并接替其工作。當數據庫服務器重新上線時,它將利用復制的方式趕上來。
主服務器可以位于幾個物理站點,也可以分布在網絡上。
受限于主服務器處理更新的能力。
聯合
聯合(或功能分區)將數據庫按功能劃分。例如,你可以有三個數據庫:Forum、users 和 products,而不是一個單一的單體數據庫,這樣就能降低對各個數據庫的讀寫流量,因此減少了復制滯后。
聯合按功能劃分數據庫。
數據庫越小,可以容納在內存中的數據就越多,這反過來會導致緩存點擊率的增加,這是由于緩存命中的改進。因為不需要單一的中央主控器序列化寫操作,所以你可以進行并行寫入,這樣就可以提高吞吐量。
分片
分片(也被稱為數據分區),是一種將大數據庫分成許多小部分的技術,這樣每個數據庫只能管理數據的一個子集。
在理想情況下,我們有不同的用戶都與不同的數據庫節點對話。它有助于提高系統的可管理性、性能、可用性和負載均衡。
每個用戶只需要和一個服務器對話,所以可以從該服務器得到快速的響應。
負載在服務器之間得到了很好的均衡——例如,如果我們有五個服務器,每個服務器只需要處理 20% 的負載。
在實踐中,有許多不同的技術可以將一個數據庫分解成多個小部分。
水平分區
這種技術是將不同的行放到不同的表中。比如,如果我們在一個表中存儲用戶資料,我們可以決定將 ID 小于 1000 的用戶存儲在一個表中,而將 ID 大于 1001 小于 2000 的用戶存儲在另一個表中。
我們將不同的行放入不同的表中。
垂直分區
在這種情況下,我們對數據進行劃分,將與特定特性相關的表存儲在它們自己的服務器上。例如,如果我們正在建立一個類似于 Instagram 的系統——需要存儲與用戶、他們上傳的照片以及他們所關注的人有關的數據——我們可以決定將用戶的資料信息放在一臺數據庫服務器上,好友列表放在另一臺服務器上,而照片放在第三臺服務器上。
我們將數據劃分,存儲與特定特性相關的表,并將其存儲在各自的服務器上。
基于目錄的分區
解決這個問題的一個松散耦合的方法,就是創建一個查詢服務,它了解你當前的分區模式,并保持每個實體以及存儲在哪個數據庫分片的映射關系。
當數據存儲可能需要擴展到超出單個存儲節點的可用資源時,或者通過減少數據存儲中的爭用來提高性能時,我們可以使用這種方法。但請記住,分片技術存在以下一些常見問題:
數據庫連接變得更加昂貴,在某些情況下是不可行的。
分片會破壞數據庫的引用完整性。
數據庫模式的改變會變得非常昂貴。
數據分布不均勻,而且在分片上有大量負載。
非規范化
非規范化的目的是提高讀取性能,但卻要犧牲一定的寫入性能。為了避免昂貴的連接,可以將數據的冗余副本寫入到多個表中。
一旦數據通過聯合和分片等技術變得分散,管理跨數據中心的連接將會進一步增加復雜性。非規范化可以避免需要如此復雜的連接。
在大多數系統中,讀取操作的次數遠遠多于寫入操作,大約是 100:1,甚至是 1000:1。導致讀取復雜數據庫連接可能會非常昂貴,而且會耗費很多時間在磁盤上。
有些 RDBMS,像 PostgreSQL 和 Oracle 都支持物化視圖,它們可以處理存儲冗余數據,并使冗余副本保持一致。
Facebook 的 Ryan Mack 在其出色的文章《建立時間表:利用非規范化的力量擴大規模來保存你的生活故事》(Building Timeline: Scaling up to hold your life story)中分享了很多時間表自身的實現故事。
5 使用哪個數據庫?
在數據庫領域,主要有兩種類型的解決方案。SQL 與 NoSQL。它們的構建方式、存儲信息的類型以及存儲方式都有所不同。
SQL
關系型數據庫以行和列的形式存儲數據。每一行包含一個實體的所有信息,每一列包含所有獨立的數據點。
目前最受歡迎的關系型數據庫是 MySQL、Oracle、MS SQL Server、SQLite、Postgres 和 MariaDB。
NoSQL
它也被稱為非關系型數據庫。這些數據庫一般分為五大類別:Key-Value、Graph、Column、Document 和 Blob 存儲。
鍵值存儲
數據被存儲在一個鍵值對的數組中。key(鍵)是一個與 value(值)相連的屬性名稱。
知名的鍵值存儲有 Redis、Voldemort 和 Dynamo。
文檔數據庫
在這些數據庫中,數據被存儲在文檔中(而不是表格中的行和列),這些文檔被分組在集合中。每個文檔都可能是截然不同的結構。
文檔數據庫包括 CouchDB 和 MongoDB。
寬列式數據庫
在列式數據庫中,我們沒有“表”,而是有列族,它們是行的容器。與關系型數據庫不同,我們不必事先了解所有的列,也不必要求每一行的列數目都相同。
列式數據庫最適合分析大型數據集,著名的有 Cassandra 和 HBase。
圖數據庫
這些數據庫用于存儲數據,其關系最好用圖來表示。數據被保存在帶有節點(實體)、屬性(關于實體的信息)和線(實體之間的連接)的圖結構中。
圖數據庫的例子包括 Neo4J 和 InfiniteGraph。
Blob 數據庫
Blob 更像是文件的鍵 / 值存儲,可以通過 Amazon S3、Windows Azure Blob Storage、Google Cloud Storage、Rackspace Cloud Files 或 OpenStack Swift 等 API 訪問。
如何選擇要使用的數據庫?
當涉及數據庫技術時,沒有放之四海而皆準的解決方案。這就是為什么許多企業同時依賴 SQL 和 NoSQL 數據庫來滿足不同的需求。
請看下面我畫的思維導圖!
使用哪個數據庫?
6 橫向擴展 Web 層
我們已經擴展了數據層,現在我們也需要擴展 Web 層。為了做到這一點,我們需要將用戶會話的數據(狀態)移出 Web 層,將其存儲在數據庫中,如關系型數據庫或 NoSQL。這也被稱為無狀態架構。
無狀態系統很簡單。
不要使用有狀態架構。
由于狀態的實現會限制可擴展性。降低可用性和提高成本,所以我們需要盡可能地選擇無狀態架構。
在上面的場景中,由于可以為最優的請求處理選擇任意服務器,因此負載均衡器能夠可以達到最高的效率。
7 先進概念
緩存
負載均衡能夠幫助你橫向擴展越來越多的服務器,但緩存可以讓你更好地利用現有的資源,從而更快速地向下一個請求提供數據。
如果數據不在緩存中,就從數據庫中獲取,然后保存到緩存中,再從緩存中讀取。
我們可以在服務器中添加緩存,避免從服務器中直接讀取網頁或數據,從而降低了服務器的響應時間及負載。這使得我們的應用程序更加易于擴展。
緩存可以被用于許多層,例如數據庫層、Web 服務器層和網絡層。
內容分發網絡 (CDN )
CDN 服務器保存內容(如圖像、網頁等)的緩存副本,并從最近的位置提供服務。
CDN 的使用可以提高用戶的頁面加載時間,因為數據是在離它最近的地方檢索的。這也有助于提高內容的可用性,因為它被存儲在多個地點。
使用 CDN 改善了用戶的頁面加載時間,因為數據是在最接近它的地方被檢索到的。
CDN 服務器向我們的網絡服務器發出請求,以驗證被緩存的內容,并在需要時更新它們。被緩存的內容通常是靜態的,如 HTML 頁面、圖像、JavaScript 文件、CSS 文件等。
走向全球
隨著你的應用程序在全球范圍內推廣,你將會在全球范圍內建立和運營數據中心,使你的產品每天 24 小時、每周 7 天保持運行。收到的請求將被路由到基于 GeoDNS 的“最佳”數據中心。
當你的應用程序走向全球時……
GeoDNS 是一項 DNS 服務,它可以將一個域名按照用戶所在的位置解析為 IP 地址。來自亞洲的客戶端可以得到與來自歐洲客戶端的不同 IP 地址。
把它整合在一起
通過迭代應用所有這些技術,我們可以輕松地將系統擴展到 1 億多用戶,如無狀態架構、應用負載均衡器、盡可能多地使用緩存數據、支持多個數據中心、在 CDN 上托管靜態資產、通過分片擴展你的數據層,諸如此類。
擴展是一個迭代的過程。
8 后面會討論哪些話題?
有很多方法可以提高可擴展性和高性能,如下所示:
分片和復制技術相結合。
長輪詢 vs Websockets vs 服務器發送事件。
索引和代理。
SQL 調優。
彈性計算。
審核編輯 :李倩
-
SQL
+關注
關注
1文章
767瀏覽量
44170 -
可靠性
+關注
關注
4文章
265瀏覽量
26760
原文標題:設計了一個支撐 數億 用戶的系統
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論