「系統設計」設計一個支援百萬用戶的系統

  • 2022 年 5 月 19 日
  • 筆記

設計一個支援數百萬用戶的系統是非常有挑戰性的, 這是一個需要不斷調整和優化的過程, 接下來的內容中, 我將構建一個系統, 從單個用戶開始,到最後支援數百萬的用戶。

從單個服務開始

千里之行,始於足下,讓我們從最簡單的單個服務開始。所有的內容都在一台伺服器上運行,包括 Web 程式, 資料庫,快取 等等, 如下圖

我們看一下它的工作流程。

  1. 用戶通過域名訪問網站, 比如, api.mysite.com, 通常情況下, 域名解析服務 (DNS) 是由第三方提供的付費服務, 而不是我們的伺服器所提供的。
  2. 返回 IP 地址給瀏覽器或者移動設備, 比如, 15.125.23.214。
  3. 通過 IP 地址, 發送 Http 請求到我們的 Web 伺服器。
  4. Web 伺服器返回 html 或者 json 內容, 瀏覽器進行渲染。

分離資料庫

隨著用戶量的增長,此時一台伺服器已經獨木難支,我們需要兩台伺服器, 一個用於 Web 服務, 一個用於資料庫。

應該選擇哪種資料庫?

您可以選擇關係型資料庫和非關係型資料庫,那它們都有什麼特點呢?

關係型資料庫也稱為關係型資料庫管理系統 (RDBMS) 或
SQL 資料庫,最常見的有 MySQL、Oracle 、PostgreSQL、Sql Server 等,可以通過 SQL 進行跨表查詢。

而非關係型資料庫也稱為 NoSQL 資料庫,最常見的有 Redis、 CouchDB,Neo4j、Cassandra、HBase、Amazon DynamoDB 等。它們分為四類:鍵值(Key-Value)存儲資料庫、列存儲資料庫、文檔型資料庫、圖(Graph)資料庫。

對於大多數開發人員來說,通常會選擇關係型資料庫。而非關係型資料庫更適合以下幾種情況:

  • 應用程式需要超低延遲。

  • 數據是非結構化的,或者沒有任何關係數據。

  • 只需要序列化和反序列化數據(JSON、XML、YAML 等)。

  • 需要存儲海量數據。

垂直縮放 、 水平縮放

垂直縮放,又稱為 “縱向擴展” (scale up), 是指升級伺服器資源, 比如 CPU, RAM 等。而水平縮放又稱為 “橫向擴展” (scale out), 是指添加伺服器到資源池中。

當流量比較少的時候, 選擇縱向擴展就足夠了,因為它足夠簡單,不過也有很大的局限性。

  • 縱向擴展有硬體限制, 無限制的升級 CPU 和記憶體是不現實的。
  • 縱向擴展沒有高可用,如果一台伺服器出現故障,網站或者應用就會直接崩潰。

而流量較大的時候,橫向擴展是更好的選擇,多個伺服器也保證了高可用。如何讓這些伺服器更好的提供服務,我們還需要做負載均衡。

Load balancer

負載均衡器可以平均分配流量給每台伺服器,如下

我們水平擴展了 Web 服務,並引入了負載均衡器,來應對快速增長的網站流量, 並提供了高可用的服務。

現在,Web 層看上去不錯,但是不要忘了,當前的設計只有一個資料庫,並不支援故障轉移和冗餘。而資料庫複製是一種常見的技術,可以解決這個問題。

Database replication

資料庫複製是把數據複製、傳輸到另外一個資料庫,最終形成一個分散式資料庫。用戶可以訪問到相同的資訊,從而提高一致性、可靠性和性能。

通常它們之間是主/從(master/slave) 的關係,一主多從,主節點支援讀寫操作,而從節點僅支援讀取操作,如下

引入了資料庫複製, 讓我們看看現在網站整體的設計。

  1. 用戶從 DNS 獲取到 Load balancer 的 IP 地址, 並連接到 Load balancer。
  2. Http 請求被路由到伺服器1 或者 伺服器2。
  3. 使用資料庫複製,進行讀寫分離。

現在,web 服務和資料庫都已經做了優化,看上去不錯!

接下來,還需要提升 web 的載入和響應時間,我們可以使用 CDN 快取靜態資源, 包括 js、css、image 等。

Content delivery network (CDN)

CDN 是一個用於交付靜態內容的網路服務,分布在不同的地理位置。當用戶訪問網站時,距離最近的 CDN 伺服器提供靜態資源,可以很好的改善網站的載入時間。

另外,對於資料庫來說,我們也可以把一些熱點數據添加到快取中,這樣可以減輕資料庫的壓力。

現在,我們的系統加了兩層快取。

  1. 對於靜態資源,由 CDN 提供而不是 Web 伺服器。
  2. 通過快取數據來減少對資料庫的訪問。

無狀態 Web 層

現在我們的 Web 應用是有狀態的服務,什麼意思呢?假如用戶在 Server 1 進行了登陸, 那後續也只能在 Server1 請求資源,因為只有 Server1 才擁有用戶的會話資訊,每個 Web 服務的狀態都是獨立的、隔離的。

我們需要把這些狀態移出 Web層,通常單獨保存在關係型資料庫或者 NoSQL, 這樣 Web 層就變成了無狀態的。

這樣做有什麼好處呢?在無狀態的架構中,來自用戶的 Http 請求可以發送到任何 Web 伺服器,而狀態資訊統一保存在單獨的共享存儲中。無狀態系統更簡單、更容易擴展。

數據中心

您的網站受到越來越多人的關注,用戶也迅速發展,並擴展到全球。

如何為各個地區的用戶都提供滿意的服務?您可以在不同的地區設置多個數據中心。

如下圖,我們分別在東、西兩個地區配置了單獨的數據中心, DC1、DC2。

看上去不錯!但是如何引導用戶去不同的數據中心呢?答案是:DNS, 是的,眾所周知,DNS 可以把我們網站的域名解析為 IP 地址,而使用 GeoDNS, 可以根據用戶請求所在的位置,解析為不同的地區的 IP 地址。把用戶引導到離他最近的數據中心,來達到加速的目的。

另外,如果某個數據中心發生重大事故,導致集群故障,我們可以把所有的流量都引導到健康的數據中心,這種架構就是我們常說的 “異地多活”。

Message queue

當需要進行解耦時,引入消息隊列通常是優先考慮的, 它支援非同步通訊,當您有耗時的任務需要處理時,可以通過生產者把消息發送到消息隊列,Web 服務可以儘快的響應用戶的請求,而消費者可以非同步地去處理這些耗時任務。

日誌、指標、自動化

當網站的流量越來越大時,就必須要引入監控工具了。

日誌:監控錯誤日誌很重要,它可以幫助您發現系統問題。 您可以把日誌統一發送到日誌中心,這樣便於分析和查看。

指標:收集各種各樣的指標,可以幫助我們更好的理解業務和系統。

  • 系統指標::CPU、記憶體、磁碟 I/O,資料庫等等。
  • 業務指標:每日用戶、活躍度等等。

自動化,當系統變得龐大且複雜時,我們需要引入自動化工具,CI/CD 很重要,自動化構建、測試、部署可以極大的提高開發人員的生產力。

現在,我們的系統引入了消息隊列,以及一些監控和自動化工具。

Database Sharding

資料庫的數據每天都在大步的增長,我們的資料庫已經不堪重負了,是時候擴展資料庫了,資料庫分片是個很好的方案。

在下面的示例中,我們使用了哈希函數來進行分片, 根據不同的 user_id, 把數據平均分配到 4個資料庫中。

現在,我們看一下資料庫的數據。

使用資料庫分片的方案時,有一個要考慮的重要因素是分片鍵(sharding key), 或者叫分區鍵,比如上面的 user_id,因為可以通過 sharding key 找到相對應的資料庫,另外,我們要選擇一個可以均勻分布數據的鍵。

看起來不錯!不過這種方案也給系統帶來的複雜性和新的挑戰,當數據越來越多,增加了資料庫節點之後,我們需要重新進行數據分片。比如 useri_id % 5, 此時,為了保證哈希函數的正確路由,我們需要移動資料庫大量的數據。

我們可以使用一致性哈希技術,來解決上面的問題,重新分片後,只需要移動一小部分數據即可,當然一致性哈希本文就不做詳細的介紹了。

讓我們看看最終的系統設計。

總結

構建一個健壯的架構系統,其實是一個迭代的過程,為了支援數百萬的用戶的架構,我們需要做到以下幾點:

  • 保證 Web 層無狀態
  • 儘可能的快取數據
  • 異地多活,配置多個數據中心
  • 使用分片擴展資料庫
  • 監控系統並使用自動化工具

希望對您有用!

譯:等天黑

作者:Alex Xu

來源:《System Design Interview》