相對于緩存服務器集群,數據存儲服務器集群對數據的持久性和可用性提出了更高的要求。因為緩存部分丟失不會影響業務,數據存儲服務器則必須保證數據的可靠存儲。所以不能簡單地使用一致性 Hash。
具體來說,數據存儲集群伸縮性又分為兩種:
- 關系數據庫集群伸縮性
- NoSQL 數據庫集群伸縮性
關系數據庫集群的伸縮性設計
主流的關系型數據庫都支持數據復制功能,我們可以利用這個功能對數據庫進行簡單伸縮。使用數據復制的 MySQL 集群伸縮性方案。
在這種架構中,數據庫寫操作都在主服務器上,由主服務器將數據同步到集群中其他從服務器,數據讀操作及數據分析等離線操作在從服務器上進行。
除了主從讀寫分離,還可以使用數據分庫,即把不同的業務數據表部署在不同的數據庫集群上。使用這種方式的不足是:跨庫的表不能進行關聯查詢(join)。
在實際應用中,還會對一些數據量很大的單表進行分片,即把一張表拆開,分別存儲在多個數據庫中。
目前比較成熟的支持數據分片的分布式關系數據庫有開源的 Amoeba 和 Cobar。它們有相似的架構,這里以 Cobar 為例:
Cobar 是分布式關系數據庫的訪問代理,部署于應用服務器和數據庫服務器之間,也可以非獨立部署(以 lib 的方式與應用部署在一起)。應用通過 JDBC 訪問 Cobar 集群,Cobar 服務器依據 SQL 和分庫規則來分解 SQL,分發到 MySQL 集群中的不同數據庫實例上執行(每個實例都部署為主從結構,保證數據高可用)。
前端通信模塊接收應用發送過來的 SQL 請求(select * from users where userid in (12, 22, 23))后交與 SQL 解析模塊處理,SQL 解析模塊解析獲得 SQL 中的路由規則查詢條件(userid in (12, 22, 23))再轉交到 SQL 路由模塊。SQL 路由模塊根據路由配置的規則(userid 為偶數路由至數據庫 A,為奇數路由至數據庫 B)把 SQL 語句分解為多條 SQL(select * from users where userid in (12, 22);select * from users where userid in (23);)轉交給 SQL 執行代理模塊,發送至數據庫 A 和數據庫 B 分別執行。
兩個數據庫把執行的結果返回給 SQL 執行代理模塊,再通過結果合并,把兩個結果集合并為一個結果集,最終返回給應用。
Cobar 如何做集群的伸縮
Cobar 服務器可以看作是無狀態的應用服務器,所以可以直接使用負載均衡手段實現集群伸縮。而 MySQL 服務器中存儲著數據,所以要做數據遷移(把集群中原有的服務器中的數據遷移到新的服務器),才能保證擴容后數據一致負載均衡。
具體遷移哪些數據可以利用一致性 Hash 算法(即路由模塊使用一致性 Hash 算法進行路由),盡量使要遷移的數據最少。因為遷移數據需要遍歷數據庫中的每一條記錄(的索引),并重新進行路由計算確定是否需要遷移,所以這會對數據庫造成一定的壓力。而且還要解決遷移過程中的數據一致性、可訪問性、遷移過程中服務器宕機時的可用性等問題。
實踐中,Cobar 服務器利用 MySQL 的數據同步功能進行數據遷移。遷移是以 Schema 為單位。在 Cobar 集群初始化時,為每一個 MySQL 實例創建多個 Schema。Schema 的個數依據業務遠景的集群規模來估算,如果未來集群的最大規模為 1000 臺數據庫服務器,那么總的初始 Schema 數>= 1000。在擴容的時候,從每個服務器中遷移部分 Schema 到新服務器。因為遷移是以 Schema 為單位,所以遷移過程可以使用 MySQL 同步機制:
同步完成后,即新服務器中的 Schema 數據和原服務器中的 Schema 數據一致后,修改 Cobar 服務器的路由配置,把這些 Schema 的 IP 地址修改為新服務器的 IP 地址,最后刪除原服務器中被遷移的 Schema,完成 MySQL 集群擴容。
在整個分布式關系數據庫的訪問請求過程中,Cobar 服務器處理所消耗的時間很少,時間主要還是花費在 MySQL 數據庫服務器上。所以應用通過 Cobar 訪問分布式關系數據庫的性能與直接訪問關系數據庫是相當的,因此可以滿足網站在線業務的實時處理需求。事實上由于 Cobar 代替應用程序連接數據庫,數據庫只需要維護更少的連接,減少不必要的資源消耗(其實是代理層 Cobar 限制了數據庫的并發數,比如代理只有 8 線程,同時的連接數就只有 8 個,用完之后可以不釋放。如果不限制線程并發的數量,則 CPU 的資源很快就被耗盡,每個線程執行任務也會相當緩慢,因為 CPU 要把時間片分配給不同的線程對象,而且上下文切換也要耗時,最終造成系統運行效率大幅降低)。
但 Cobar 路由后只能在單一數據庫實例上處理查詢請求,因此無法執行跨庫關聯操作,當然更不能進行跨庫事務處理。這是分布式數據庫的通病。
相比關系數據庫本身功能的強大,目前各類分布式關系數據庫解決方案都顯得簡陋,限制了關系數據庫某些功能的使用。但因為不斷增長的海量數據存儲壓力,又不得不利用分布式關系數據庫的集群伸縮能力,這時就必須從業務上回避分布式關系數據庫的各種缺點:避免事務或利用事務補償機制代替數據庫事務;分解數據訪問邏輯避免 JOIN 操作等。
NoSQL 數據庫集群的伸縮性設計
NoSQL 指的是非關系的、分布式數據庫設計模式。一般而言,NoSQL 數據庫產品都放棄了關系數據庫的兩大重要基礎:以關系代數為基礎的結構化查詢語句(SQL)和事務一致性保證(ACID)。而強化其他一些大型網站更關注的特性:高可用性和可伸縮性。
目前應用最廣泛的 NoSQL 產品是 Apache HBase。
HBase 的伸縮性主要依賴其可分裂的 HRegion 和可伸縮的分布式文件系統 HDFS 實現。
HBase 中,數據以 HRegion 為單位進行管理,也就是說應用程序如果想要訪問一個數據,必須先找到 HRegion,然后將數據讀寫操作提交給 HRegion,由 HRegion 完成存儲層面的數據操作。每個 HRegion 存儲一段 KEY 值區間 [key1,key2) 的數據。HRegionServer 是物理服務器,每個 HRegionServer 上可以啟動多個 HRegion 實例。當一個 HRegion 寫入的數據超過配置的閾值時,HRegion 就會分裂為兩個 HRegion,并將 HRegion 在整個集群中進行遷移,以使 HRegionServer 達到負載均衡。
所有的 HRegion 信息(存儲的 Key 值區間、所在的 HRegionServer 地址、訪問端口號等)都記錄在 HMaster 服務器上。為了保證高可用,HBase 會啟動多個 HMaster,并通過 ZooKeeper (一個支持分布式一致性的數據管理服務)選舉出一個主服務器。應用會通過 ZooKeeper 獲得主 HMaster 的地址,輸入 Key 值獲得這個 Key 所在的 HRegionServer 地址,然后請求 HRegionServer 上的 HRegion,獲得需要的數據。
寫入過程也類似,需要先得到 HRegion 才能繼續操作。HRegion 會把數據存儲為多個 HFile 格式的文件,這些文件使用 HDFS 分布式文件系統進行存儲,保證在整個集群內分布并高可用。當一個 HRegion 中數據量太多時,HRegion(連同 HFile)會分裂長兩個 HRegion,并根據集群中服務器負載進行遷移,如果集群中加入了新的服務器,也就是說有了新的 HRegionServer,由于其負載較低,也會把 HRegion 遷移過去并記錄到 HMaster 中,從而實現 HBase 的線性伸縮。