使用Redis分散式鎖實現叢集的主備
最近工作中遇到一個問題,我們會呼叫業務部門提供的HTTP介面獲取所有的音視訊任務資訊,這些任務會被分發到各個機器節點進行處理。有兩個方案:
方案一
為每臺機器編號,比如有5臺機器,編號為0,1,2,3,4,然後每臺機器讀取全量任務資訊,將每個任務ID用機器總數量取餘,然後和機器編號比較,相等的表示這個任務在此機器上執行。
- 優點 可以達到任務分開處理的目的
- 缺點 任務分配不均/一臺機器死掉,分給這臺機器的任務將永遠不會被執行到/每臺機器都需要讀取HTTP資訊,浪費資源。
方案二
我們使用其中一臺機器將任務投遞到Kafka中,然後所有機器消費這些任務。
但是需要做到以下2點:
- 需要解決投遞機器單點故障的問題,最好能達到一主多備。
- 任務分配要均勻。
第一個問題是本文的重點,我們採用了Redis的分散式鎖,下面要詳細介紹。關於Kafka任務均勻投遞的問題,需要自己實現排程模組,根據機器效能來投遞到不同機器消費的partition中。
方案二解決了方案一的所有缺點,下面詳細說一下分散式鎖,做一個記錄。
關於主備
主備是高可用叢集中繞不開的問題,服務端一般使用nginx反向代理做一次負載均衡,但是如果nginx掛了呢,這就需要做主備(或者主主也可以),網上這個帖子很詳細Nginx負載均衡高可用(keepalived+nginx實現主備)。但是我們遇到的問題優點特殊,我們做的是客戶端的負載均衡,每次主動呼叫任務介面獲取任務資料來進行處理。並且只能做主備,不能主主,不然會造成任務的重複投遞。
Redis 分散式鎖實現主備
第一次在工作中接觸到redis,發現redis真是個好東西。分散式鎖原理
我們的主備方案中,使用分散式鎖來實現一個類似單例模式的邏輯。
- 使用一個鍵值_master_IP來儲存主機IP,並且設定過期時間(類似單例模式類裡面的資料成員)。
- 定義一個分散式鎖,只有在鍵值_master_IP的值為空的時候,才會獲取鎖,設定鍵_deliver_task_IP的值(類似單例模式中的第一次建構函式呼叫)。
下面是流程圖:
- 主備系統啟動的時候Redis中沒有鍵值_master_IP,所有機器會搶佔Redis分散式鎖_master_lock。
- 搶鎖成功的機器會變為主機,啟動投遞任務。並將_master_IP 值Set成自己的IP,並設定鍵值過期時間,這些操作完成後釋放分散式鎖。
- 主機釋放鎖後,其他備機有可能搶佔到鎖,為了防止備機啟動投遞任務和寫_master_IP,獲取鎖之後會再次判斷_master_IP是否有值,如果有值說明主機已經起來了,直接返回即可。(有點類似於單例模式的雙重鎖)
- 主機任務起來之後,各個機器每隔固定時間會去檢測鍵值_master_IP,主機每次讀取鍵值_master_IP後會自動Extend這個鍵值的Expire Time。備機發現鍵值有值並且不是自己就返回了。
- 主機死掉之後,過了鍵值_master_IP的Expire Time, 鍵值會被刪除。其他備機器會像整個系統啟動的時候一樣開始搶佔鎖並啟動新的主機。
注意:一個Redis叢集(只有一個Master)有可能出現一個鎖被不同服務獲取的情況(Master當機,鎖狀態還沒有來得及同步到Slave就會出現),這樣會在不同的機器上啟動投遞任務,上面的流程中在下一個5秒後會判斷,投遞任務IP是否為本機IP,只保留本機的服務,其他服務全部停止。