Redis 可以使用從屬伺服器來實現讀寫分離提高吞吐量或在主伺服器故障時接替主伺服器以提高可用性。
每個 Redis 伺服器例項都可以配置多個 slave 節點,slave 伺服器也可以擁有次級 slave 節點, 可以組織成複雜的樹狀結構(雖說生產環境下極少有人這麼做)。
配置主從複製
為了嘗試配置主從複製,我們至少需要兩個 redis 伺服器例項。最簡單的方法是在 redis 官網下載 redis-server 二進位制可執行檔案,分別放在 master 和 slave 目錄中。
在每個目錄中分別建立 redis.conf 配置檔案。master 例項的配置檔案採用預設即可, 在 slave 例項中進行主從複製配置:
# 和主伺服器 6379 區分
port 6380
# 主伺服器 ip 埠
slaveof 127.0.0.1 6379
# 如果主伺服器配置了密碼請寫在這個配置項中
# masterauth <master-password>
##
## 接下來的選項保留預設配置即可,這裡僅做介紹
##
# 當與 master 斷開連線或正在進行同步時
# yes: 仍然正常響應客戶端請求,但可能返回過時資料
# no: 除 INFO 和 SLAVEOF 命令正常外,其它命令均返回 SYNC with master in progress 錯誤
slave-serve-stale-data yes
# 從伺服器只讀
slave-read-only yes
使用redis-server redis.conf
命令分別啟動 redis-server 例項。 使用redis-cli -p 6380
命令連線從伺服器:
127.0.0.1:6380> info Replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:16
master_sync_in_progress:0
slave_repl_offset:3640
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:0b4e100aa9e9fda54aeba2bc110316d811ac2ff6
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:3640
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:3640
127.0.0.1:6380> get a
1
127.0.0.1:6380> set a 2
(error) READONLY You can't write against a read only slave.
SLAVEOF host port
可以動態改變從伺服器所屬的 master 節點。SLAVEOF NO ONE
關閉複製功能,並從 slave 伺服器轉變回 master 伺服器,原來同步所得的資料集不會被丟棄。在當主伺服器故障時,可以使用SLAVEOF NO ONE
命令將 slave 伺服器提升為 master。
主從複製原理
SYNC
在 Redis 2.8 之前的版本里,Redis 僅支援全量複製不支援增量複製,這極大的影響了主從同步的效能。
Redis 2.8 之前的版本主從複製流程如下:
- slave 傳送 SYNC 命令給 master
- master 執行 bgsave 命令生成 rdb 檔案。於此同時,所有新的寫命令都將被寫入複製緩衝區內
- master 將 rdb 檔案傳送給 slave
- master 將緩衝區中的命令同步給 slave, 完成一次主從同步
PSYNC
Redis 2.8 開始使用 PSYNC 命令代替 SYNC 命令, PSYNC 具有全量複製和增量複製功能。
master 和 slave 節點均擁有一個 runid 作為自己的唯一標識。
master 和 slave 會各自維護一個複製偏移量,在增量複製時標識同步進度。
master 會維護一個 FIFO 的複製緩衝區(replication backlog),預設大小 1mb。
# 複製緩衝區大小
repl-backlog-size 1mb
# 當 master 不再與任何 slave 保持連線時,複製緩衝區可能被清空
# repl-backlog-ttl 用於配置從斷開連線到清空緩衝區間隔的秒數
# 0 表示永不清除緩衝區
repl-backlog-ttl 3600
接下來我們可以開始說明 PSYNC 命令執行的流程:
- slave 向 master 請求同步
- 若 slave 未與任何 master 同步過或執行了 SLAVEOF NO ONE 命令,則向 master 傳送
PSYNC ? -1
命令要求進行全量同步。 - 否則,則向 master 傳送
psync <runid> <offset>
命令要求增量同步,其中 runid 是上次同步的主伺服器的ID,offset 是同步偏移量
- 若 slave 未與任何 master 同步過或執行了 SLAVEOF NO ONE 命令,則向 master 傳送
- master 響應同步請求
- 若 slave 請求增量同步且滿足:1. runid 與自身相同;2. 同步偏移量處於自身複製緩衝區內,則響應
+continue
將複製緩衝區內的資料同步到 slave - 若 slave 請求增量同步但不同時滿足上述兩個條件或 slave 請求全量同步, 則響應
+fullresync <runid> <offset>
執行全量同步,其中 runid 是自身ID, offset 是自身同步偏移量。
- 若 slave 請求增量同步且滿足:1. runid 與自身相同;2. 同步偏移量處於自身複製緩衝區內,則響應
- 若自身版本過低不支援
PSYNC
命令則響應 error, slave 會嘗試使用 SYNC 命令進行同步。
哨兵
簡單的主從複製架構在 master 故障後會不可用,Redis 官方提供了哨兵(sentinel)機制自動實現主備切換保證高可用。
哨兵機制通過一組哨兵節點監控主從節點的執行狀態,並在主節點故障後選舉新的主節點。
哨兵節點定時執行3個任務:
哨兵節點每隔10s向主從節點傳送
INFO
命令以更新拓撲圖,自動感知新的 slave 節點。哨兵節點每隔1s向主從節點傳送
PING
命令進行心跳檢測。哨兵節點每隔2s向
__sentinel__:hello
頻道傳送自身哨兵節點資訊和自身瞭解的 master 資訊。所有哨兵節點均會訂閱該頻道,並以此更新哨兵叢集資訊。
若哨兵節點發現 master 節點心跳響應超時,則認為 master 主觀下線。此時,master 可能真的已經崩潰也可能僅僅是此哨兵節點與 master 之間出現網路故障。
認為 master 主觀下線的哨兵會向其它哨兵傳送sentinel is-master-down-by addr
詢問 master 是否下線。若半數以上的哨兵認為 master 已經下線則認為 master 客觀下線。
哨兵節點會選舉自己第一個收到的 is-master-down-by 命令的傳送者為哨兵領導者。若某一個節點得到過半投票則會成為哨兵領導者,若沒有節點得到半數以上票則會進入下一輪投票。此選舉流程與 Paxos 演算法類似。
哨兵領導者負責選擇一個slave節點提升為新 master 節點, 選擇邏輯為:
- 過濾掉不健康的 slave 節點
- 選擇
slave-priority
配置值最小的從節點。若有多個從節點 slave-priority 最小且相同則進入下一步 - 選擇複製偏移量最大的從節點,這意味著這個從節點上面的資料最完整。若仍有多個 slave 節點偏移量相同則進入下一步
- 選擇 runid 最小的從節點
新的 master 節點選出後會執行提升流程:
- 向新選出的 master 節點發出 SLAVEOF NO ONE 命令,提升為新的 master 節點
- 向其它 slave 節點發出 SLAVEOF 命令跟隨新的 master 節點
- 在哨兵叢集中將下線的 master 節點更新為下線的 slave 節點,在其回覆後命令其跟隨新的 master