大型網際網路系統架構是如何設計的?

lihong發表於2019-04-27

大型網際網路系統架構是如何設計的?

接下來,我們將看看高階的權衡和取捨:

  • 效能可擴充套件性
  • 延遲吞吐量
  • 可用性一致性

記住每個方面都面臨取捨和權衡

然後,我們將深入更具體的主題,如 DNS、CDN 和負載均衡器。

1. 效能與可擴充套件性

如果服務效能的增長與資源的增加是成比例的,服務就是可擴充套件的。通常,提高效能意味著服務於更多的工作單元,另一方面,當資料集增長時,同樣也可以處理更大的工作單位。1

另一個角度來看待效能與可擴充套件性:

  • 如果你的系統有效能問題,對於單個使用者來說是緩慢的。
  • 如果你的系統有可擴充套件性問題,單個使用者較快但在高負載下會變慢。

2. 延遲與吞吐量

延遲是執行操作或運算結果所花費的時間。

吞吐量是單位時間內(執行)此類操作或運算的數量。

通常,你應該以可接受級延遲最大化吞吐量為目標。

3. 可用性與一致性

CAP 理論

大型網際網路系統架構是如何設計的?

在一個分散式計算系統中,只能同時滿足下列的兩點:

  • 一致性 ─ 每次訪問都能獲得最新資料但可能會收到錯誤響應
  • 可用性 ─ 每次訪問都能收到非錯響應,但不保證獲取到最新資料
  • 分割槽容錯性 ─ 在任意分割槽網路故障的情況下系統仍能繼續執行

網路並不可靠,所以你應要支援分割槽容錯性,並需要在軟體可用性和一致性間做出取捨。

CP ─ 一致性和分割槽容錯性

等待分割槽節點的響應可能會導致延時錯誤。如果你的業務需求需要原子讀寫,CP 是一個不錯的選擇。

AP ─ 可用性與分割槽容錯性

響應節點上可用資料的最近版本可能並不是最新的。當分割槽解析完後,寫入(操作)可能需要一些時間來傳播。

如果業務需求允許最終一致性,或當有外部故障時要求系統繼續執行,AP 是一個不錯的選擇。

4. 一致性模式

有同一份資料的多份副本,我們面臨著怎樣同步它們的選擇,以便讓客戶端有一致的顯示資料。回想 CAP 理論中的一致性定義 ─ 每次訪問都能獲得最新資料但可能會收到錯誤響應

弱一致性

在寫入之後,訪問可能看到,也可能看不到(寫入資料)。盡力優化之讓其能訪問最新資料。

這種方式可以 memcached 等系統中看到。弱一致性在 VoIP,視訊聊天和實時多人遊戲等真實用例中表現不錯。打個比方,如果你在通話中丟失訊號幾秒鐘時間,當重新連線時你是聽不到這幾秒鐘所說的話的。

最終一致性

在寫入後,訪問最終能看到寫入資料(通常在數毫秒內)。資料被非同步複製。

DNS 和 email 等系統使用的是此種方式。最終一致性在高可用性系統中效果不錯。

強一致性

在寫入後,訪問立即可見。資料被同步複製。

檔案系統和關係型資料庫(RDBMS)中使用的是此種方式。強一致性在需要記錄的系統中運作良好。

5. 可用性模式

有兩種支援高可用性的模式: 故障切換(fail-over)複製(replication)

故障切換

工作到備用切換(Active-passive)

關於工作到備用的故障切換流程是,工作伺服器傳送週期訊號給待機中的備用伺服器。如果週期訊號中斷,備用伺服器切換成工作伺服器的 IP 地址並恢復服務。

當機時間取決於備用伺服器處於“熱”待機狀態還是需要從“冷”待機狀態進行啟動。只有工作伺服器處理流量。

工作到備用的故障切換也被稱為主從切換。

雙工作切換(Active-active)

在雙工作切換中,雙方都在管控流量,在它們之間分散負載。

如果是外網伺服器,DNS 將需要對兩方都瞭解。如果是內網伺服器,應用程式邏輯將需要對兩方都瞭解。

雙工作切換也可以稱為主主切換。

缺陷:故障切換

  • 故障切換需要新增額外硬體並增加複雜性。
  • 如果新寫入資料在能被複制到備用系統之前,工作系統出現了故障,則有可能會丟失資料。

複製

主 ─ 從複製和主 ─ 主複製

這個主題進一步探討了資料庫部分:

  • 主 ─ 從複製
  • 主 ─ 主複製

6. 域名系統

大型網際網路系統架構是如何設計的?

域名系統是把 www.example.com 等域名轉換成 IP 地址。

域名系統是分層次的,一些 DNS 伺服器位於頂層。當查詢(域名) IP 時,路由或 ISP 提供連線 DNS 伺服器的資訊。較底層的 DNS 伺服器快取對映,它可能會因為 DNS 傳播延時而失效。DNS 結果可以快取在瀏覽器或作業系統中一段時間,時間長短取決於存活時間 TTL。

  • NS 記錄(域名服務) ─ 指定解析域名或子域名的 DNS 伺服器。
  • MX 記錄(郵件交換) ─ 指定接收資訊的郵件伺服器。
  • A 記錄(地址) ─ 指定域名對應的 IP 地址記錄。
  • CNAME(規範) ─ 一個域名對映到另一個域名或 CNAME 記錄( example.com 指向 www.example.com )或對映到一個 A 記錄。

CloudFlare 和 Route 53 等平臺提供管理 DNS 的功能。某些 DNS 服務通過集中方式來路由流量:

  • 加權輪詢排程
    • 防止流量進入維護中的伺服器
    • 在不同大小叢集間負載均衡
    • A/B 測試
  • 基於延遲路由
  • 基於地理位置路由

缺陷:DNS

  • 雖說快取可以減輕 DNS 延遲,但連線 DNS 伺服器還是帶來了輕微的延遲。
  • 雖然它們通常由政府,網路服務提供商和大公司管理,但 DNS 服務管理仍可能是複雜的。
  • DNS 服務最近遭受 DDoS 攻擊,阻止不知道 Twtter IP 地址的使用者訪問 Twiiter。

7. 內容分發網路(CDN)

大型網際網路系統架構是如何設計的?

內容分發網路(CDN)是一個全球性的代理伺服器分散式網路,它從靠近使用者的位置提供內容。通常,HTML/CSS/JS,圖片和視訊等靜態內容由 CDN 提供,雖然亞馬遜 CloudFront 等也支援動態內容。CDN 的 DNS 解析會告知客戶端連線哪臺伺服器。

將內容儲存在 CDN 上可以從兩個方面來提供效能:

  • 從靠近使用者的資料中心提供資源
  • 通過 CDN 你的伺服器不必真的處理請求

CDN 推送(push)

當你伺服器上內容發生變動時,推送 CDN 接受新內容。直接推送給 CDN 並重寫 URL 地址以指向你的內容的 CDN 地址。你可以配置內容到期時間及何時更新。內容只有在更改或新增是才推送,流量最小化,但儲存最大化。

CDN 拉取(pull)

CDN 拉取是當第一個使用者請求該資源時,從伺服器上拉取資源。你將內容留在自己的伺服器上並重寫 URL 指向 CDN 地址。直到內容被快取在 CDN 上為止,這樣請求只會更慢,

存活時間(TTL)決定快取多久時間。CDN 拉取方式最小化 CDN 上的儲存空間,但如果過期檔案並在實際更改之前被拉取,則會導致冗餘的流量。

高流量站點使用 CDN 拉取效果不錯,因為只有最近請求的內容儲存在 CDN 中,流量才能更平衡地分散。

缺陷:CDN

  • CDN 成本可能因流量而異,可能在權衡之後你將不會使用 CDN。
  • 如果在 TTL 過期之前更新內容,CDN 快取內容可能會過時。
  • CDN 需要更改靜態內容的 URL 地址以指向 CDN。

8. 負載均衡器

大型網際網路系統架構是如何設計的?

負載均衡器將傳入的請求分發到應用伺服器和資料庫等計算資源。無論哪種情況,負載均衡器將從計算資源來的響應返回給恰當的客戶端。負載均衡器的效用在於:

  • 防止請求進入不好的伺服器
  • 防止資源過載
  • 幫助消除單一的故障點

負載均衡器可以通過硬體(昂貴)或 HAProxy 等軟體來實現。 增加的好處包括:

  • SSL 終結 ─ 解密傳入的請求並加密伺服器響應,這樣的話後端伺服器就不必再執行這些潛在高消耗運算了。
    • 不需要再每臺伺服器上安裝 X.509 證照。
  • Session 留存 ─ 如果 Web 應用程式不追蹤會話,發出 cookie 並將特定客戶端的請求路由到同一例項。

通常會設定採用工作 ─ 備用 或 雙工作 模式的多個負載均衡器,以免發生故障。

負載均衡器能基於多種方式來路由流量:

  • 隨機
  • 最少負載
  • Session/cookie
  • 輪詢排程或加權輪詢排程演算法
  • 四層負載均衡
  • 七層負載均衡

四層負載均衡

四層負載均衡根據監看傳輸層的資訊來決定如何分發請求。通常,這會涉及來源,目標 IP 地址和請求頭中的埠,但不包括資料包(報文)內容。四層負載均衡執行網路地址轉換(NAT)來向上遊伺服器轉發網路資料包。

七層負載均衡器

七層負載均衡器根據監控應用層來決定怎樣分發請求。這會涉及請求頭的內容,訊息和 cookie。七層負載均衡器終結網路流量,讀取訊息,做出負載均衡判定,然後傳送給特定伺服器。比如,一個七層負載均衡器能直接將視訊流量連線到託管視訊的伺服器,同時將更敏感的使用者賬單流量引導到安全性更強的伺服器。

以損失靈活性為代價,四層負載均衡比七層負載均衡花費更少時間和計算資源,雖然這對現代商用硬體的效能影響甚微。

水平擴充套件

負載均衡器還能幫助水平擴充套件,提高效能和可用性。使用商業硬體的價效比更高,並且比在單臺硬體上垂直擴充套件更貴的硬體具有更高的可用性。相比招聘特定企業系統人才,招聘商業硬體方面的人才更加容易。

缺陷:水平擴充套件

  • 水平擴充套件引入了複雜度並涉及伺服器複製
    • 伺服器應該是無狀態的:它們也不該包含像 session 或資料圖片等與使用者關聯的資料。
    • session 可以集中儲存在資料庫或持久化快取(Redis、Memcached)的資料儲存區中。
  • 快取和資料庫等下游伺服器需要隨著上游伺服器進行擴充套件,以處理更多的併發連線。

缺陷:負載均衡器

  • 如果沒有足夠的資源配置或配置錯誤,負載均衡器會變成一個效能瓶頸。
  • 引入負載均衡器以幫助消除單點故障但導致了額外的複雜性。
  • 單個負載均衡器會導致單點故障,但配置多個負載均衡器會進一步增加複雜性。

9. 反向代理(web 伺服器)

大型網際網路系統架構是如何設計的?

反向代理是一種可以集中地呼叫內部服務,並提供統一介面給公共客戶的 web 伺服器。來自客戶端的請求先被反向代理伺服器轉發到可響應請求的伺服器,然後代理再把伺服器的響應結果返回給客戶端。

帶來的好處包括:

  • 增加安全性 - 隱藏後端伺服器的資訊,遮蔽黑名單中的 IP,限制每個客戶端的連線數。
  • 提高可擴充套件性和靈活性 - 客戶端只能看到反向代理伺服器的 IP,這使你可以增減伺服器或者修改它們的配置。
  • 本地終結 SSL 會話 - 解密傳入請求,加密伺服器響應,這樣後端伺服器就不必完成這些潛在的高成本的操作。
    • 免除了在每個伺服器上安裝 X.509 證照的需要
  • 壓縮 - 壓縮伺服器響應
  • 快取 - 直接返回命中的快取結果
  • 靜態內容 - 直接提供靜態內容
    • HTML/CSS/JS
    • 圖片
    • 視訊
    • 等等

負載均衡器與反向代理

  • 當你有多個伺服器時,部署負載均衡器非常有用。通常,負載均衡器將流量路由給一組功能相同的伺服器上。
  • 即使只有一臺 web 伺服器或者應用伺服器時,反向代理也有用,可以參考上一節介紹的好處。
  • NGINX 和 HAProxy 等解決方案可以同時支援第七層反向代理和負載均衡。

不利之處:反向代理

  • 引入反向代理會增加系統的複雜度。
  • 單獨一個反向代理伺服器仍可能發生單點故障,配置多臺反向代理伺服器(如故障轉移)會進一步增加複雜度。

10. 應用層

大型網際網路系統架構是如何設計的?

將 Web 服務層與應用層(也被稱作平臺層)分離,可以獨立縮放和配置這兩層。新增新的 API 只需要新增應用伺服器,而不必新增額外的 web 伺服器。

單一職責原則提倡小型的,自治的服務共同合作。小團隊通過提供小型的服務,可以更激進地計劃增長。

應用層中的工作程式也有可以實現非同步化。

微服務

與此討論相關的話題是 微服務,可以被描述為一系列可以獨立部署的小型的,模組化服務。每個服務執行在一個獨立的執行緒中,通過明確定義的輕量級機制通訊,共同實現業務目標。1

例如,Pinterest 可能有這些微服務: 使用者資料、關注者、Feed 流、搜尋、照片上傳等。

服務發現

像 Consul,Etcd 和 Zookeeper 這樣的系統可以通過追蹤註冊名、地址、埠等資訊來幫助服務互相發現對方。Health checks 可以幫助確認服務的完整性和是否經常使用一個 HTTP 路徑。Consul 和 Etcd 都有一個內建的 key-value 儲存 用來儲存配置資訊和其他的共享資訊。

不利之處:應用層

  • 新增由多個鬆耦合服務組成的應用層,從架構、運營、流程等層面來講將非常不同(相對於單體系統)。
  • 微服務會增加部署和運營的複雜度。

11. 資料庫

大型網際網路系統架構是如何設計的?

關係型資料庫管理系統(RDBMS)

像 SQL 這樣的關係型資料庫是一系列以表的形式組織的資料項集合。

校對注:這裡作者 SQL 可能指的是 MySQL

ACID 用來描述關係型資料庫事務的特性。

  • 原子性 - 每個事務內部所有操作要麼全部完成,要麼全部不完成。
  • 一致性 - 任何事務都使資料庫從一個有效的狀態轉換到另一個有效狀態。
  • 隔離性 - 併發執行事務的結果與順序執行事務的結果相同。
  • 永續性 - 事務提交後,對系統的影響是永久的。

關係型資料庫擴充套件包括許多技術:主從複製主主複製聯合分片非規範化SQL 調優

大型網際網路系統架構是如何設計的?

主從複製

主庫同時負責讀取和寫入操作,並複製寫入到一個或多個從庫中,從庫只負責讀操作。樹狀形式的從庫再將寫入複製到更多的從庫中去。如果主庫離線,系統可以以只讀模式執行,直到某個從庫被提升為主庫或有新的主庫出現。

不利之處:主從複製

  • 將從庫提升為主庫需要額外的邏輯。
  • 參考不利之處:複製中,主從複製和主主複製共同的問題。

大型網際網路系統架構是如何設計的?

主主複製

兩個主庫都負責讀操作和寫操作,寫入操作時互相協調。如果其中一個主庫掛機,系統可以繼續讀取和寫入。

不利之處: 主主複製

  • 你需要新增負載均衡器或者在應用邏輯中做改動,來確定寫入哪一個資料庫。
  • 多數主-主系統要麼不能保證一致性(違反 ACID),要麼因為同步產生了寫入延遲。
  • 隨著更多寫入節點的加入和延遲的提高,如何解決衝突顯得越發重要。
  • 參考不利之處:複製中,主從複製和主主複製共同的問題。

不利之處:複製

  • 如果主庫在將新寫入的資料複製到其他節點前掛掉,則有資料丟失的可能。
  • 寫入會被重放到負責讀取操作的副本。副本可能因為過多寫操作阻塞住,導致讀取功能異常。
  • 讀取從庫越多,需要複製的寫入資料就越多,導致更嚴重的複製延遲。
  • 在某些資料庫系統中,寫入主庫的操作可以用多個執行緒並行寫入,但讀取副本只支援單執行緒順序地寫入。
  • 複製意味著更多的硬體和額外的複雜度。

聯合

大型網際網路系統架構是如何設計的?

聯合(或按功能劃分)將資料庫按對應功能分割。例如,你可以有三個資料庫:論壇使用者產品,而不僅是一個單體資料庫,從而減少每個資料庫的讀取和寫入流量,減少複製延遲。較小的資料庫意味著更多適合放入記憶體的資料,進而意味著更高的快取命中機率。沒有隻能序列寫入的中心化主庫,你可以並行寫入,提高負載能力。

不利之處:聯合

  • 如果你的資料庫模式需要大量的功能和資料表,聯合的效率並不好。
  • 你需要更新應用程式的邏輯來確定要讀取和寫入哪個資料庫。
  • 用 server link 從兩個庫聯結資料更復雜。
  • 聯合需要更多的硬體和額外的複雜度。

分片

大型網際網路系統架構是如何設計的?

分片將資料分配在不同的資料庫上,使得每個資料庫僅管理整個資料集的一個子集。以使用者資料庫為例,隨著使用者數量的增加,越來越多的分片會被新增到叢集中。

類似聯合的優點,分片可以減少讀取和寫入流量,減少複製並提高快取命中率。也減少了索引,通常意味著查詢更快,效能更好。如果一個分片出問題,其他的仍能執行,你可以使用某種形式的冗餘來防止資料丟失。類似聯合,沒有隻能序列寫入的中心化主庫,你可以並行寫入,提高負載能力。

常見的做法是使用者姓氏的首字母或者使用者的地理位置來分隔使用者表。

不利之處:分片

  • 你需要修改應用程式的邏輯來實現分片,這會帶來複雜的 SQL 查詢。
  • 分片不合理可能導致資料負載不均衡。例如,被頻繁訪問的使用者資料會導致其所在分片的負載相對其他分片高。
    • 再平衡會引入額外的複雜度。基於一致性雜湊的分片演算法可以減少這種情況。
  • 聯結多個分片的資料操作更復雜。
  • 分片需要更多的硬體和額外的複雜度。

非規範化

非規範化試圖以寫入效能為代價來換取讀取效能。在多個表中冗餘資料副本,以避免高成本的聯結操作。一些關係型資料庫,比如 PostgreSQL 和 Oracle 支援物化檢視,可以處理冗餘資訊儲存和保證冗餘副本一致。

當資料使用諸如聯合和分片等技術被分割,進一步提高了處理跨資料中心的聯結操作複雜度。非規範化可以規避這種複雜的聯結操作。

在多數系統中,讀取操作的頻率遠高於寫入操作,比例可達到 100:1,甚至 1000:1。需要複雜的資料庫聯結的讀取操作成本非常高,在磁碟操作上消耗了大量時間。

不利之處:非規範化

  • 資料會冗餘。
  • 約束可以幫助冗餘的資訊副本保持同步,但這樣會增加資料庫設計的複雜度。
  • 非規範化的資料庫在高寫入負載下效能可能比規範化的資料庫差。

SQL 調優

SQL 調優是一個範圍很廣的話題,有很多相關的書可以作為參考。

利用基準測試效能分析來模擬和發現系統瓶頸很重要。

  • 基準測試 - 用 ab 等工具模擬高負載情況。
  • 效能分析 - 通過啟用如慢查詢日誌等工具來輔助追蹤效能問題。

基準測試和效能分析可能會指引你到以下優化方案。

改進模式

  • 為了實現快速訪問,MySQL 在磁碟上用連續的塊儲存資料。
  • 使用 CHAR 型別儲存固定長度的欄位,不要用 VARCHAR
    • CHAR 在快速、隨機訪問時效率很高。如果使用 VARCHAR,如果你想讀取下一個字串,不得不先讀取到當前字串的末尾。
  • 使用 TEXT 型別儲存大塊的文字,例如部落格正文。TEXT 還允許布林搜尋。使用 TEXT 欄位需要在磁碟上儲存一個用於定位文字塊的指標。
  • 使用 INT 型別儲存高達 2^32 或 40 億的較大數字。
  • 使用 DECIMAL 型別儲存貨幣可以避免浮點數表示錯誤。
  • 避免使用 BLOBS 儲存物件,儲存存放物件的位置。
  • VARCHAR(255) 是以 8 位數字儲存的最大字元數,在某些關係型資料庫中,最大限度地利用位元組。
  • 在適用場景中設定 NOT NULL 約束來提高搜尋效能。

使用正確的索引

  • 你正查詢(SELECTGROUP BYORDER BYJOIN)的列如果用了索引會更快。
  • 索引通常表示為自平衡的 B 樹,可以保持資料有序,並允許在對數時間內進行搜尋,順序訪問,插入,刪除操作。
  • 設定索引,會將資料存在記憶體中,佔用了更多記憶體空間。
  • 寫入操作會變慢,因為索引需要被更新。
  • 載入大量資料時,禁用索引再載入資料,然後重建索引,這樣也許會更快。

避免高成本的聯結操作

  • 有效能需要,可以進行非規範化。

分割資料表

  • 將熱點資料拆分到單獨的資料表中,可以有助於快取。

調優查詢快取

  • 在某些情況下,查詢快取可能會導致效能問題。

NoSQL

NoSQL 是鍵-值資料庫文件型資料庫列型資料庫圖資料庫的統稱。資料庫是非規範化的,表聯結大多在應用程式程式碼中完成。大多數 NoSQL 無法實現真正符合 ACID 的事務,支援最終一致。

BASE 通常被用於描述 NoSQL 資料庫的特性。相比 CAP 理論,BASE 強調可用性超過一致性。

  • 基本可用 - 系統保證可用性。
  • 軟狀態 - 即使沒有輸入,系統狀態也可能隨著時間變化。
  • 最終一致性 - 經過一段時間之後,系統最終會變一致,因為系統在此期間沒有收到任何輸入。

除了在 SQL 還是 NoSQL 之間做選擇,瞭解哪種型別的 NoSQL 資料庫最適合你的用例也是非常有幫助的。我們將在下一節中快速瞭解下 鍵-值儲存文件型儲存列型儲存圖儲存資料庫。

鍵-值儲存

抽象模型:雜湊表

鍵-值儲存通常可以實現 O(1) 時間讀寫,用記憶體或 SSD 儲存資料。資料儲存可以按字典順序維護鍵,從而實現鍵的高效檢索。鍵-值儲存可以用於儲存後設資料。

鍵-值儲存效能很高,通常用於儲存簡單資料模型或頻繁修改的資料,如存放在記憶體中的快取。鍵-值儲存提供的操作有限,如果需要更多操作,複雜度將轉嫁到應用程式層面。

鍵-值儲存是如文件儲存,在某些情況下,甚至是圖儲存等更復雜的儲存系統的基礎。

文件型別儲存

抽象模型:將文件作為值的鍵-值儲存

文件型別儲存以文件(XML、JSON、二進位制檔案等)為中心,文件儲存了指定物件的全部資訊。文件儲存根據文件自身的內部結構提供 API 或查詢語句來實現查詢。請注意,許多鍵-值儲存資料庫有用值儲存後設資料的特性,這也模糊了這兩種儲存型別的界限。

基於底層實現,文件可以根據集合、標籤、後設資料或者資料夾組織。儘管不同文件可以被組織在一起或者分成一組,但相互之間可能具有完全不同的欄位。

MongoDB 和 CouchDB 等一些文件型別儲存還提供了類似 SQL 語言的查詢語句來實現複雜查詢。DynamoDB 同時支援鍵-值儲存和文件型別儲存。

文件型別儲存具備高度的靈活性,常用於處理偶爾變化的資料。

列型儲存

大型網際網路系統架構是如何設計的?

抽象模型:巢狀的 ColumnFamily<RowKey, Columns<ColKey, Value, Timestamp>> 對映

型別儲存的基本資料單元是列(名/值對)。列可以在列族(類似於 SQL 的資料表)中被分組。超級列族再分組普通列族。你可以使用行鍵獨立訪問每一列,具有相同行鍵值的列組成一行。每個值都包含版本的時間戳用於解決版本衝突。

Google 釋出了第一個列型儲存資料庫 Bigtable,它影響了 Hadoop 生態系統中活躍的開源資料庫 HBase 和 Facebook 的 Cassandra。像 BigTable,HBase 和 Cassandra 這樣的儲存系統將鍵以字母順序儲存,可以高效地讀取鍵列。

列型儲存具備高可用性和高可擴充套件性。通常被用於大資料相關儲存。

圖資料庫

大型網際網路系統架構是如何設計的?

抽象模型: 圖

在圖資料庫中,一個節點對應一條記錄,一個弧對應兩個節點之間的關係。圖資料庫被優化用於表示外來鍵繁多的複雜關係或多對多關係。

圖資料庫為儲存複雜關係的資料模型,如社交網路,提供了很高的效能。它們相對較新,尚未廣泛應用,查詢開發工具或者資源相對較難。許多圖只能通過 REST API 訪問。

SQL 還是 NoSQL

大型網際網路系統架構是如何設計的?

選取 SQL 的原因:

  • 結構化資料
  • 嚴格的模式
  • 關係型資料
  • 需要複雜的聯結操作
  • 事務
  • 清晰的擴充套件模式
  • 既有資源更豐富:開發者、社群、程式碼庫、工具等
  • 通過索引進行查詢非常快

選取 NoSQL 的原因:

  • 半結構化資料
  • 動態或靈活的模式
  • 非關係型資料
  • 不需要複雜的聯結操作
  • 儲存 TB (甚至 PB)級別的資料
  • 高資料密集的工作負載
  • IOPS 高吞吐量

適合 NoSQL 的示例資料:

  • 埋點資料和日誌資料
  • 排行榜或者得分資料
  • 臨時資料,如購物車
  • 頻繁訪問的(“熱”)表
  • 後設資料/查詢表

12. 快取

大型網際網路系統架構是如何設計的?

快取可以提高頁面載入速度,並可以減少伺服器和資料庫的負載。在這個模型中,分發器先檢視請求之前是否被響應過,如果有則將之前的結果直接返回,來省掉真正的處理。

資料庫分片均勻分佈的讀取是最好的。但是熱門資料會讓讀取分佈不均勻,這樣就會造成瓶頸,如果在資料庫前加個快取,就會抹平不均勻的負載和突發流量對資料庫的影響。

客戶端快取

快取可以位於客戶端(作業系統或者瀏覽器),服務端或者不同的快取層。

CDN 快取

CDN 也被視為一種快取。

Web 伺服器快取

反向代理和快取(比如 Varnish)可以直接提供靜態和動態內容。Web 伺服器同樣也可以快取請求,返回相應結果而不必連線應用伺服器。

資料庫快取

資料庫的預設配置中通常包含快取級別,針對一般用例進行了優化。調整配置,在不同情況下使用不同的模式可以進一步提高效能。

應用快取

基於記憶體的快取比如 Memcached 和 Redis 是應用程式和資料儲存之間的一種鍵值儲存。由於資料儲存在 RAM 中,它比儲存在磁碟上的典型資料庫要快多了。RAM 比磁碟限制更多,所以例如 least recently used (LRU) 的快取無效演算法可以將「熱門資料」放在 RAM 中,而對一些比較「冷門」的資料不做處理。

Redis 有下列附加功能:

  • 永續性選項
  • 內建資料結構比如有序集合和列表

有多個快取級別,分為兩大類:資料庫查詢物件

  • 行級別
  • 查詢級別
  • 完整的可序列化物件
  • 完全渲染的 HTML

一般來說,你應該儘量避免基於檔案的快取,因為這使得複製和自動縮放很困難。

資料庫查詢級別的快取

當你查詢資料庫的時候,將查詢語句的雜湊值與查詢結果儲存到快取中。這種方法會遇到以下問題:

  • 很難用複雜的查詢刪除已快取結果。
  • 如果一條資料比如表中某條資料的一項被改變,則需要刪除所有可能包含已更改項的快取結果。

物件級別的快取

將您的資料視為物件,就像對待你的應用程式碼一樣。讓應用程式將資料從資料庫中組合到類例項或資料結構中:

  • 如果物件的基礎資料已經更改了,那麼從快取中刪掉這個物件。
  • 允許非同步處理:workers 通過使用最新的快取物件來組裝物件。

建議快取的內容:

  • 使用者會話
  • 完全渲染的 Web 頁面
  • 活動流
  • 使用者圖資料

何時更新快取

由於你只能在快取中儲存有限的資料,所以你需要選擇一個適用於你用例的快取更新策略。

快取模式

大型網際網路系統架構是如何設計的?

應用從儲存器讀寫。快取不和儲存器直接互動,應用執行以下操作:

  • 在快取中查詢記錄,如果所需資料不在快取中
  • 從資料庫中載入所需內容
  • 將查詢到的結果儲存到快取中
  • 返回所需內容
def get_user(self, user_id):
    user = cache.get("user.{0}", user_id)
    if user is None:
        user = db.query("SELECT * FROM users WHERE user_id = {0}", user_id)
        if user is not None:
            key = "user.{0}".format(user_id)
            cache.set(key, json.dumps(user))
    return user
複製程式碼

Memcached 通常用這種方式使用。

新增到快取中的資料讀取速度很快。快取模式也稱為延遲載入。只快取所請求的資料,這避免了沒有被請求的資料佔滿了快取空間。

快取的缺點:

  • 請求的資料如果不在快取中就需要經過三個步驟來獲取資料,這會導致明顯的延遲。
  • 如果資料庫中的資料更新了會導致快取中的資料過時。這個問題需要通過設定 � TTL 強制更新快取或者直寫模式來緩解這種情況。
  • 當一個節點出現故障的時候,它將會被一個新的節點替代,這增加了延遲的時間。

直寫模式

大型網際網路系統架構是如何設計的?

應用使用快取作為主要的資料儲存,將資料讀寫到快取中,而快取負責從資料庫中讀寫資料。

  • 應用向快取中新增/更新資料
  • 快取同步地寫入資料儲存
  • 返回所需內容

應用程式碼:

set_user(12345, {"foo":"bar"})
複製程式碼

快取程式碼:

def set_user(user_id, values):
    user = db.query("UPDATE Users WHERE id = {0}", user_id, values)
    cache.set(user_id, user)
複製程式碼

由於存寫操作所以直寫模式整體是一種很慢的操作,但是讀取剛寫入的資料很快。相比讀取資料,使用者通常比較能接受更新資料時速度較慢。快取中的資料不會過時。

直寫模式的缺點:

  • 由於故障或者縮放而建立的新的節點,新的節點不會快取,直到資料庫更新為止。快取應用直寫模式可以緩解這個問題。
  • 寫入的大多數資料可能永遠都不會被讀取,用 TTL 可以最小化這種情況的出現。

回寫模式

大型網際網路系統架構是如何設計的?

在回寫模式中,應用執行以下操作:

  • 在快取中增加或者更新條目
  • 非同步寫入資料,提高寫入效能。

回寫模式的缺點:

  • 快取可能在其內容成功儲存之前丟失資料。
  • 執行直寫模式比快取或者回寫模式更復雜。

重新整理

大型網際網路系統架構是如何設計的?

你可以將快取配置成在到期之前自動重新整理最近訪問過的內容。

如果快取可以準確預測將來可能請求哪些資料,那麼重新整理可能會導致延遲與讀取時間的降低。

重新整理的缺點:

  • 不能準確預測到未來需要用到的資料可能會導致效能不如不使用重新整理。

快取的缺點:

  • 需要保持快取和真實資料來源之間的一致性,比如資料庫根據快取無效。
  • 需要改變應用程式比如增加 Redis 或者 memcached。
  • 無效快取是個難題,什麼時候更新快取是與之相關的複雜問題。

13. 非同步

大型網際網路系統架構是如何設計的?

非同步工作流有助於減少那些原本順序執行的請求時間。它們可以通過提前進行一些耗時的工作來幫助減少請求時間,比如定期彙總資料。

訊息佇列

訊息佇列接收,保留和傳遞訊息。如果按順序執行操作太慢的話,你可以使用有以下工作流的訊息佇列:

  • 應用程式將作業釋出到佇列,然後通知使用者作業狀態
  • 一個 worker 從佇列中取出該作業,對其進行處理,然後顯示該作業完成

不去阻塞使用者操作,作業在後臺處理。在此期間,客戶端可能會進行一些處理使得看上去像是任務已經完成了。例如,如果要傳送一條推文,推文可能會馬上出現在你的時間線上,但是可能需要一些時間才能將你的推文推送到你的所有關注者那裡去。

Redis 是一個令人滿意的簡單的訊息代理,但是訊息有可能會丟失。

RabbitMQ 很受歡迎但是要求你適應「AMQP」協議並且管理你自己的節點。

Amazon SQS 是被託管的,但可能具有高延遲,並且訊息可能會被傳送兩次。

任務佇列

任務佇列接收任務及其相關資料,執行它們,然後傳遞其結果。 它們可以支援排程,並可用於在後臺執行計算密集型作業。

Celery 支援排程,主要是用 Python 開發的。

背壓

如果佇列開始明顯增長,那麼佇列大小可能會超過記憶體大小,導致快取記憶體未命中,磁碟讀取,甚至效能更慢。背壓可以通過限制佇列大小來幫助我們,從而為佇列中的作業保持高吞吐率和良好的響應時間。一旦佇列填滿,客戶端將得到伺服器忙或者 HTTP 503 狀態碼,以便稍後重試。客戶端可以在稍後時間重試該請求,也許是指數退避。

非同步的缺點:

  • 簡單的計算和實時工作流等用例可能更適用於同步操作,因為引入佇列可能會增加延遲和複雜性。

14. 通訊

大型網際網路系統架構是如何設計的?

超文字傳輸協議(HTTP)

HTTP 是一種在客戶端和伺服器之間編碼和傳輸資料的方法。它是一個請求/響應協議:客戶端和服務端針對相關內容和完成狀態資訊的請求和響應。HTTP 是獨立的,允許請求和響應流經許多執行負載均衡,快取,加密和壓縮的中間路由器和伺服器。

一個基本的 HTTP 請求由一個動詞(方法)和一個資源(端點)組成。 以下是常見的 HTTP 動詞:

動詞描述*冪等安全性可快取
GET讀取資源YesYesYes
POST建立資源或觸發處理資料的程式NoNoYes,如果回應包含重新整理資訊
PUT建立或替換資源YesNoNo
PATCH部分更新資源NoNoYes,如果回應包含重新整理資訊
DELETE刪除資源YesNoNo

多次執行不會產生不同的結果

HTTP 是依賴於較低階協議(如 TCPUDP)的應用層協議。

傳輸控制協議(TCP)

大型網際網路系統架構是如何設計的?

TCP 是通過 IP 網路的面向連線的協議。 使用握手建立和斷開連線。 傳送的所有資料包保證以原始順序到達目的地,用以下措施保證資料包不被損壞:

  • 每個資料包的序列號和校驗碼。
  • 確認包和自動重傳

如果傳送者沒有收到正確的響應,它將重新傳送資料包。如果多次超時,連線就會斷開。TCP 實行流量控制和擁塞控制。這些確保措施會導致延遲,而且通常導致傳輸效率比 UDP 低。

為了確保高吞吐量,Web 伺服器可以保持大量的 TCP 連線,從而導致高記憶體使用。在 Web 伺服器執行緒間擁有大量開放連線可能開銷巨大,消耗資源過多,也就是說,一個 memcached 伺服器。連線池 可以幫助除了在適用的情況下切換到 UDP。

TCP 對於需要高可靠性但時間緊迫的應用程式很有用。比如包括 Web 伺服器,資料庫資訊,SMTP,FTP 和 SSH。

以下情況使用 TCP 代替 UDP:

  • 你需要資料完好無損。
  • 你想對網路吞吐量自動進行最佳評估。

使用者資料包協議(UDP)

大型網際網路系統架構是如何設計的?

UDP 是無連線的。資料包(類似於資料包)只在資料包級別有保證。資料包可能會無序的到達目的地,也有可能會遺失。UDP 不支援擁塞控制。雖然不如 TCP 那樣有保證,但 UDP 通常效率更高。

UDP 可以通過廣播將資料包傳送至子網內的所有裝置。這對 DHCP 很有用,因為子網內的裝置還沒有分配 IP 地址,而 IP 對於 TCP 是必須的。

UDP 可靠性更低但適合用在網路電話、視訊聊天,流媒體和實時多人遊戲上。

以下情況使用 UDP 代替 TCP:

  • 你需要低延遲
  • 相對於資料丟失更糟的是資料延遲
  • 你想實現自己的錯誤校正方法

遠端過程呼叫協議(RPC)

大型網際網路系統架構是如何設計的?

在 RPC 中,客戶端會去呼叫另一個地址空間(通常是一個遠端伺服器)裡的方法。呼叫程式碼看起來就像是呼叫的是一個本地方法,客戶端和伺服器互動的具體過程被抽象。遠端呼叫相對於本地呼叫一般較慢而且可靠性更差,因此區分兩者是有幫助的。熱門的 RPC 框架包括 Protobuf、Thrift 和 Avro。

RPC 是一個“請求-響應”協議:

  • 客戶端程式 ── 呼叫客戶端存根程式。就像呼叫本地方法一樣,引數會被壓入棧中。
  • 客戶端 stub 程式 ── 將請求過程的 id 和引數打包進請求資訊中。
  • 客戶端通訊模組 ── 將資訊從客戶端傳送至服務端。
  • 服務端通訊模組 ── 將接受的包傳給服務端存根程式。
  • 服務端 stub 程式 ── 將結果解包,依據過程 id 呼叫服務端方法並將引數傳遞過去。

RPC 呼叫示例:

GET /someoperation?data=anId

POST /anotheroperation
{
  "data":"anId";
  "anotherdata": "another value"
}
複製程式碼

RPC 專注於暴露方法。RPC 通常用於處理內部通訊的效能問題,這樣你可以手動處理本地呼叫以更好的適應你的情況。

當以下情況時選擇本地庫(也就是 SDK):

  • 你知道你的目標平臺。
  • 你想控制如何訪問你的“邏輯”。
  • 你想對發生在你的庫中的錯誤進行控制。
  • 效能和終端使用者體驗是你最關心的事。

遵循 REST 的 HTTP API 往往更適用於公共 API。

缺點:RPC

  • RPC 客戶端與服務實現捆綁地很緊密。
  • 一個新的 API 必須在每一個操作或者用例中定義。
  • RPC 很難除錯。
  • 你可能沒辦法很方便的去修改現有的技術。舉個例子,如果你希望在 Squid 這樣的快取伺服器上確保 RPC 被正確快取的話可能需要一些額外的努力了。

表述性狀態轉移(REST)

REST 是一種強制的客戶端/服務端架構設計模型,客戶端基於服務端管理的一系列資源操作。服務端提供修改或獲取資源的介面。所有的通訊必須是無狀態和可快取的。

RESTful 介面有四條規則:

  • 標誌資源(HTTP 裡的 URI) ── 無論什麼操作都使用同一個 URI。
  • 表示的改變(HTTP 的動作) ── 使用動作, headers 和 body。
  • 可自我描述的錯誤資訊(HTTP 中的 status code) ── 使用狀態碼,不要重新造輪子。
  • HATEOAS(HTTP 中的 HTML 介面) ── 你的 web 伺服器應該能夠通過瀏覽器訪問。

REST 請求的例子:

GET /someresources/anId

PUT /someresources/anId
{"anotherdata": "another value"}
複製程式碼

REST 關注於暴露資料。它減少了客戶端/服務端的耦合程度,經常用於公共 HTTP API 介面設計。REST 使用更通常與規範化的方法來通過 URI 暴露資源,通過 header 來表述並通過 GET、POST、PUT、DELETE 和 PATCH 這些動作來進行操作。因為無狀態的特性,REST 易於橫向擴充套件和隔離。

缺點:REST

  • 由於 REST 將重點放在暴露資料,所以當資源不是自然組織的或者結構複雜的時候它可能無法很好的適應。舉個例子,返回過去一小時中與特定事件集匹配的更新記錄這種操作就很難表示為路徑。使用 REST,可能會使用 URI 路徑,查詢引數和可能的請求體來實現。
  • REST 一般依賴幾個動作(GET、POST、PUT、DELETE 和 PATCH),但有時候僅僅這些沒法滿足你的需要。舉個例子,將過期的文件移動到歸檔資料夾裡去,這樣的操作可能沒法簡單的用上面這幾個 verbs 表達。
  • 為了渲染單個頁面,獲取被巢狀在層級結構中的複雜資源需要客戶端,伺服器之間多次往返通訊。例如,獲取部落格內容及其關聯評論。對於使用不確定網路環境的移動應用來說,這些多次往返通訊是非常麻煩的。
  • 隨著時間的推移,更多的欄位可能會被新增到 API 響應中,較舊的客戶端將會接收到所有新的資料欄位,即使是那些它們不需要的欄位,結果它會增加負載大小並引起更大的延遲。

RPC 與 REST 比較

操作RPCREST
註冊POST /signupPOST /persons
登出POST /resign
{
"personid": "1234"
}
DELETE /persons/1234
讀取使用者資訊GET /readPerson?personid=1234GET /persons/1234
讀取使用者物品列表GET /readUsersItemsList?personid=1234GET /persons/1234/items
向使用者物品列表新增一項POST /addItemToUsersItemsList
{
"personid": "1234";
"itemid": "456"
}
POST /persons/1234/items
{
"itemid": "456"
}
更新一個物品POST /modifyItem
{
"itemid": "456";
"key": "value"
}
PUT /items/456
{
"key": "value"
}
刪除一個物品POST /removeItem
{
"itemid": "456"
}
DELETE /items/456

15. 安全

這一部分需要更多內容。一起來吧!

安全是一個寬泛的話題。除非你有相當的經驗、安全方面背景或者正在申請的職位要求安全知識,你不需要了解安全基礎知識以外的內容:

  • 在運輸和等待過程中加密
  • 對所有的使用者輸入和從使用者那裡發來的引數進行處理以防止 XSS 和 SQL 注入。
  • 使用引數化的查詢來防止 SQL 注入。
  • 使用最小許可權原則。

16. 附錄

一些時候你會被要求做出保守估計。比如,你可能需要估計從磁碟中生成 100 張圖片的縮圖需要的時間或者一個資料結構需要多少的記憶體。2 的次方表每個開發者都需要知道的一些時間資料(譯註:OSChina 上有這篇文章的譯文)都是一些很方便的參考資料。

2 的次方表

Power           Exact Value         Approx Value        Bytes
---------------------------------------------------------------
7                             128
8                             256
10                           1024   1 thousand           1 KB
16                         65,536                       64 KB
20                      1,048,576   1 million            1 MB
30                  1,073,741,824   1 billion            1 GB
32                  4,294,967,296                        4 GB
40              1,099,511,627,776   1 trillion           1 TB
複製程式碼

每個程式設計師都應該知道的延遲數

Latency Comparison Numbers
--------------------------
L1 cache reference                           0.5 ns
Branch mispredict                            5   ns
L2 cache reference                           7   ns                      14x L1 cache
Mutex lock/unlock                          100   ns
Main memory reference                      100   ns                      20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy            10,000   ns       10 us
Send 1 KB bytes over 1 Gbps network     10,000   ns       10 us
Read 4 KB randomly from SSD*           150,000   ns      150 us          ~1GB/sec SSD
Read 1 MB sequentially from memory     250,000   ns      250 us
Round trip within same datacenter      500,000   ns      500 us
Read 1 MB sequentially from SSD*     1,000,000   ns    1,000 us    1 ms  ~1GB/sec SSD, 4X memory
Disk seek                           10,000,000   ns   10,000 us   10 ms  20x datacenter roundtrip
Read 1 MB sequentially from 1 Gbps  10,000,000   ns   10,000 us   10 ms  40x memory, 10X SSD
Read 1 MB sequentially from disk    30,000,000   ns   30,000 us   30 ms 120x memory, 30X SSD
Send packet CA->Netherlands->CA    150,000,000   ns  150,000 us  150 ms

Notes
-----
1 ns = 10^-9 seconds
1 us = 10^-6 seconds = 1,000 ns
1 ms = 10^-3 seconds = 1,000 us = 1,000,000 ns
複製程式碼

基於上述數字的指標:

  • 從磁碟以 30 MB/s 的速度順序讀取
  • 以 100 MB/s 從 1 Gbps 的乙太網順序讀取
  • 從 SSD 以 1 GB/s 的速度讀取
  • 以 4 GB/s 的速度從主存讀取
  • 每秒能繞地球 6-7 圈
  • 資料中心內每秒有 2,000 次往返

延遲數視覺化

大型網際網路系統架構是如何設計的?

相關文章