1. 程式人生 > >說說大型網站可伸縮性架構的設計原理

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

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

大型網站中的 “大型”,可以表現在以下幾個方面:
* 使用者方面 - 大量的使用者與大量訪問(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~