設計一個支持數百萬用戶的系統是非常有挑戰性的, 這是一個需要不斷調整和優化的過程, 接下來的內容中, 我將構建一個系統, 從單個用戶開始,到最后支持數百萬的用戶。
? ? 從單個服務開始? ?
千里之行,始于足下,讓我們從最簡單的單個服務開始。所有的內容都在一臺服務器上運行,包括 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》