Sentinel是一個管理多個redis實例的工具,它可以實現對redis的監控、通知、自動故障轉移。sentinel不斷的檢測redis實例是否可以正常工作,通過API向其他程序報告redis的狀態,如果redis master不能工作,則會自動啟動故障轉移進程,將其中的一個slave提升為master,其他的slave重新設置新的master實例。也就是說,它提供了:
監控(Monitoring): Sentinel 會不斷地檢查你的主實例和從實例是否正常。
通知(Notification): 當被監控的某個 Redis 實例出現問題時, Sentinel 進程可以通過 API 向管理員或者其他應用程序發送通知。
自動故障遷移(Automatic failover): 當一個主redis實例失效時, Sentinel 會開始記性一次failover, 它會將失效主實例的其中一個從實例升級為新的主實例, 並讓失效主實例的其他從實例改為復制新的主實例; 而當客戶端試圖連接失效的主實例時, 集群也會向客戶端返回新主實例的地址, 使得集群可以使用新主實例代替失效實例。
Redis Sentinel自身也是一個分布式系統, 你可以在一個架構中運行多個 Sentinel 進程, 這些進程使用流言協議(gossip protocols)來接收關於主Redis實例是否失效的信息, 然后使用投票協議來決定是否執行自動failover,以及評選出從Redis實例作為新的主Redis實例。
1.啟動sentinel的方法
當前Redis stable版已經自帶了redis-sentinel這個工具。雖然 Redis Sentinel 已經提供了一個單獨的可執行文件 redis-sentinel , 但實際上它只是一個運行在特殊模式下的 Redis實例, 你可以在啟動一個普通 Redis實例時通過給定 –sentinel 選項來啟動 Redis Sentinel 實例。也就是說:
redis-sentinel /path/to/sentinel.conf
等同於
redis-server /path/to/sentinel.conf --sentinel
其中sentinel.conf是redis的配置文件,Redis sentinel會需要寫入配置文件來保存sentinel的當前狀態。當配置文件無法寫入時,Sentinel啟動失敗。
2. sentinel的配置
一個簡單的sentinel配置文件實例如下:
| 1 2 3 4 5 6 |
port 26329 sentinel monitor myredis 127.0.0.1 6379 2 sentinel down-after-milliseconds mymaster 60000 sentinel failover-timeout mymaster 180000 sentinel parallel-syncs mymaster 1 sentinel notification-script myredis <script-path> |
sentinel 選項的名字 主Redis實例的名字 選項的值
配置文件的格式為:
第一行配置指示 Sentinel 去監視一個名為 mymaster 的主redis實例, 這個主實例的 IP 地址為本機地址127.0.0.1 , 端口號為 6379 , 而將這個主實例判斷為失效至少需要 2 個 Sentinel 進程的同意,只要同意 Sentinel 的數量不達標,自動failover就不會執行。同時,一個Sentinel都需要獲得系統中大多數Sentinel進程的支持, 才能發起一次自動failover, 並預留一個新主實例配置的編號。而當超過半數Redis不能正常工作時,自動故障轉移是無效的。
各個選項的功能如下:
down-after-milliseconds 選項指定了 Sentinel 認為Redis實例已經失效所需的毫秒數。
當實例超過該時間沒有返回PING,或者直接返回錯誤, 那么 Sentinel 將這個實例標記為主觀下線(subjectively down,簡稱 SDOWN )。只有一個 Sentinel進程將實例標記為主觀下線並不一定會引起實例的自動故障遷移: 只有在足夠數量的 Sentinel 都將一個實例標記為主觀下線之后,實例才會被標記為客觀下線(objectively down, 簡稱 ODOWN ), 這時自動故障遷移才會執行。具體的行為如下:
(1). 每個 Sentinel 每秒一次向它所監控的主實例、從實例以及其他 Sentinel 實例發送一個 PING 命令。當一個實例(instance)距離最后一次有效回復 PING 命令的時間超過 down-after-milliseconds 選項所指定的值, 那么這個實例會被 Sentinel 標記為主觀下線。如果一個主實例被標記為主觀下線, 並且有足夠數量的 Sentinel (至少要達到配置文件指定的數量)在指定的時間范圍內同意這一判斷, 那么這個主實例被標記為客觀下線。
(2).在一般情況下, 每個 Sentinel 進程會以每 10 秒一次的頻率向它已知的所有主實例和從實例發送 INFO 命令。 當一個主實例被 Sentinel實例標記為客觀下線時, Sentinel 向下線主實例的所有從實例發送 INFO 命令的頻率會從 10 秒一次改為每秒一次。
(3).當沒有足夠數量的 Sentinel 同意主實例已經下線, 主Redis服務實例的客觀下線狀態就會被移除。 當主服務器重新向 Sentinel 的PING 命令返回有效回復時, 主服務器的主觀下線狀態就會被移除。
parallel-syncs 選項指定了在執行故障轉移時, 最多可以有多少個從Redis實例在同步新的主實例, 在從Redis實例較多的情況下這個數字越小,同步的時間越長,完成故障轉移所需的時間就越長。
盡管復制過程的絕大部分步驟都不會阻塞從實例, 但從redis實例在載入主實例發來的 RDB 文件時, 仍然會造成從實例在一段時間內不能處理命令請求: 如果全部從實例一起對新的主實例進行同步, 那么就可能會造成所有從Redis實例在短時間內全部不可用的情況出現。
所以從實例被設置為允許使用過期數據集(參見對 redis.conf 文件中對 slave-serve-stale-data 選項),可以緩解所有從實例都在同一時間向新的主實例發送同步請求的負擔。你可以通過將這個值設為 1 來保證每次只有一個從Redis實例處於不能處理命令請求的同步狀態。
failover-timeout如果在該時間(ms)內未能完成failover操作,則認為該failover失敗。
notification-script: 指定sentinel檢測到該監控的redis實例指向的實例異常時,調用的報警腳本。該配置項可選,但是很常用。
3. Sentinel集群的運行機制
一個 Sentinel進程可以與其他多個 Sentinel進程進行連接, 每個 Sentinel進程之間可以互相檢查對方的可用性, 並進行信息交換。
和其他集群不同的是,你無須設置其他Sentinel的地址,Sentinel進程可以通過發布與訂閱來自動發現正在監視相同主實例的其他Sentinel。同樣,你也不必手動列出主實例屬下的所有從實例,因為Sentinel實例可以通過詢問主實例來獲得所有從實例的信息。
每個 Sentinel 都訂閱了被它監視的所有主服務器和從服務器的 __sentinel__:hello 頻道, 查找之前未出現過的 sentinel進程。 當一個 Sentinel 發現一個新的 Sentinel 時,它會將新的 Sentinel 添加到一個列表中,這個列表保存了 Sentinel 已知的,監視同一個主服務器的所有其他Sentinel。
4. 啟動Redis和sentinel
下面的測試環境為ubuntu 14.04 LTS,Redis 2.8.19。下面首先配置一個小型的M/2S環境。
| 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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
/etc/redis/redis-m.conf daemonize yes pidfile /var/run/redis/redis-server-m.pid port 6379 loglevel notice logfile /var/log/redis/redis-server-m.log databases 16 ##disable snapshot save "" dir /app/redis-m appendonly yes appendfilename "appendonly.aof" ##not to be a slave #slaveof no one /etc/redis/redis-s1.conf daemonize yes pidfile /var/run/redis/redis-server-s1.pid port 6380 loglevel warning logfile /var/log/redis/redis-server-s1.log databases 16 ##disable snapshot save "" dir /app/redis-s1 appendonly yes appendfilename "appendonly.aof" ##to be a slave /etc/redis/redis-s2.conf daemonize yes pidfile /var/run/redis/redis-server-s2.pid port 6381 loglevel warning logfile /var/log/redis/redis-server-s2.log databases 16 ##disable snapshot,enable AOF save "" dir /app/redis-s2 appendonly yes appendfilename "appendonly.aof" ##to be a slave slaveof 127.0.0.1 6379 |
啟動后,查看進程和復制狀態是否正常:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
ps -ef | grep redis root 17560 1 0 09:48 ? 00:00:00 redis-server *:6379 root 17572 1 0 09:48 ? 00:00:00 redis-server *:6380 root 17609 1 0 09:49 ? 00:00:00 redis-server *:6381 redis-cli 127.0.0.1:6379> info replication # Replication role:master connected_slaves:2 slave0:ip=127.0.0.1,port=6380,state=online,offset=547,lag=0 slave1:ip=127.0.0.1,port=6381,state=online,offset=547,lag=1 master_repl_offset:547 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:2 repl_backlog_histlen:546 |
前面我們了解了,Sentinel可以通過master Redis實例來獲得它的從實例的信息。所以每一個Sentinel只配置主實例的監控即可。Sentinel之間端口有所不同。
| 1 2 3 4 5 6 |
port 26379 sentinel monitor myredis 127.0.0.1 6379 2 sentinel down-after-milliseconds myredis 60000 sentinel failover-timeout myredis 180000 sentinel parallel-syncs myredis 1 sentinel notification-script myredis /etc/redis/log-issues.sh |
其中,通知腳本簡單的記錄一下failover事件。
| 1 2 3 |
#!/bin/bash echo "master failovered at `date`" > /root/redis_issues.log |
另外兩個端口分別為port 26380。然后通過redis-sentinel命令來啟動兩個sentinel實例。
| 1 2 3 4 |
# nohup redis-sentinel /etc/redis/sentinel1.conf > /root/sentinel1.log 2>&1 & [1] 18207 # nohup redis-sentinel /etc/redis/sentinel2.conf > /root/sentinel2.log 2>&1 & [2] 18212 |
通過日志輸出可以看出已經成功監控master和兩個slave。並和另一個sentinel進程通信。
| 1 2 3 4 5 |
[18207] 08 May 13:28:29.336 # Sentinel runid is d72be991001b994568d3de1b746f611884b0343a [18207] 08 May 13:28:29.336 # +monitor master myredis 127.0.0.1 6379 quorum 2 [18207] 08 May 13:28:29.339 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379 [18207] 08 May 13:28:29.339 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379 [18207] 08 May 13:28:36.602 * +sentinel sentinel 127.0.0.1:26479 127.0.0.1 26479 @ myredis 127.0.0.1 6379 |
5. 連接Sentinel和主動failover
在默認情況下,Sentinel 使用TCP端口26379(普通 Redis 服務器使用的是 6379)。Sentinel 接受 Redis 協議格式的命令請求, 所以你可以使用 redis-cli 或者任何其他 Redis 客戶端來與 Sentinel 進行通訊。
有兩種方式可以和 Sentinel 進行通訊:
第一種方法是通過直接發送命令來查詢被監視 Redis 服務器的當前狀態, 以及進行主動轉移等操作。這些命令包括:
(1). PING :返回 PONG 。
| 1 2 3 |
# redis-cli -p 26379 127.0.0.1:26379> ping PONG |
(2). SENTINEL masters :列出所有被監視的主Redis服務實例,以及這些主服務實例的當前狀態。SENTINEL slaves :列出給定主服務實例的所有從實例,以及這些從實例的當前狀態。
| 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 28 29 |
127.0.0.1:26379> sentinel masters 1) 1) "name" 2) "myredis" 3) "ip" 4) "127.0.0.1" 5) "port" 6) "6379" 7) "runid" 8) "a88ffd6548e333f3ac895cf1b890ef32a527dd66" ...... 37) "parallel-syncs" 38) "1" 39) "notification-script" 40) "/etc/redis/log-issues.sh" 127.0.0.1:26379> sentinel slaves myredis 1) 1) "name" 2) "127.0.0.1:6381" 3) "ip" 4) "127.0.0.1" 5) "port" 6) "6381" ...... 2) 1) "name" 2) "127.0.0.1:6380" 3) "ip" 4) "127.0.0.1" 5) "port" 6) "6380" ....... |
(3). SENTINEL get-master-addr-by-name : 返回給定名字的主實例的 IP 地址和端口號。 如果這個主實例正在執行故障轉移操作, 或者針對這個主實例的故障轉移操作已經完成, 那么這個命令返回新的主服務器的 IP 地址和端口號。
| 1 2 3 |
127.0.0.1:26379> sentinel get-master-addr-by-name myredis 1) "127.0.0.1" 2) "6379" |
(4). SENTINEL reset : 重置所有名字和給定模式 pattern 相匹配的主服務器。 pattern 參數是一個 Glob 風格的模式。 重置操作清除該sentinel的所保存的所有狀態信息,並進行一次重新的發現過程。
127.0.0.1:26379> sentinel reset myredis
(integer) 1
(5). SENTINEL failover :進行一次主動的failover。即在不詢問其他 Sentinel 意見的情況下, 強制開始一次自動故障遷移 。發起故障轉移的 Sentinel 會向其他 Sentinel 發送一個新的配置,其他 Sentinel 會根據這個配置進行相應的更新。
| 1 2 |
127.0.0.1:26379> sentinel failover myredis OK |
## master切換到了localhost:6381上。
| 1 2 3 4 5 6 7 8 9 10 11 |
127.0.0.1:26379> sentinel get-master-addr-by-name myredis 1) "127.0.0.1" 2) "6381" ## redis-cli中 redis-cli 127.0.0.1:6379> info replication # Replication role:slave master_host:127.0.0.1 master_port:6381 master_link_status:up |
第二種方法是使用發布與訂閱功能, 通過接收 Sentinel 發送的通知: 當執行故障轉移操作, 或者某個被監視的實例被判斷為主觀下線或者客觀下線時, Sentinel 就會發送相應的信息。
一個頻道能夠接收和這個頻道的名字相同的事件。 比如說, 名為 +sdown 的頻道就可以接收所有實例進入主觀下線(SDOWN)狀態的事件。
通過執行 PSUBSCRIBE * 命令可以接收所有事件信息。例如:
| 1 2 3 4 5 |
127.0.0.1:26379> PSUBSCRIBE * Reading messages... (press Ctrl-C to quit) 1) "psubscribe" 2) "*" 3) (integer) 1 |
###此時進行一次主動的failover,各個頻道的輸出中涉及了新紀元(epoch)通知、投票、選舉、新主和新slave的通告、角色轉變通知,最終完成了這次主動failover過程。
| 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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
1) "pmessage" 2) "*" 3) "+new-epoch" 4) "2" 1) "pmessage" 2) "*" 3) "+try-failover" 4) "master myredis 127.0.0.1 6381" 1) "pmessage" 2) "*" 3) "+vote-for-leader" 4) "d72be991001b994568d3de1b746f611884b0343a 2" 1) "pmessage" 2) "*" 3) "+elected-leader" 4) "master myredis 127.0.0.1 6381" 1) "pmessage" 2) "*" 3) "+failover-state-select-slave" 4) "master myredis 127.0.0.1 6381" 1) "pmessage" 2) "*" 3) "+selected-slave" 4) "slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6381" 1) "pmessage" 2) "*" 3) "+failover-state-send-slaveof-noone" 4) "slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6381" 1) "pmessage" 2) "*" 3) "+failover-state-wait-promotion" 4) "slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6381" 1) "pmessage" 2) "*" 3) "-role-change" 4) "slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6381 new reported role is master" 1) "pmessage" 2) "*" 3) "+promoted-slave" 4) "slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6381" 1) "pmessage" 2) "*" 3) "+failover-state-reconf-slaves" 4) "master myredis 127.0.0.1 6381" 1) "pmessage" 2) "*" 3) "+slave-reconf-sent" 4) "slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381" 1) "pmessage" 2) "*" 3) "+slave-reconf-inprog" 4) "slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381" 1) "pmessage" 2) "*" 3) "+slave-reconf-done" 4) "slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381" 1) "pmessage" 2) "*" 3) "+failover-end" 4) "master myredis 127.0.0.1 6381" 1) "pmessage" 2) "*" 3) "+switch-master" 4) "myredis 127.0.0.1 6381 127.0.0.1 6380" 1) "pmessage" 2) "*" 3) "+slave" 4) "slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6380" 1) "pmessage" 2) "*" 3) "+slave" 4) "slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6380" 1) "pmessage" 2) "*" 3) "-role-change" 4) "slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6380 new reported role is master" 1) "pmessage" 2) "*" 3) "+role-change" 4) "slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6380 new reported role is slave" |
由此可見,一次failover的過程如下:
一次故障轉移操作由以下步驟組成:
(1). 由sentinel主動發起failover或者發現主服務器已經進入客觀下線狀態。
(2). sentinel對我們的當前紀元(epoch)進行自增,並嘗試在這個紀元中當選為此次failover的總指揮。
(3). 如果當選失敗, 那么在設定的故障遷移超時時間的兩倍之后, 重新嘗試當選。 如果當選成功, 那么執行以下步驟。
(4). 選出一個從redis實例,並將它升級為主redis實例。
(5). 向被選中的從redis實例發送 SLAVEOF NO ONE 命令,讓它轉變為主redis實例。
(6). 通過發布與訂閱功能, 將更新后的配置傳播給所有其他 Sentinel , 其他 Sentinel 對它們自己的配置進行更新。
(7). 向已下線主服務器的從服務器發送SLAVEOF命令, 讓它們去復制新的主服務器。
(8). 當所有從redis實例都已經開始復制新的主redis實例時, 領頭Sentinel 終止這次故障遷移操作。
6. 被動failover場景測試
當前狀態,主failover為6380
| 1 2 3 4 5 |
127.0.0.1:6379> info replication # Replication role:slave master_host:127.0.0.1 master_port:6380 |
關閉6380Redis實例,等待down-after-milliseconds后,sentinel的日志有如下輸出,證明切換成功。
| 1 2 3 4 5 6 7 |
[18207] 08 May 14:20:32.784 # +sdown master myredis 127.0.0.1 6380 [18207] 08 May 14:20:32.855 # +odown master myredis 127.0.0.1 6380 #quorum 2/2 [18207] 08 May 14:20:32.855 # +new-epoch 3 ......投票、選舉...... [18207] 08 May 14:20:34.989 # +switch-master myredis 127.0.0.1 6380 127.0.0.1 6381 [18207] 08 May 14:20:34.989 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381 [18207] 08 May 14:20:34.992 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6381 |
啟動6380實例,它會自己作為slave連接行6381這個master節點上。
| 1 2 3 4 5 6 7 8 |
$ redis-server /etc/redis/redis-s1.conf $ redis-cli -p 6380 127.0.0.1:6380> info replication # Replication role:slave master_host:127.0.0.1 master_port:6381 master_link_status:up |
測試成功。最后需要注意的是,sentinel集群自身也需要多數機制,也就是2個sentinel進程時,掛掉一個另一個就不可用了。
7. Sentinel的客戶端
如果要做到應用程序(客戶端)對Redis的failover透明Transparent),客戶端需要監控sentinel的頻道信息,並自動連接新的主節點。官方提供了一個專門的topic來講解這個問題:Guidelines for Redis clients with support for Redis Sentinel,而一些常用的開發語言也已經有了整合sentinel的redis driver:
Python redis 2.10.3
Java Jedis
以Python為例,首先安裝redis-py。
pip install redis
一個簡單的測試代碼如下,首先獲得一個Sentinel對象,然后
| 1 2 3 4 5 6 7 |
vim sentinel.py from redis.sentinel import Sentinel sentinel = Sentinel([('localhost', 26379)], socket_timeout=0.1) master = sentinel.master_for('myredis', socket_timeout=0.1) master.set('foo', 'bar') slave = sentinel.slave_for('myredis', socket_timeout=0.1) slave.get('foo') |
執行后成功得到bar這個值。
| 1 2 |
python sentinel.py bar |
上面的master和slave都是標准的建立好連接的StrictRedis實例,slave則是sentinel查詢到的第一個可用的slave。如果正在連接的master不可用時,客戶端會先拋出redis.exceptions.ConnectionError異常(此時還沒開始failover),然后拋出redis.sentinel.MasterNotFoundError異常(failover進行中),在sentinel正常failover之后,實例正常。所以要開發一個sentinel的高可用客戶端,可以參考上面那篇官方的指導。
