說說大型網站可伸縮性架構的設計原理

可伸縮性架構指的是:不改變網站的軟硬件設計,只通過改變部署的服務器數量就可以擴大或縮小網站的服務處理能力。

大型網站中的 “大型”,可以表現在以下幾個方面:

  • 用戶方面 - 大量的用戶與大量訪問(Facebook 有超過 20 億的用戶數)
  • 功能方面 - 功能龐雜,產品眾多(騰訊有超過 1700 種產品)
  • 技術方面 - 部署大量的服務器(Google 有近 200 萬臺服務器)

大型網站都是從小型網站(一臺廉價的 PC 服務器)開啟自己的大型系統演化之路的。在這一過程中,最重要的技術手段就是使用服務器集群,通過不斷地向集群中添加服務器來增強整個集群的處理能力。只要在技術上能夠向集群中加入的服務器數量與集群的處理能力成線性關系,那么就可以利用這一手段不斷提升自己的網站規模,這就是系統的伸縮性架構。

演化過程從總體上來說是漸進式的,網站的規模和服務器的規模總是在不斷地擴大,即總是在 “伸”。但也有可能因為運營的需要(促銷活動),在某個短時間內,網站的訪問量和交易規模突然爆發式增長,然后又回歸正常狀態。這就需要網站的技術架構具有極好的伸縮性——在活動期間向服務器集群中加入更多的服務器以滿足用戶的訪問,活動結束后再將這些服務器下線,以節約成本。

1 設計伸縮性架構

網站架構發展史其實就是一部不斷向網站添加服務器的歷史。

伸縮性架構分為兩種:

  • 根據功能進行物理分離 - 不同服務器部署不同的服務。
  • 單一功能通過集群實現 - 集群內的多臺服務器部署相同的服務,提供相同的功能。

1.1 根據功能進行物理分離

網站發展早期,總是從現有的服務器中分離出部分功能與服務的:

根據功能進行物理分離

每次分離都會有更多的服務器加入,這些新增的服務器被用于處理某種特定的服務。這種伸縮性手段可以用于網站發展的任何階段,它可以分為兩種情況:縱向分離與橫向分離。

縱向分離(分層后分離):是將業務流程上的不同層進行分離部署。

縱向分離

橫向分離(業務分割后的分離):把不同的業務模塊分離部署。

橫向分離

橫向分離的粒度可以很小,比如一個關鍵網頁可以獨立部署為一個服務,專門部署。

1.2 單一功能集群部署

在 “根據功能進行物理分離” 的模式下,隨著網站訪問量的增長,即使是分離到最小粒度的獨立部署也可能無法滿足業務規模的需要。這時就必須使用集群,即把相同的服務部署在多臺服務器構成的集群上,實現整體對外服務。

當一頭牛拉不動車時,不是去尋找一頭更強壯的牛,而是用兩頭牛來拉車。

一個服務的集群規模,需要同時考慮可用性、性能以及關聯服務集群的影響。


集群伸縮性的類別有這幾種,后面我們會一一探討哦O(∩_∩)O~

集群伸縮性的類別

2 伸縮性設計之應用服務器集群

把應用服務器設計為無狀態模式,這樣通過負載均衡服務器,就可以把用戶請求轉發到不同的應用服務器上咯:

負載均衡實現的應用集群

負載均衡服務器能夠感知或配置集群的服務器數量,這樣就可以向新上線的服務器分發請求,并停止已下線的服務器,這樣就實現了應用服務器集群的伸縮性。

負載均衡技術,不僅可以實現伸縮性,還能改善網站的可用性,所以是網站技術的殺手锏之一哦O(∩_∩)O~

實現負載均衡的基礎技術有以下這些。

2.1 HTTP 重定向

利用 HTTP 重定向協議實現負載均衡:

HTTP 重定向協議實現負載均衡

這里的負載均衡服務器只是一臺普通的應用服務器,它會根據用戶的 HTTP 請求計算出一臺真實的 Web 服務器地址,然后把地址寫入 HTTP 的重定向響應(狀態碼 302)返回給用戶瀏覽器。

這種實現技術的優點是簡單。缺點是瀏覽器需要請求兩次服務器才能訪問一次訪問,性能較差;而且重定向服務器自身的處理能力有可能成為瓶頸,所以整個集群的伸縮性規模有限。而且使用 HTTP 302 響應碼,有可能會被 SEO 判定為作弊,導致搜索排名被降低的后果。因此在實踐中很少使用。

2.2 DNS 域名解析

DNS 域名解析實現負載均衡

DNS 的每一次域名解析請求,都會根據負載均衡算法計算出一個不同的 IP 地址并返回,這樣就可以實現負載均衡啦O(∩_∩)O~

DNS 域名解析實現負載均衡的方案優點是:把工作轉交給了 DNS,省掉了管理維護負載均衡服務器的麻煩,而且許多 DNS 還支持基于地理位置信息的域名解析,即會把域名解析為距離用戶最近的一個服務器的地址,這樣可以加快用戶的訪問速度,提高性能。

但缺點是:DNS 是多級解析,即如果下線了某臺服務器,也需要較長的時間才會真正生效。在這段時間內,DNS 依然會將域名解析到已經下線的服務器,這樣就會導致用戶訪問失敗;而且 DNS 的負載均衡控制權掌握在域名服務商那里,這樣我們就無法對其做出更多的改善。

實踐中,會使用 DNS 域名解析作為第一級的負載均衡手段,即域名解析得到的結果是提供負載均衡服務的內部服務器,這樣我們就可以進行二次負載均衡,把用戶請求分發到真正的 Web 服務器上。

2.3 反向代理

反向代理實現負載均衡

在實際部署時,反向代理服務器位于 Web 服務器之前,這個正好也是負載均衡服務器的位置,所以大多數的反向代理服務器同時會提供負載均衡的功能。

因為 web 服務器不直接對外提供訪問,所以它們不需要外部 IP,而反向代理服務器則需要配置雙網卡和內外兩套 IP。

因為反向代理服務器的轉發請求位于 HTTP 協議層,所以叫做應用層的負載均衡。優點是集成了兩種功能(反向代理、負載均衡),部署簡單。缺點是反向代理服務器是所有請求和響應的中轉站,所以它的性能可能會成為瓶頸。

2.4 IP 負載均衡

在網絡層通過修改請求的目標地址,實現負載均衡。

IP 負載均衡

用戶請求到達負載均衡服務器之后,負載均衡服務器會在操作系統內核進程中獲取網絡數據包,根據負載均衡算法計算得出一臺 Web 服務器,然后把目的的 IP 地址修改為這臺 Web 服務器。Web 服務器處理后,返回響應。負載均衡服務器再把數據包的源地址修改為自身的 IP 地址,發送回瀏覽器。

這里的關鍵點是:真實的物理 Web 服務器,它發送的響應數據包如何返回給負載均衡服務器。這里有兩種方案:

  1. 源地址轉換(SNAT)- 負載均衡服務器在修改目的 IP 地址的同時,修改源地址。
  2. 把負載均衡服務器作為真實物理服務器集群的網關服務器,這樣所有的響應數據都會到達負載均衡服務器啦O(∩_∩)O~

因為 IP 負載均衡是在操作系統的內核層面完成數據轉發,所以相對于反向代理負載均衡,有著更好的處理性能。但由于所有的請求都要經由負載均衡服務器,所以集群中的最大響應數據吞吐量會受制于負載均衡服務器的網卡帶寬。

2.5 數據鏈路層的負載均衡

數據鏈路層的負載均衡指的是,在通信協議的數據鏈路層修改 mac 地址:

數據鏈路層的負載均衡

這種方式又稱為三角傳輸模式或直接路由模式。在分發的過程中,只修改目標的 mac 地址。服務器集群內所有真實、物理的機器都配置一個與負載均衡服務器 IP 地址一樣的虛擬 IP。這樣配置的目的是為了讓處理請求的物理服務器 IP 和與數據請求的目的 IP 一致,這樣就無需在負載均衡服務器上進行地址轉換啦,響應數據會直接發送給瀏覽器(通過網關服務器)O(∩_∩)O~

數據鏈路層的負載均衡是目前大型網站使用最廣的一種負載均衡手段。在 Linux 平臺推薦使用 LVS(Linux Virtual Server)。

2.6 負載均衡算法

實現負載均衡服務器的步驟如下:

  1. 根據負載均衡算法和 Web 服務器列表,計算出集群中的一臺 Web 服務器地址。
  2. 把請求數據發送到這個地址所對應的 Web 服務器上。

目前有這些負載均衡算法:

輪詢

所有請求依次分發到每一臺應用服務器,即每臺服務器處理的請求數是相同的,這適合所有服務器硬件都相同的場景。

加權輪詢

根據服務器的硬件性能,在輪詢的基礎上,按照配置的權重進行請求分發,高性能的服務器會被分配更多的請求。

隨機

把請求隨機分配到各個服務器。這種方案簡單實用,因為好的隨機數本身就很均衡。如果服務器的配置不同,也可以使用加權隨機。

最少連接

記錄每一臺服務器正在處理的請求數(連接數),把新到的請求分發到最少連接的服務器上。這個算法最符合 “負載均衡” 原本定義 !也可以使用加權最少連接。

源地址散列

根據請求來源的 IP 地址進行 Hash 計算,算出應用服務器。這樣來自同一個 IP 地址的請求總會在同一臺服務器上進行處理,因此可以實現會話黏滯。

3 伸縮性設計之分布式緩存集群

分布式緩存集群中的服務器,它們所緩存的數據并不相同,所以緩存的訪問請求,必須先找出需要的緩存數據所在的服務器,才能處理請求。

因此,分布式緩存集群的伸縮性設計必須考慮:讓新上線的緩存服務器對整個分布式緩存集群影響最小,即保證新加入緩存服務器后,整個緩存服務器集群中已經緩存的數據還是能夠盡可能地被訪問到!

3.1 Memcached 訪問模型

應用通過 Memcached 客戶端訪問 Memcached 的服務器集群。Memcached 客戶端由 API、路由算法、服務器集群列表和通信模塊組成。

路由算法會根據緩存數據的 KEY,計算出應該把數據寫入到哪一臺服務器(寫入緩存)或從哪一臺服務器讀取數據(讀取緩存)。

Memcached 訪問模型

一個典型的緩存寫操作,如圖所示。假設應用程序需要寫入緩存的數據 <'DENIRO',DATA> ,Memcached API 會把數據輸入到路由算法模塊。然后路由算法會根據 KEY 和 Memcached 集群服務器列表計算出服務器編號(Node 1),這樣就可以得到這臺服務器的 IP 地址與端口。然后 Memcached API 調用通信模塊與編號為 Node 1 的服務器通信,把數據寫入這臺服務器。這樣就完成了一次分布式緩存寫操作。

讀緩存的過程與寫類似,因為都使用同樣的路由算法和服務器列表,所以只要應用程序提供相同的 KEY,那么 Memcached 客戶端就總是會訪問相同的服務器來讀取數據。因此只要服務器還緩存著數據,就能保證緩存被命中。

3.2 Memcached 分布式緩存實現伸縮性

Memcached 分布式緩存系統中,路由算法很重要,因為它決定了應該訪問集群中的哪一臺服務器。

簡單的路由算法是余數 Hash:服務器數除以緩存數據 KEY 的 Hash 值,求得的余數即為服務器列表的下標。因為 Hash 值的隨機性,所以余數 Hash 可以保證緩存數據在整個 Memcached 服務器集群中比較均衡地分布。

但是,當分布式緩存服務器集群需要擴容時,事情就棘手咯。假設把目前已有的 3 臺緩存服務器擴容為 4 臺。更改服務器列表后,仍然使用余數 Hash 算法,會導致緩存不命中(因為原來是除以 3,現在是除以 4,自然有問題咯)。

三臺服務器擴容至四臺,大約有 75%(3/4)被緩存的數據不命中。隨著服務器集群規模的增大,這個比例呈線性上升。當在 N 臺服務器集群中加入一臺新服務器時,不能命中的概率為 N/(N+1)。如在 100 臺中加入一臺,不能命中的概率為 99%。

這個結果顯然不能接受。網站的大部分業務的讀操作請求,實際上都是通過緩存獲取的,只有少量的讀操作請求會訪問數據庫,因此數據庫的負載能力是以有緩存的前提而設計的。當大部分緩存的數據因為服務器擴容而不能正確讀取時,這些訪問數據的壓力就都落在了數據庫身上,這將大大超出數據庫的負載能力,甚至會導致數據庫宕機。

一種方法是:在網站訪問量最少的時候再擴容,這時候對數據庫的負載壓力最小。然后通過模擬請求來逐步預熱緩存,使得緩存服務器中的數據可以重新分布。但這種方案對業務場景有要求,而且還需要技術團隊通宵加班。看來好像不是個好主意!

3.3 一致性 Hash 算法

一致性 Hash 算法通過一致性 Hash 環的數據結構來實現 KEY 到緩存服務器的 Hash 映射:

一致性 Hash 算法

先構造一個長度為 0 ~ 2 的 32 次方的整數環(一致性 Hash 環),根據節點名稱的 Hash 值,把緩存服務器節點放置在這個 Hash 環上。然后根據需要緩存數據的 KEY 值計算出 Hash 值(范圍在 0 ~ 2 的 32 次方),最后再在 Hash 環上順時針查找距離這個 KEY 的 Hash 值最近的緩存服務器節點,完成 KEY 到服務器的 Hash 映射查找。

比如圖中的 KEY0 在環上順時針查找,就會找到一個最近的節點 NODE 1。

當緩存服務器需要擴容時,只需要將新加入的節點名稱(比如 Node 3)的 Hash 值放入環中,因為 KEY 是順時針查找距離最近的節點,所以新加入的節點只會影響整個環中的一小段。

增加新的節點

如圖所示,原來的大部分的 KEY 還能繼續使用原來的節點,這樣就能保證大部分被緩存的數據還能被命中。3 臺服務器擴容至 4 臺服務器,可以繼續命中原有緩存數據的概率為 75%;100 臺服務器集群增加一臺服務器,繼續命中原有緩存數據的概率為 99%。是不是很棒呀O(∩_∩)O~

一致性 Hash 環通常使用二叉查找樹實現,樹最右邊的葉子節點和最左邊的葉子節點是相連接,構成環。Hash 查找的過程是在樹中查找不小于查找數的最小數值。

一致性 Hash 環有一個缺陷:比如上例,新加入的節點 NODE 3 只影響了原來的節點 NODE 1。這意味著 NODE 0 和 NODE 2 緩存的數據量和負載量是 NODE 1 和 NODE 3 的兩倍。如果這 4 臺服務器性能相同,那么我們自然希望這些服務器緩存的數據量和負載量分布是均衡的。

計算機的任何問題都可以通過增加一個虛擬層來解決。

我們把每一臺物理緩存服務器虛擬為一組虛擬緩存服務器,這樣就可以將虛擬服務器的 Hash 值放置在環上咯。KEY 會先在環上找出虛擬服務器節點,然后再得到物理服務器的信息。

這樣新加入的緩存服務器,會較為均勻地影響原來集群中已經存在的服務器:

  • 綠色:NODE 0 對應的虛擬節點。
  • 藍色:NODE 1 對應的虛擬節點。
  • 紫色:NODE 2 對應的虛擬節點。
  • 紅色:NODE 3 對應的虛擬節點。

顯然,每個物理節點對應的虛擬節點越多,那么各個物理節點之間的負載就會越均衡。虛擬節點數的經驗值是 150。

3 伸縮性設計之數據存儲服務器集群

數據存儲服務器必須保證數據的可靠存儲,任何情況下都必須保證數據的可用性和正確性。

3.1 關系數據庫集群

目前,主流的關系型數據庫都支持數據復制功能,我們可以利用這個功能對數據庫進行簡單伸縮。

MySQL 關系型數據庫集群的伸縮性設計

寫操作都在主服務器上進行,然后再由主服務器把數據同步到集群中的其他從服務器。

也可以使用數據分庫,即把不同的業務數據表部署在不同的數據庫集群上。使用這種方式的不足是:跨庫的表不能進行關聯查詢。

在實際應用中,還會對一些數據量很大的單表進行分片,即把一張表拆開,分別存儲在多個數據庫中。

目前比較成熟的支持數據分片的分布式關系數據庫有開源的 Amoeba 和 Cobar。它們有相似的架構,所以我們這里以 Cobar 為例:

Cobar 部署模型

Cobar 是分布式關系數據庫的訪問代理,部署于應用服務器和數據庫服務器之間,也可以以 lib 的方式與應用部署在一起。應用通過 JDBC 訪問 Cobar 集群,Cobar 服務器依據 SQL 和分庫規則來分解 SQL,然后把請求分發到 MySQL 集群中的不同數據庫實例(每個實例都部署為主從結構,保證數據高可用)上執行。

Cobar 系統組件模型

前端通信模塊接收應用發送過來的 SQL 請求,然后交與 SQL 解析模塊處理,再流轉到 SQL 路由模塊。SQL 路由模塊根據路由配置的規則把 SQL 語句分解為多條 SQL。最后把這些 SQL 發送給多個數據庫分別執行。

多個數據庫把執行的結果返回給 SQL 執行代理模塊,再通過結果合并,把兩個結果集合并為一個結果集,最終返回給應用。

Cobar 服務器可以看作是無狀態的應用服務器,所以可以直接使用負載均衡手段實現集群伸縮。而 MySQL 服務器中存儲著數據,所以我們要做數據遷移(把集群中原有的服務器中的數據遷移到新的服務器),才能保證擴容后數據一致負載均衡。

Cobar 服務器伸縮性原理

可以利用一致性 Hash 算法進行數據遷移,盡量使得要遷移的數據最少。因為遷移數據需要遍歷數據庫中的每一條記錄進行路由計算,所以這會對數據庫造成一定的壓力。而且還要解決遷移過程中的數據一致性、可訪問性、可用性等問題。

實踐中,Cobar 服務器利用 MySQL 的數據同步功能進行數據遷移。遷移是以 Schema 為單位。在 Cobar 集群初始化時,為每一個 MySQL 實例創建多個 Schema。Schema 的個數依據業務遠景的集群規模來估算,如果未來集群的最大規模為 1000 臺數據庫服務器,那么總的初始 Schema 數>= 1000。在擴容的時候,從每個服務器中,遷移一部分 Schema 到新服務器。因為遷移是以 Schema 為單位,所以可以利用 MySQL 同步機制:

利用 MySQL 同步機制實現 Cobar 集群伸縮

同步完成后,即新服務器中的 Schema 數據和原服務器中的 Schema 數據一致后,修改 Cobar 服務器的路由配置,把這些 Schema 的 IP 地址修改為新服務器的 IP 地址,最后刪除原服務器中被遷移的 Schema,即可完成 MySQL 集群的擴容啦O(∩_∩)O~

Cobar 服務器處理所消耗的時間很少,時間主要還是花費在 MySQL 數據庫服務器上。所以應用通過 Cobar 訪問分布式關系數據庫的性能與直接訪問關系數據庫是相當的,因此可以滿足網站在線業務的實時處理需求。而且Cobar 使用了較少的連接來訪問數據庫,還因此改善了性能。

但 Cobar 只能在單一數據庫實例上處理查詢請求,因此無法執行跨庫關聯操作,當然更不能進行跨庫事務處理咯。這是分布式數據庫的通病。

面對海量的業務數據存儲壓力,我們還是得使用分布式數據庫呀,怎么辦?我們可以避免事務或者利用事務補償機制來代替數據庫事務,也可以通過分解數據訪問邏輯來避免數據表關聯操作。

有的分布式數據庫(如 GreenPlum)支持跨庫關聯操作,但訪問延遲較大,因為跨庫關聯需要在服務器之間傳輸大量的數據,所以一般用于數據倉庫等非實時的業務中。

3.2 NoSQL 數據庫

NoSQL 指的是非關系的、分布式數據庫設計模式。它更關注高可用與可伸縮性。

目前應用最廣泛的是 Apache HBase。

HBase 依賴可分裂的 HRegion 和可伸縮的分布式文件系統 HDFS。

HBase 架構

數據以 HRegion 為單位。數據的讀寫操作都是交由 HRegion 進行處理。每個 HRegion 存儲一段以 KEY 值為區間 [key1,key2) 的數據。因為 HRegionServer 是物理服務器,所以每個 HRegionServer 上可以啟動多個 HRegion 實例。當一個 HRegion 寫入的數據超過配置的閾值時,HRegion 就會分裂為兩個 HRegion,然后把這兩個 HRegion 在整個集群上進行遷移,以使 HRegionServer 達到負載均衡。

所有的 HRegion 信息都記錄在 HMaster 服務器上。為了保證高可用,HBase 會啟動多個 HMaster,并通過 ZooKeeper 選舉出一個主服務器。應用會通過 ZooKeeper 獲得主的 HMaster 地址:

HBase 查詢數據時序圖

寫入過程也類似,需要先得到 HRegion 才能寫入。HRegion 會把數據存儲為多個 HFile 格式的文件,這些文件使用 HDFS 分布式文件系統進行存儲,保證在整個集群內分布并高可用。

如果集群中加入了新的服務器(新的 HRegionServer),HBase 會把 HRegion 遷移過去并記錄到 HMaster 服務器中,從而實現 HBase 的線性伸縮。


伸縮性架構設計能力是網站架構師必備的技能。

伸縮性架構設計:一方面是簡單的,因為有很多案例可供借鑒,而且又有大量商業、開源的具有伸縮性能力的軟硬件產品可供選擇;另一方面又是復雜的,因為沒有通用、完美的產品或解決方案。而且伸縮性往往又跟可用性、正確性和性能耦合在一起,所以架構師必須對網站的商業目標、歷史演化和技術路線了然于胸,并綜合考慮技術團隊的知識儲備和結構以及管理層的戰略愿景和規劃,才能做出最適合的伸縮性架構決策。

一個具有良好的伸縮性架構的網站,它的設計總是走在業務發展的前面,在業務需要處理更多訪問和服務之前,就已經做好充分的準備,只要業務有需求,只需要購買或租用服務器簡單部署就好咯O(∩_∩)O~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容