如何設計一個支援百萬使用者的系統

語言: CN / TW / HK

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

    從單個服務開始   

千里之行,始於足下,讓我們從最簡單的單個服務開始。所有的內容都在一臺伺服器上執行,包括 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》