當前位置: 妍妍網 > 碼農

字節二面:如何設計一個支撐數億使用者的系統?

2024-04-01碼農

來自:網路,侵刪

要設計出一套能支撐幾十億人的系統是很困難的。對於軟體架構師來說,這一直是一項很大的挑戰,但是,從現在開始,看完我的文章,你就會覺得容易很多了。

下面是我在本文中提到的幾個話題:

  • 從最簡單的開始:萬事合一。

  • 可延伸性的藝術:縱向擴充套件,橫向擴充套件。

  • 擴充套件關系型資料庫:主 - 從復制、主 - 主復制、聯合、分片、非規範化和 SQL 調優。

  • 使用哪種資料庫:NoSQL 還是 SQL?

  • 先進概念:緩存、CDN、geoDNS 等。

  • 在這篇文章裏,我不打算談論諸如容錯、可靠性、高可用性等高效能計算的通用術語。

    1. 從頭開始

    在下圖中,我要先設計一個有一些使用者的基本套用。最容易的方式是在一台伺服器上部署整個套用。我們中的大部份人可能都是這樣開始的。

  • 一個網站(包括 API)在 Apache(或 Tomcat)等網路伺服器上執行。

  • 一個 Oracle(或 MySQL)之類的資料庫。

  • 我們在同一台物理機上同時擁有 Web 伺服器和 資料庫伺服器

    但是,當前的架構存在下列缺陷:

  • 如果資料庫出現故障,則系統將失效。

  • 一旦網路伺服器出現故障,則會導致整個系統的癱瘓。

  • 在這種情況下,我們沒有故障轉移和冗余。如果一個伺服器出現故障,所有的都將會失效。

    使用 DNS 伺服器來解析主機名和 IP 地址

    在上圖中,使用者(或客戶端)連線到 DNS 系統,以獲得我們系統所在的伺服器的互聯網協定(IP)地址。一旦獲得 IP 地址,請求就會直接發送到我們的系統。

    每次存取網站時,電腦都會執行 DNS 查詢。

    通常情況下,網域名稱系統(DNS)伺服器是作為托管公司提供的付費服務使用的,並不在你自己的伺服器上執行。

    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)指的是刪除現有伺服器的過程。

    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 調優。

  • 彈性計算。

  • <END>