keepalived+redis 高可用redis主從解決方案

keepalived+redis 高可用redis主從解決方案

背景介紹:

目前,Redis還沒有一個類似於MySQL Proxy或Oracle RAC的官方HA方案。
#Redis 2.8版開始正式提供名爲Sentinel的主從切換方案(後面附上,未測試)

因此,如何在出現故障時自動轉移是一個需要解決的問題。

通過對網上一些資料的搜索,有建議採用HAProxy或Keepalived來實現的,事實上如果是做Failover而非負載均衡的話,Keepalived的效率肯定是超過HAProxy的,所以我決定採用Keepalived的方案。

環境介紹:
Master: 192.168.0.100
Slave: 192.168.0.101
Virtural IP Address (VIP): 192.168.0.200

設計思路:

當 Master 與 Slave 均運作正常時, Master負責服務,Slave負責Standby;
當 Master 掛掉,Slave 正常時, Slave接管服務,同時關閉主從複製功能;
當 Master 恢復正常,則從Slave同步數據,同步數據之後關閉主從複製功能,恢復Master身份,於此同時Slave等待Master同步數據完成之後,恢復Slave身份。
然後依次循環。

需要注意的是,這樣做需要在Master與Slave上都開啓本地化策略,否則在互相自動切換的過程中,未開啓本地化的一方會將另一方的數據清空,造成數據完全丟失。

下面,是具體的實施步驟:

在Master和Slave上安裝Keepalived

$ yum install keepalived

默認安裝完成keepalived有默認的配置文件,因此我們重寫覆蓋它:

首先,在Master上創建如下配置文件
$ vim /etc/keepalived/keepalived.conf

! Configuration File for keepalived
global_defs {
   router_id redis100
}
vrrp_script chk_redis 

     script "/etc/keepalived/scripts/redis_check.sh 127.0.0.1 6379" 
     interval 2 
     timeout 2
     fall 3
}
vrrp_instance redis {
     state MASTER # master set to SLAVE also
     interface eth0  
     virtual_router_id 50 
     priority  150       
     nopreempt # no seize,must add
     advert_int 1
authentication {   #all node must same
         auth_type PASS
         auth_pass 1111
    }
    virtual_ipaddress {  
192.168.0.200/24
    }
    track_script { 
         chk_redis 
    } 
     notify_master "/etc/keepalived/scripts/redis_master.sh 127.0.0.1 192.168.0.101 6379"
     notify_backup "/etc/keepalived/scripts/redis_backup.sh 127.0.0.1 192.168.0.101 6379"
     notify_fault /etc/keepalived/scripts/redis_fault.sh 
     notify_stop /etc/keepalived/scripts/redis_stop.sh 
}

然後,在Slave上創建如下配置文件:

! Configuration File for keepalived

global_defs {
   router_id redis101
}
vrrp_script chk_redis 

     script "/etc/keepalived/scripts/redis_check.sh 127.0.0.1 6379" 
     interval 2
     timeout 2
     fall 3
}
vrrp_instance redis {
    state BACKUP   
    interface eth0   
    virtual_router_id 50  
    priority  100       
    advert_int 1
authentication {   #all node must same
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress { 
    192.168.0.200/24
    }
    track_script { 
         chk_redis 
    } 
    notify_master "/etc/keepalived/scripts/redis_master.sh 127.0.0.1 192.168.0.100 6379"
    notify_backup "/etc/keepalived/scripts/redis_backup.sh 127.0.0.1 192.168.0.100 6379"
    notify_fault /etc/keepalived/scripts/redis_fault.sh 
    notify_stop /etc/keepalived/scripts/redis_stop.sh 
}

在Master和Slave上創建監控Redis的腳本
$ mkdir /etc/keepalived/scripts
$ vim /etc/keepalived/scripts/redis_check.sh

#!/bin/bash 
ALIVE=`/usr/redis/redis-cli -h $1 -p $2 PING` 
LOGFILE="/var/log/keepalived-redis-check.log" 
echo "[CHECK]" >> $LOGFILE
date >> $LOGFILE
if [ $ALIVE == "PONG" ]; then :
   echo "Success: redis-cli -h $1 -p $2 PING $ALIVE" >> $LOGFILE 2>&1
    exit 0 
else 
    echo "Failed:redis-cli -h $1 -p $2 PING $ALIVE " >> $LOGFILE 2>&1
    exit 1 
fi 

編寫以下負責運作的關鍵腳本:
notify_master /etc/keepalived/scripts/redis_master.sh
notify_backup /etc/keepalived/scripts/redis_backup.sh
notify_fault /etc/keepalived/scripts/redis_fault.sh
notify_stop /etc/keepalived/scripts/redis_stop.sh

因爲Keepalived在轉換狀態時會依照狀態來呼叫:
當進入Master狀態時會呼叫notify_master
當進入Backup狀態時會呼叫notify_backup
當發現異常情況時進入Fault狀態呼叫notify_fault
當Keepalived程序終止時則呼叫notify_stop

首先,在Redis Master上創建notity_master與notify_backup腳本:
$ vim /etc/keepalived/scripts/redis_master.sh

#!/bin/bash 
REDISCLI="/usr/redis/redis-cli -h $1 -p $3" 
LOGFILE="/var/log/keepalived-redis-state.log" 
echo "[master]" >> $LOGFILE 
date >> $LOGFILE 
echo "Being master...." >> $LOGFILE 2>&1  
echo "Run MASTER cmd ..." >> $LOGFILE 2>&1
$REDISCLI SLAVEOF $2 $3 >> $LOGFILE  
sleep 10 #delay 10 s wait data async cancel sync
echo "Run SLAVEOF NO ONE cmd ..." >> $LOGFILE
$REDISCLI SLAVEOF NO ONE >> $LOGFILE 2>&1



$ sudo vim /etc/keepalived/scripts/redis_backup.sh

#!/bin/bash 
REDISCLI="/usr/redis/redis-cli" 
LOGFILE="/var/log/keepalived-redis-state.log" 
echo "[backup]" >> $LOGFILE 
date >> $LOGFILE 
echo "Run SLAVEOF cmd ..." >> $LOGFILE 
$REDISCLI SLAVEOF $2 $3 >> $LOGFILE 2>&1 
# echo "Being slave...." >> $LOGFILE 2>&1 
sleep 15 #delay 15 s wait data sync exchange role 


接着,在Redis Slave上創建notity_master與notify_backup腳本:

$ vim /etc/keepalived/scripts/redis_master.sh

#!/bin/bash 
REDISCLI="/usr/redis/redis-cli -h $1 -p $3" 
LOGFILE="/var/log/keepalived-redis-state.log" 
echo "[master]" >> $LOGFILE 
date >> $LOGFILE 
echo "Being master...." >> $LOGFILE 2>&1 
echo "Run SLAVEOF cmd ... " >> $LOGFILE 
$REDISCLI SLAVEOF $2 $3 >> $LOGFILE  2>&1
#echo "SLAVEOF $2 cmd can't excute ... " >> $LOGFILE 
sleep 10 ##delay 15 s wait data sync exchange role
echo "Run SLAVEOF NO ONE cmd ..." >> $LOGFILE
$REDISCLI SLAVEOF NO ONE >> $LOGFILE 2>&1 


$ vim /etc/keepalived/scripts/redis_backup.sh

#!/bin/bash 
REDISCLI="/usr/redis/redis-cli" 
LOGFILE="/var/log/keepalived-redis-state.log" 
echo "[BACKUP]" >> $LOGFILE 
date >> $LOGFILE 
echo "Being slave...." >> $LOGFILE 2>&1 
echo "Run SLAVEOF cmd ..." >> $LOGFILE 2>&1
$REDISCLI SLAVEOF $2 $3 >> $LOGFILE  
sleep 100 #delay 10 s wait data async cancel sync 
exit(0)


然後在Master與Slave創建如下相同的腳本:
$ vim /etc/keepalived/scripts/redis_fault.sh

#!/bin/bash 
LOGFILE=/var/log/keepalived-redis-state.log 
echo "[fault]" >> $LOGFILE
date >> $LOGFILE 

$ sudo vim /etc/keepalived/scripts/redis_stop.sh

#!/bin/bash 
LOGFILE=/var/log/keepalived-redis-state.log 
echo "[stop]" >> $LOGFILE 
date >> $LOGFILE 

給腳本都加上可執行權限:

(這點很重要,最開始由於這不沒做,運行後一直報錯 "VRRP_Instance(redis) Now in FAULT state")

$ sudo chmod +x /etc/keepalived/scripts/*.sh

腳本創建完成以後,我們開始按照如下流程進行測試
1.啓動Master上的Redis
$ /etc/init.d/redis start

2.啓動Slave上的Redis
$ /etc/init.d/redis start

3.啓動Master上的Keepalived
$ /etc/init.d/keepalived start

4.啓動Slave上的Keepalived
$ /etc/init.d/keepalived start


5.嘗試通過VIP連接Redis:
$ redis-cli -h 10.6.1.200 INFO

連接成功,Slave也連接上來了。
role:master
slave0:10.6.1.144,6379,online

6.嘗試插入一些數據:
$ redis-cli -h 10.6.1.200 SET Hello Redis
OK

從VIP讀取數據
$ redis-cli -h 10.6.1.200 GET Hello
"Redis"

從Master讀取數據
$ redis-cli -h 10.6.1.143 GET Hello
"Redis"

從Slave讀取數據
$ redis-cli -h 10.6.1.144 GET Hello
"Redis"


下面,模擬故障產生:
將Master上的Redis停了
$ service redis_6379 stop

查看Master上的Keepalived日誌
$ tailf /var/log/keepalived-redis-state.log
[fault]
Thu Sep 27 08:29:01 CST 2012

同時Slave上的日誌顯示:
$ tailf /var/log/keepalived-redis-state.log
[master]
Fri Sep 28 14:14:09 CST 2012
Being master....
Run SLAVEOF cmd ...
OK
Run SLAVEOF NO ONE cmd ...
OK

然後我們可以發現,Slave已經接管服務,並且擔任Master的角色了。
$ redis-cli -h 192.168.0.200 INFO

role:master

然後我們恢復Master的Redis進程
$ service redis_6379 start

查看Master上的Keepalived日誌
$ tailf /var/log/keepalived-redis-state.log
[master]
Thu Sep 27 08:31:33 CST 2012
Being master....
Run SLAVEOF cmd ...
OK
Run SLAVEOF NO ONE cmd ...
OK

同時Slave上的日誌顯示:
$ tailf /var/log/keepalived-redis-state.log
[backup]
Fri Sep 28 14:16:37 CST 2012
Being slave....
Run SLAVEOF cmd ...
OK

可以發現目前的Master已經再次恢復了Master的角色,故障切換以及自動恢復都成功了。

主從用到的腳本及keepalived.conf 可以從這下載 http://download.csdn.net/detail/huwei2003/8252221

注意事項:主從的redis都要開啓本地備份


附:

Redis Sentinel的主從切換方案

Redis 2.8版開始正式提供名爲Sentinel的主從切換方案,Sentinel用於管理多個Redis服務器實例,主要負責三個方面的任務:

    1. 監控(Monitoring): Sentinel 會不斷地檢查你的主服務器和從服務器是否運作正常。
    2. 提醒(Notification): 當被監控的某個 Redis 服務器出現問題時, Sentinel 可以通過 API 向管理員或者其他應用程序發送通知。
    3. 自動故障遷移(Automatic failover): 當一個主服務器不能正常工作時, Sentinel 會開始一次自動故障遷移操作, 它會將失效主服務器的其中一個從服務器升級爲新的主服務器, 並讓失效主服務器的其他從服務器改爲複製新的主服務器; 當客戶端試圖連接失效的主服務器時, 集羣也會向客戶端返回新主服務器的地址, 使得集羣可以使用新主服務器代替失效服務器。

Redis Sentinel 是一個分佈式系統, 你可以在一個架構中運行多個 Sentinel 進程(progress), 這些進程使用流言協議(gossip protocols)來接收關於主服務器是否下線的信息, 並使用投票協議(agreement protocols)來決定是否執行自動故障遷移, 以及選擇哪個從服務器作爲新的主服務器。

啓動Sentinel

使用--sentinel參數啓動,並必須指定一個對應的配置文件,系統會使用配置文件來保存 Sentinel 的當前狀態, 並在 Sentinel 重啓時通過載入配置文件來進行狀態還原。

    redis-server /path/to/sentinel.conf --sentinel

使用TCP端口26379,可以使用redis-cli或其他任何客戶端與其通訊。

如果啓動 Sentinel 時沒有指定相應的配置文件, 或者指定的配置文件不可寫(not writable), 那麼 Sentinel 會拒絕啓動。

配置Sentinel

以下是一段配置文件的示例:

sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 60000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1

sentinel monitor resque 192.168.1.3 6380 4
sentinel down-after-milliseconds resque 10000
sentinel failover-timeout resque 180000
sentinel parallel-syncs resque 5

    第一行配置指示 Sentinel 去監視一個名爲 mymaster 的主服務器, 這個主服務器的 IP 地址爲 127.0.0.1 , 端口號爲 6379 , 而將這個主服務器判斷爲失效至少需要 2 個 Sentinel 同意 (只要同意 Sentinel 的數量不達標,自動故障遷移就不會執行)。
    不過需要注意的是,無論你設置要多少個 Sentinel 同意才能判斷一個服務器失效,一個 Sentinel 都需要獲得系統中多數(majority) Sentinel 的支持,才能發起一次自動故障遷移,並預留一個給定的配置紀元 (Configuration Epoch ,一個配置紀元就是一個新主服務器配置的版本號)。也就是說,如果只有少數(minority)Sentinel 進程正常運作的情況下,是不能執行自動故障遷移的。

    down-after-milliseconds 選項指定了 Sentinel 認爲服務器已經斷線所需的毫秒數(判定爲主觀下線SDOWN)。
    parallel-syncs 選項指定了在執行故障轉移時, 最多可以有多少個從服務器同時對新的主服務器進行同步, 這個數字越小, 完成故障轉移所需的時間就越長,但越大就意味着越多的從服務器因爲複製而不可用。可以通過將這個值設爲 1 來保證每次只有一個從服務器處於不能處理命令請求的狀態。

主觀下線和客觀下線

    1. 主觀下線(Subjectively Down, 簡稱 SDOWN)指的是單個 Sentinel 實例對服務器做出的下線判斷。
    2. 客觀下線(Objectively Down, 簡稱 ODOWN)指的是多個 Sentinel 實例在對同一個服務器做出 SDOWN 判斷, 並且通過 SENTINEL is-master-down-by-addr 命令互相交流之後, 得出的服務器下線判斷。

客觀下線條件只適用於主服務器: 對於任何其他類型的 Redis 實例, Sentinel 在將它們判斷爲下線前不需要進行協商, 所以從服務器或者其他 Sentinel 永遠不會達到客觀下線條件。
只要一個 Sentinel 發現某個主服務器進入了客觀下線狀態, 這個 Sentinel 就可能會被其他 Sentinel 推選出, 並對失效的主服務器執行自動故障遷移操作。

每個Sentinel實例都執行的定時任務

    1. 每個 Sentinel 以每秒鐘一次的頻率向它所知的主服務器、從服務器以及其他 Sentinel 實例發送一個 PING 命令。
    2. 如果一個實例(instance)距離最後一次有效回覆 PING 命令的時間超過 down-after-milliseconds 選項所指定的值, 那麼這個實例會被 Sentinel 標記爲主觀下線。 一個有效回覆可以是: +PONG 、 -LOADING 或者 -MASTERDOWN 。
    3. 如果一個主服務器被標記爲主觀下線, 那麼正在監視這個主服務器的所有 Sentinel 要以每秒一次的頻率確認主服務器的確進入了主觀下線狀態。
    4. 如果一個主服務器被標記爲主觀下線, 並且有足夠數量的 Sentinel (至少要達到配置文件指定的數量)在指定的時間範圍內同意這一判斷, 那麼這個主服務器被標記爲客觀下線。
    5. 在一般情況下, 每個 Sentinel 會以每 10 秒一次的頻率向它已知的所有主服務器和從服務器發送 INFO 命令。 當一個主服務器被 Sentinel 標記爲客觀下線時, Sentinel 向下線主服務器的所有從服務器發送 INFO 命令的頻率會從 10 秒一次改爲每秒一次。
    6. 當沒有足夠數量的 Sentinel 同意主服務器已經下線, 主服務器的客觀下線狀態就會被移除。 當主服務器重新向 Sentinel 的 PING 命令返回有效回覆時, 主服務器的主管下線狀態就會被移除。

Sentinel API

有兩種方式可以與Sentinel進行通訊:指令、發佈與訂閱。

    Sentinel命令

       PING :返回 PONG 。
       SENTINEL masters :列出所有被監視的主服務器,以及這些主服務器的當前狀態;
       SENTINEL slaves <master name> :列出給定主服務器的所有從服務器,以及這些從服務器的當前狀態;
       SENTINEL get-master-addr-by-name <master name> : 返回給定名字的主服務器的 IP 地址和端口號。 如果這個主服務器正在執行故障轉移操作, 或者針對這個主服務器的故障轉移操作已經完成, 那麼這個                     命令返回新的主服務器的 IP 地址和端口號;
       SENTINEL reset <pattern> : 重置所有名字和給定模式 pattern 相匹配的主服務器。 pattern 參數是一個 Glob 風格的模式。 重置操作清楚主服務器目前的所有狀態, 包括正在執行中的故障轉移, 並移除目前已經發現和關聯的, 主服務器的所有從服務器和 Sentinel ;
       SENTINEL failover <master name> : 當主服務器失效時, 在不詢問其他 Sentinel 意見的情況下, 強制開始一次自動故障遷移。

客戶端可以通過SENTINEL get-master-addr-by-name <master name>獲取當前的主服務器IP地址和端口號,以及SENTINEL slaves <master name>獲取所有的Slaves信息

    發佈與訂閱信息

    客戶端可以將 Sentinel 看作是一個只提供了訂閱功能的 Redis 服務器: 你不可以使用 PUBLISH 命令向這個服務器發送信息, 但你可以用 SUBSCRIBE 命令或者 PSUBSCRIBE 命令, 通過訂閱給定的頻道來獲取相應的事件提醒。
   一個頻道能夠接收和這個頻道的名字相同的事件。 比如說, 名爲 +sdown 的頻道就可以接收所有實例進入主觀下線(SDOWN)狀態的事件。
   通過執行 PSUBSCRIBE * 命令可以接收所有事件信息。

        +switch-master <master name> <oldip> <oldport> <newip> <newport> :配置變更,主服務器的 IP 和地址已經改變。 這是絕大多數外部用戶都關心的信息。

    可以看出,我們使用Sentinel命令和發佈訂閱兩種機制就能很好的實現和客戶端的集成整合:
    使用get-master-addr-by-name和slaves指令可以獲取當前的Master和Slaves的地址和信息;而當發生故障轉移時,即Master發生切換,可以通過訂閱的+switch-master事件獲得最新的Master信息。

    *PS:更多Sentinel的可訂閱事件參見官方文檔

sentinel.conf中的notification-script

    在sentinel.conf中可以配置多個sentinel notification-script <master name> <shell script-path>, 如sentinel notification-script mymaster ./check.sh

    這個是在羣集failover時會觸發執行指定的腳本。腳本的執行結果若爲1,即稍後重試(最大重試次數爲10);若爲2,則執行結束。並且腳本最大執行時間爲60秒,超時會被終止執行。

    PS:目前會存在該腳本被執行多次的問題,查找資料有人解釋是:
        腳本分爲兩個級別, SENTINEL_LEADER 和 SENTINEL_OBSERVER ,前者僅由領頭 Sentinel 執行(一個 Sentinel),而後者由監視同一個 master 的所有 Sentinel 執行(多個 Sentinel)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章