解決方案系列-叢集選主(基於 DB 唯一鍵)

glmapper發表於2020-04-05

一個業務量很小的系統,所有的程式碼都放在一個專案中,部署在一臺伺服器上。所有的服務都由這臺伺服器提供,這就是常說的單機模式;我們知道單機模式的缺點是:1、處理能力有限,2、存在單點問題。單機模式大致如下圖所示:

為了解決這些問題,出現了叢集模式。

常見的叢集模式方案

叢集主要的使用場景就是為了分擔請求的壓力,也就是在幾個伺服器上部署相同的應用程式,來分擔客戶端請求。叢集主要是通過加機器來解決問題,對於問題本身是不會做任何分解的。(PS:分解了就是分散式)。那麼常見的叢集模式有哪幾種呢?

主備,冷備模式,主寫主讀

所有的寫請求都由 master 負責處理,如果請求打在 slave 上,則 slave 會將請求轉發給 master 處理,架構圖如下圖所示:

冷備模式主要是在請求量不大,且對於資料狀態有要求的情況(比如所有的資料其實都是 master 記憶體儲存),提供一個從機用於備用,在 master 出現問題的時候能夠及時頂上去。

主備,主寫從讀

基於冷備模式,衍生出主寫從讀,也就是 master 會將資料同步給各個 slave,然後就可以分擔掉讀請求的處理壓力,架構圖如下圖所示:

這種模式算是比較常見的,像資料庫的主從模式。

對等,廣播

這種場景是客戶端的請求可能需要指定的叢集中的某個 server 來處理,常見的比如配置中心,配置中心客戶端會選擇與叢集中的某個 server 建立連線,當通過管控端推送配置指令下來時,因為沒法指定到具體的 server 去推送,所以當請求推到叢集的某臺機器時,該機器會向叢集中的其他機器廣播此次請求,誰持有這個客戶端的連線,誰去處理。架構圖如下圖所示:

純對等(無狀態)

最後一種就是純對等的,叢集中的機器誰處理都行;這種就是廣義說的叢集模式,通過擴機器解決高併發。

叢集選主

叢集選主指是上述提到的主備模式下進行的,也就是有狀態的情況,無狀態場景不需要進行選主操作。叢集選主很容易,困難的是選主之後的操作(比如如何協調和感知叢集中的機器狀態),還有叢集內機器發生變更之後的操作(如如何分配客戶端連線,這裡就會涉及到一個非常常見的問題:一致性 hash)。本篇主要介紹叢集選主,所以對於這兩個問題不做過多的探討。

叢集選主的方式有很多種,本篇只介紹通過 ”爭搶鎖“ 的選主方式,即在叢集中機器啟動時會通過爭搶某個”非共享“資源,誰搶到誰來當 master。那麼基於此,我們可以羅列以下幾種常見的”非共享“資源:

  • DB 的唯一鍵插入
  • zk 的節點建立
  • redis 的原子寫

基於 DB 的唯一鍵插入實現選主

下面以 DB 的唯一鍵插入 為例來簡單介紹下選主的邏輯。

資料庫唯一鍵:unique key,用來保證對應的欄位中的資料唯一。

機器表設計&唯一鍵設定

所有叢集中的機器啟動時都需要將自己的機器資訊寫到 DB 中。這裡建立一個唯一鍵:uk_master,包括兩個欄位,分別是 機器狀態和一個 master_lock(master 鎖)。

-- ----------------------------
-- Table structure for cluster_servers
-- ----------------------------
DROP TABLE IF EXISTS `cluster_servers`;
CREATE TABLE `cluster_servers` (
  `id` int(11NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `host_name` varchar(128NOT NULL COMMENT '主機名',
  `ip` varchar(16NOT NULL COMMENT 'ip 地址',
  `is_master` int(4NOT NULL COMMENT '是否是 master',
  `status` varchar(32NOT NULL COMMENT '機器狀態',
  `master_lock` varchar(128NOT NULL COMMENT 'master 鎖',
  `heartbeat` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '心跳時間',
  `gmt_sql_server_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'sql 執行時間',
  `gmt_modify` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改時間',
  `gmt_create` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_master` (`status`,`master_lock`USING BTREE
ENGINE=InnoDB DEFAULT CHARSET=utf8;
複製程式碼

爭搶唯一鍵

這裡就是誰更新資料狀態成功,誰就是 master,沒有爭搶成功則是 slave。

<update id="setMaster" >
    update cluster_servers set is_master = #{isMaster}, master_lock = #{masterLock}, status = #{status}, heartbeat = CURRENT_TIMESTAMP, gmt_modify = CURRENT_TIMESTAMP where host_name = #{hostName}
</update>
複製程式碼

叢集選主的過程

基於此,叢集選主的大致過程可以通過下圖描述:

這裡的像 slave 的後置任務,比如開啟監聽 master 狀態,這樣在 master 出現問題時就會觸發新的選舉。master 的後置任務一個是開始監聽各個 slave 的狀態,如果 slave 出現問題,則可以及時的將此 slave 踢出叢集,其他則需要根據具體的業務情況來看。

總結

本篇主要介紹了叢集的幾種基本形態,然後基於需要選主的場景進行了簡單分析;最後提供了基於 DB 進行叢集選主的一種可行性方案,並介紹了大體的選主流程。需要補充一點,基於 DB 選主和維持心跳本身是比較重的,強依賴 DB 的狀態,如果 DB 有問題則叢集狀態可能會出現一些非預期的情況或者導致叢集直接不可用,所以大家在選擇具體的方式時,還是要結合業務的具體場景來選擇。

相關文章