使用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,只保留本機的服務,其他服務全部停止。