設計一個支援數百萬使用者的系統是非常有挑戰性的, 這是一個需要不斷調整和優化的過程, 接下來的內容中, 我將構建一個系統, 從單個使用者開始,到最後支援數百萬的使用者。
從單個服務開始
千里之行,始於足下,讓我們從最簡單的單個服務開始。所有的內容都在一臺伺服器上執行,包括 Web 程式, 資料庫,快取 等等, 如下圖
我們看一下它的工作流程。
- 使用者通過域名訪問網站, 比如, api.mysite.com, 通常情況下, 域名解析服務 (DNS) 是由第三方提供的付費服務, 而不是我們的伺服器所提供的。
- 返回 IP 地址給瀏覽器或者移動裝置, 比如, 15.125.23.214。
- 通過 IP 地址, 傳送 Http 請求到我們的 Web 伺服器。
- 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) 的關係,一主多從,主節點支援讀寫操作,而從節點僅支援讀取操作,如下
引入了資料庫複製, 讓我們看看現在網站整體的設計。
- 使用者從 DNS 獲取到 Load balancer 的 IP 地址, 並連線到 Load balancer。
- Http 請求被路由到伺服器1 或者 伺服器2。
- 使用資料庫複製,進行讀寫分離。
現在,web 服務和資料庫都已經做了優化,看上去不錯!
接下來,還需要提升 web 的載入和響應時間,我們可以使用 CDN 快取靜態資源, 包括 js、css、image 等。
Content delivery network (CDN)
CDN 是一個用於交付靜態內容的網路服務,分佈在不同的地理位置。當使用者訪問網站時,距離最近的 CDN 伺服器提供靜態資源,可以很好的改善網站的載入時間。
另外,對於資料庫來說,我們也可以把一些熱點資料新增到快取中,這樣可以減輕資料庫的壓力。
現在,我們的系統加了兩層快取。
- 對於靜態資源,由 CDN 提供而不是 Web 伺服器。
- 通過快取資料來減少對資料庫的訪問。
無狀態 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》