Redis+Keepalived高可用方案詳細分析

五柳-先生發表於2015-05-11

上一篇簡單的瞭解了一下redis官方自帶的HA方案sentinel,試用發現還是不錯的,但是由於還沒有合併進穩定分支中,所以在生產環境也不敢使用,還有一個就是需求還暫時不能完全滿足,所以就嘗試一下redis+keepalived方案,畢竟keepalived現在還是很穩定的,而且資料也充足。

實驗環境

    ubuntu12.04  10.20.112.26 預設的master

                 10.20.112.27 預設的slave

    VIP          10.20.112.29

    redis-server 2.6.16


    keepalived預設只能做到對網路故障和keepalived本身的監控,即當出現網路故障或者keepalived本身出現問題時,進行切換。但我們更關注的是機器上執行的業務,如果業務出問題了VIP沒有變化,整體來說還是失敗的。這時候就需要根據業務程式的執行狀態決定是否需要進行主備切換。還好keepalived提供了這樣一個自定義指令碼監控功能,不過該引數設定在官方預設的文件中並沒有出現。

    其實文件中有兩個我們常用的引數都沒有提到:

    1 vrrp_script && track_script

      vrrp_script程式碼塊是用來定義監控指令碼,指令碼執行時間間隔以及指令碼的執行結果導致優先順序變更幅度的。

?
1
2
3
4
5
vrrp_script chk_redis {
    script "/etc/keepalived/scripts/redis_check.sh"  #指定執行指令碼的路徑
    interval 1                                       #指定指令碼的執行時間間隔
    weight 10                                        #指令碼結果導致的優先順序變更:10表示優先順序+10;-10則表示優先順序-10
}
      定義好vrrp_script程式碼塊之後,就可以在instance中使用了

?
1
2
3
track_script {
     chk_redis
}

       注意:VRRP指令碼(vrrp_script)和VRRP例項(vrrp_instance)屬於同一個級別

     2 notify_stop

       keepalived停止執行前執行notify_stop指定的指令碼。

       配合官方文件提到的以下三個引數一起使用,功能更強大:

       notify_master  keepalived切換到master時執行的指令碼   
       notify_backup  keepalived切換到backup時執行的指令碼  
       notify_fault   keepalived出現故障時執行的指令碼

      3 還有個問題需要注意

       當master down了,backup接管了,master再次起來,不能再成為master。否則master恢復了再接管的話,會造成業務來回切換,這時候就需要nopreempt引數了。

       nopreempt:設定不搶佔,這裡只能設定在state為backup的節點上,而且這個節點的優先順序必須別另外的高


先來看看方案的整體思路:

    通過keepalived的自定義指令碼功能監控本機的redis服務狀態,當監控指令碼檢測到redis服務出現異常時,則改變本機keepalived的優先順序,同時這會導致master/backup角色的變化,而keepalived在角色變化時也會觸發一些機制執行相關指令碼,這就為我們改變redis的master/slave狀態提供了機會,這樣做的目的是為了是redis的master/slave直接的資料保持一致。

    在keepalived+redis的使用過程中有四種情況:

    1 一種是keepalived掛了,同時redis也掛了,這樣的話直接VIP飄走之後,是不需要進行redis資料同步的,因為redis掛了,你也無法去master上同步,不過會損失已經寫在master上卻還沒同步到slave上面的這部分資料。

    2 另一種是keepalived掛了,redis沒掛,這時候VIP飄走後,redis的master/slave還是老的對應關係,如果不變化的話會把資料寫入redis slave中,從而不會同步到master上去,這就要藉助監控指令碼反轉redis的master/slave關係。這時候就要預留一點時間進行資料同步,然後反轉master/slave。

    3 還有一種是keepalived沒掛,redis掛了,這時候根據監控指令碼會檢測到redis掛了,並且降低keepalived master的優先順序,同樣會導致VIP飄走,情況和第二種一樣,也是需要進行資料同步,然後反轉當前redis的master/slave關係的。

    4 隨後一種是keepalived沒掛,redis也沒掛,大吉大利啊,什麼都不用操作。

    本文的實驗環境四種情況都適合,第一種是不需要同步資料的,指令碼會預設去同步資料,但是其實是不會成功的。指令碼主要是用來處理第二和第三種情況的。



配置10.20.112.26

1 安裝keepalived

?
1
apt-get install keepalived
2 安裝redis

    redis是採用原始碼編譯安裝的,ubuntu12.04預設自帶版本沒有這麼高,安裝過程參照以前文件。

3 配置keepalived

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
global_defs {
    lvs_id LVS_redis
}
vrrp_script chk_redis {
        script "/etc/keepalived/scripts/redis_check.sh"
        weight -20
        interval 2                                    
}
 
vrrp_instance VI_1 {
        state backup                           
        interface eth0                         
        virtual_router_id 51
        nopreempt
        priority 200     
        advert_int 5                     
        track_script {
            chk_redis                    
        }
        virtual_ipaddress {
             10.20.112.29                       
        }
        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
}

4 配置redis,/etc/redis/redis.com 

    最簡單的配置就是把預設配置檔案中的bind 127.0.0.1修改為0.0.0.0即可。

5 建立redis狀態切換指令碼

    在/etc/keepalived目錄下建立log和scripts目錄。

    在script下有五個指令碼,一個是檢測redis狀態的redis_check.sh指令碼,其餘四個是keepalived狀態變化時執行的指令碼。keepalived有master/backup/stop/fault四種狀態,因為我們主要是關注系統上的業務,所以在在keepalived進入fault/stop狀態後,也認為是進入了backup狀態,需要對redis的master/slave關係進行反轉,否則即使VIP漂移過去,但是redis的主從關係還沒有改變,會導致資料不一致,所以最終四個指令碼只有兩種內容。

  5.1 redis服務狀態檢測指令碼redis_check.sh(27上面內容和它一樣)

?
1
2
3
4
5
6
7
8
9
10
#!/bin/bash
###/etc/keepalived/scripts/redis_check.sh
ALIVE=`/usr/bin/redis-cli PING`
if [ "$ALIVE" == "PONG" ]; then
  echo $ALIVE
  exit 0
else
  echo $ALIVE
  exit 1
fi

   5.2 keepalived進入master狀態時的檢測指令碼redis_master.sh

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
###/etc/keepalived/scripts/redis_master.sh
REDISCLI="redis-cli"
LOGFILE="/etc/keepalived/log/redis-state.log"
pid=$$
 
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[slaver]" >> $LOGFILE
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[slaver] Run 'SLAVEOF 10.20.112.27 6379'" >> $LOGFILE
$REDISCLI SLAVEOF 10.20.112.27 6379 >> $LOGFILE  2>&1
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[slaver] wait 10 sec for data sync from old master" >> $LOGFILE
sleep 10
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[slaver] data rsync from old mater ok..." >> $LOGFILE
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[master] Run slaveof no one,close master/slave" >> $LOGFILE
$REDISCLI SLAVEOF NO ONE >> $LOGFILE 2>&1
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[master] wait other slave connect...." >> $LOGFILE

    5.3 keepalived進入backup/stop/fault時的檢測指令碼,由於內容都一致,所以只寫出redis_backup.sh

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
###/etc/keepalived/scripts/redis_backup.sh
REDISCLI="redis-cli"
LOGFILE="/etc/keepalived/log/redis-state.log"
pid=$$
 
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[master]" >> $LOGFILE
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[master] Being slave state..." >> $LOGFILE 2>&1
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[master] wait 10 sec for data sync from old master" >> $LOGFILE
sleep 10
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[master] data rsync from old mater ok..." >> $LOGFILE
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[slaver] Run 'SLAVEOF 10.20.112.27 6379'" >> $LOGFILE
$REDISCLI SLAVEOF 10.20.112.27 6379 >> $LOGFILE  2>&1
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[slaver] slave connect to 10.20.112.27 ok..." >> $LOGFILE

配置10.20.112.27

1 安裝keepalived

2 安裝redis

3 配置redis,/etc/redis/redis.com

    前面三步驟均一樣

4 配置keepalived

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
global_defs {
    lvs_id LVS_redis
}
 
vrrp_script chk_redis {
        script "/etc/keepalived/scripts/redis_check.sh"  
        weight  -20
        interval 2                                      
}
vrrp_instance VI_1 {
        state backup                               
        interface eth0                             
        virtual_router_id 51
        priority 190   
        advert_int  5                           
        track_script {
             chk_redis                  
        }
        virtual_ipaddress {
             10.20.112.29                   
        }
        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
}

5 建立redis狀態切換指令碼

    在/etc/keepalived目錄下建立log和scripts目錄

    5.1 redis服務狀態檢測指令碼redis_check.sh(26上面內容和它一樣)

    5.2 keepalived進入master狀態時的檢測指令碼redis_master.sh

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
###/etc/keepalived/scripts/redis_master.sh
REDISCLI="/usr/bin/redis-cli"
LOGFILE="/etc/keepalived/log/redis-state.log"
pid=$$
 
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[backup]" >> $LOGFILE
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[backup] Run 'SLAVEOF 10.20.112.26 6379'" >> $LOGFILE
$REDISCLI SLAVEOF 10.20.112.26 6379 >> $LOGFILE  2>&1
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[backup] wait 10 sec for data sync from old master" >> $LOGFILE
sleep 10
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[master] data rsync from old mater ok..." >> $LOGFILE
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[master] Run slaveof no one,close master/slave" >> $LOGFILE
$REDISCLI SLAVEOF NO ONE >> $LOGFILE 2>&1
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[master] wait other slave connect...." >> $LOGFILE

    5.3 keepalived進入backup/stop/fault時的檢測指令碼,由於內容都一致,所以只寫出redis_backup.sh

?
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
###/etc/keepalived/scripts/redis_backup.sh
REDISCLI="/usr/bin/redis-cli"
LOGFILE="/etc/keepalived/log/redis-state.log"
pid=$$
 
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[master] Being slave state..." >> $LOGFILE 2>&1
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[master] wait 15 sec for data sync from old master" >> $LOGFILE
sleep 15
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[master] data rsync from old mater ok..." >> $LOGFILE
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[slaver] Run 'SLAVEOF 10.20.112.26 6379'" >> $LOGFILE
$REDISCLI SLAVEOF 10.20.112.26 6379 >> $LOGFILE  2>&1
echo "`date +'%Y-%m-%d:%H:%M:%S'`|$pid|state:[slaver] slave connect to 10.20.112.26 ok..." >> $LOGFILE



下面開始試驗

    既然我們設定了nopreempt,那麼在啟動keepalived的時候就有啟動的順序問題了,我們把redis的master和keepalived的master(雖然配置檔案中都是backup,但是我們是想讓26這臺做master的)預設設定在同一臺機器上,由於在keepalived的master上面設定了nopreempt引數,所以在啟動keepalived服務的時候,一定要先啟動redis master的那臺,因為在設定了nopreempt了,keepalived在啟動後都是先進入backup狀態,而指令碼又設定了進入backup狀態後,會連線新的對方進行資料同步,所以,在啟動keepalived之前還有一個條件就是redis的master和slave中的資料必須一致。這樣先啟動redis的master那臺的keepalived,雖然redis master會連線到redis slave同步資料,但是兩邊資料在剛開始的時候是一致的,並不會產生什麼問題。

1 先啟動26和27上的redis服務,配置27從26上面同步資料,同步完畢後,取消27的同步機制。

    這個就是在27的redis上面執行slaveof 10.20.112.26 6379,等待資料同步完畢後再執行slaveos on one,讓26和27的redis都保持master狀態

2 接著啟動26的keepalived,不要啟動27的keepalived,在26和27上面各開啟三個終端,觀察自定義日誌和系統日誌狀態

  26的syslog


    紅框 19秒keepalived啟動後,由於設定了不搶佔,且起始狀態都是backup,所以啟動後keepalived先進入backup狀態,進入bankup狀態後,成功的執行了redis_backup.sh指令碼

    綠框 34秒的時候,keepalived開始向master狀態轉變,注意:這個時間很重要,為什麼是34秒呢?預設是3秒後就開始向master狀態轉變,由於我們設定了advert_int 5,預設檢測三次,所以15秒之後才向master轉變,為什麼設定間隔這麼長時間呢?是為了讓redis_backup.sh指令碼有充足的時間執行完畢。39秒的時候成功的轉變為master狀態,這時候會執行redis_master.sh指令碼。

  26的redis-state.log

   過程解釋:

   紅框 確實如系統日誌表現的那樣,19秒的時候先進入backup狀態,執行redis_backup.sh指令碼。redis_backup是keepalived進入到backup狀態時執行的指令碼。進入到banckup狀態,說明自身以前是master狀態或者無狀態,這時候會等待兩秒種,讓之前的slave把資料同步完,2秒鐘在資料大的時候有些短,其實是可以設定的,只要小於3倍的advert_int時間就行,因為3倍的時間之後會轉為master狀態的。 2秒鐘之後我們的redis連線到27上面(27這時候充當master),開始進行正常的master/slave機制同步資料,也就是紅框中21秒的時候。到此為止,redis_backup.sh執行完畢。

   綠框 由於設定了不搶佔,所以過了3倍的advert_int時間之後,開始向master狀態轉變,這個轉變過程需要的時間是不固定的,在上面的日誌中需要5秒,正式的成為master狀態,這時候會執行redis_master.sh指令碼,指令碼指定需要到27的redis上同步資料(27在紅框中的時候是充當master的),你可以看到已經有連線了,那是因為在紅框中已經連線過一次了,我設定的是同步10秒鐘之後斷開和27的連線,可以看到49秒的時候執行了slaveof no one命令,斷開了和27的連線,把26自身的redis提升為master,這時候資料也是最新的了。

3 待26的各項程式都ok後,我們開始啟動27上面的keepalived服務

  27的syslog


   紅框 40秒的時候啟動27的keepalived,由於預設都是backup,所以直接進入backup狀態,由於27的優先順序比26低,所以27並不會過度到master狀態,這時候26是master狀態了。在backup狀態執行了redis_backup.sh指令碼。

  27的redis-state.log

   紅框 在進入backup狀態後,預設之前是master狀態或者無狀態,所以等待15秒,讓26把資料同步完(26這時候充當backup),在15秒之後,開始向backup轉變,開始執行slaveof 10.20.112.26 6379,作為26的slave。


下面要模擬故障3:

1 kill掉26的redis程式,保持keepalived程式。(模擬3)

  26的syslog


  紅框 可以看到檢測指令碼chk_redis發現redis已經宕掉,降低了26的keepalived的優先順序,導致26進入backup狀態,開始執行redis_backup.sh監控指令碼

  26的redis-state.log


  紅框 由於redis我是kill掉的,所以必然不能進行資料同步了,不過這並不影響使用。

  27的syslog


  綠框 可以看到27轉變到master狀態,並且執行了redis_master.sh指令碼

  27的redis-state.log

  綠框 由於26的redis是kill掉的,所以即使27轉變到master狀態也不能去26同步資料,當然27也等不到26連線上來,不過並不影響我們使用。

下面要模擬故障2:

2 kill掉26的keepalived程式,保持redis程式。(模擬2)

  26的syslog


  紅框 是keepslived的啟動過程,在模擬3中已經詳細分析過

  綠框 是kill掉keepalived程式後的日誌。

  26的redis-state.log


  紅框 是keepalived啟動過程中執行的監控指令碼日誌,前面模擬3中已經詳細分析過

  綠框 是keepalived程式kill掉之後,執行的監控指令碼日誌,在等待27同步完資料後,作為27的slave連線上去。

  26的redis info

  可以看到master/slave的轉變過程

  27的syslog


  紅框 是keepalived作為backup的啟動過程,在模擬3中已經分析過。

  綠框 是kill掉26的keepalived程式後,27轉變為master時的日誌,執行了redis_master.sh指令碼。

  27的redis-state.log


  紅框 是keepalived啟動時執行的監控指令碼日誌,由於開始是backup的,所以沒有過多的狀態切換過程。

  綠框 是26的keepalived程式被kill掉之後,27轉變為master時執行的指令碼日誌,由於keepalived掛了,但是26的redis程式還在,所以先和26進行資料同步,完成之後再把自己提升為redis master。

  27的redis info


  可以看到redis的master/slave變化過程


以上就是我所能想到的keepalived+redis HA方案中需要注意的地方。

轉載: http://my.oschina.net/guol/blog/182491

相關文章