從零開始Redis(八):Redis主從與哨兵
Redis主從與哨兵
前言
本節將介紹redis叢集的主從模式與哨兵模式
“高可用性”(High Availability)通常來描述一個系統經過專門的設計,從而減少停工時間,而保持其服
務的高度可用性。CAP的A AP模型
單機的Redis是無法保證高可用性的,當Redis伺服器宕機後,即使在有持久化的機制下也無法保證不丟失資料。
所以我們採用Redis多機和叢集的方式來保證Redis的高可用性。
單程序+單執行緒 + 多機 (叢集)
主從複製
Redis支援主從複製功能,可以通過執行slaveof(Redis5以後改成replicaof)或者在配置檔案中設定slaveof(Redis5以後改成replicaof)來開啟複製功能。
上圖為一主一從
上圖為一主多從
(傳遞複製)
- 主對外從對內,主可寫從不可寫
- 主掛了,從不可為主
主從配置
- 主Redis配置
無需特殊配置 - 從Redis配置
修改從伺服器上的 redis.conf 檔案:
# slaveof <masterip> <masterport>
# 表示當前【從伺服器】對應的【主伺服器】的IP是192.168.10.135,埠是6379。
replicaof 127.0.0.1 6379
作用
- 讀寫分離
- 一主多從,主從同步
- 主負責寫,從負責讀
- 提升Redis的效能和吞吐量
- 存在主從的資料一致性問題
- 資料容災
- 從機是主機的備份
- 主機宕機,從機可讀不可寫
- 預設情況下主機宕機後,從機不可為主機
- 利用哨兵可以實現主從切換,做到高可用
原理與實現
複製流程
- 儲存主節點資訊
當客戶端向從伺服器傳送slaveof(replicaof) 主機地址(127.0.0.1) 埠(6379)時:從伺服器將主機ip(127.0.0.1)和埠(6379)儲存到redisServer的masterhost和masterport中。
從伺服器將向傳送SLAVEOF命令的客戶端返回OK,表示複製指令已經被接收,而實際上覆制工作是在OK返回之後進行。 - 建立socket連線
slaver與master建立socket連線
slaver關聯檔案事件處理器
該處理器接收RDB檔案(全量複製)、接收Master傳播來的寫命令(增量複製)
主伺服器accept從伺服器Socket連線後,建立相應的客戶端狀態。相當於從伺服器是主伺服器的Client端。
-
傳送ping命令
Slaver向Master傳送ping命令
1.檢測socket的讀寫狀態
2.檢測Master能否正常處理
Master的響應:- 傳送“pong” , 說明正常
- 返回錯誤,說明Master不正常
- timeout,說明網路超時
-
許可權驗證
主從正常連線後,進行許可權驗證
主未設定密碼(requirepass=“”) ,從也不用設定密碼(masterauth=“”)
主設定密碼(requirepass!=""),從需要設定密碼(masterauth=主的requirepass的值)
或者從通過auth命令向主傳送密碼
-
傳送埠資訊
在身份驗證步驟之後,從伺服器將執行命令REPLCONF listening-port ,向主伺服器傳送從伺服器的監聽埠號。 -
同步資料
Redis 2.8之後分為全量同步和增量同步 -
命令傳播
當同步資料完成後,主從伺服器就會進入命令傳播階段,主伺服器只要將自己執行的寫命令傳送給從伺服器,而從伺服器只要一直執行並接收主伺服器發來的寫命令 -
同步資料集
1.Redis 2.8以前使用SYNC命令同步複製
實現方式:Redis的同步功能分為同步(sync)和命令傳播(command propagate)。
同步操作:- 通過從伺服器傳送到SYNC命令給主伺服器
- 主伺服器生成RDB檔案併發送給從伺服器,同時傳送儲存所有寫命令給從伺服器
- 從伺服器清空之前資料並執行解釋RDB檔案
- 保持資料一致(還需要命令傳播過程才能保持一致)
. 命令傳播操作:
同步操作完成後,主伺服器執行寫命令,該命令傳送給從伺服器並執行,使主從儲存一致。
缺陷:
沒有全量同步和增量同步的概念,從伺服器在同步時,會清空所有資料。
主從伺服器斷線後重複製,主伺服器會重新生成RDB檔案和重新記錄緩衝區的所有命令,並全量同步到從伺服器上。2.Redis 2.8之後採用PSYNC命令替代SYNC
實現方式:-
在Redis 2.8之後使用PSYNC命令,具備完整重同步和部分重同步模式。
-
Redis 的主從同步,分為全量同步和增量同步。
-
只有從機第一次連線上主機是全量同步。
-
斷線重連有可能觸發全量同步也有可能是增量同步( master 判斷 runid 是否一致)
-
除此之外的情況都是增量同步。
全量同步:
Redis 的全量同步過程主要分三個階段:- 同步快照階段: Master 建立併發送快照RDB給 Slave , Slave 載入並解析快照。 Master 同時將此階段所產生的新的寫命令儲存到緩衝區。
- 同步寫緩衝階段: Master 向 Slave 同步儲存在緩衝區的寫操作命令
- 同步增量階段: Master 向 Slave 同步寫操作命令
增量同步: - Redis增量同步主要指Slave完成初始化後開始正常工作時, Master 發生的寫操作同步到 Slave 的過程。
2.通常情況下, Master 每執行一個寫命令就會向 Slave 傳送相同的寫命令,然後 Slave 接收並執行。
-
心跳檢測
在命令傳播階段,從伺服器預設會以每秒一次的頻率向主伺服器傳送命令:
replconf ack <replication_offset>
#ack :應答
#replication_offset:從伺服器當前的複製偏移量
主要作用有三個:
- 檢測主從的連線狀態
檢測主從伺服器的網路連線狀態
通過向主伺服器傳送INFO replication命令,可以列出從伺服器列表,可以看出從最後一次向主傳送命令距離現在過了多少秒。lag的值應該在0或1之間跳動,如果超過1則說明主從之間的連線有故障。 - 輔助實現min-slaves
Redis可以通過配置防止主伺服器在不安全的情況下執行寫命令
min-slaves-to-write 3 (min-replicas-to-write 3 )
min-slaves-max-lag 10 (min-replicas-max-lag 10)
上面的配置表示:從伺服器的數量少於3個,或者三個從伺服器的延遲(lag)值都大於或等於10秒時,主伺服器將拒絕執行寫命令。這裡的延遲值就是上面INFOreplication命令的lag值。 - 檢測命令丟失
如果因為網路故障,主伺服器傳播給從伺服器的寫命令在半路丟失,那麼當從伺服器向主伺服器傳送REPLCONF ACK命令時,主伺服器將發覺從伺服器當前的複製偏移量少於自己的複製偏移量,然後主伺服器就會根據從伺服器提交的複製偏移量,在複製積壓緩衝區裡面找到從伺服器缺少的資料,並將這些資料重新發送給從伺服器。(補發) 網路不斷
增量同步:網斷了,再次連線時
哨兵模式
哨兵(sentinel)是Redis的高可用性(High Availability)的解決方案:
由一個或多個sentinel例項組成sentinel叢集可以監視一個或多個主伺服器和多個從伺服器。當主伺服器進入下線狀態時,sentinel可以將該主伺服器下的某一從伺服器升級為主伺服器繼續提供服務,從而保證redis的高可用性。
部署方案
搭建配置
在一臺機器上採用偽分散式的方式部署。(生產環境應該是多臺機器)
根據上面的部署方案搭建如下:
Redis-Master :127.0.0.1 6379
採用安裝的方式,正常安裝和配置
#1 安裝redis5.0
mkdir redis-master
make install PREFIX=/var/redis-ms/redis-master
cp /var/redis-5.0.5/redis.conf /var/redis-ms/redis-master/bin
#2 修改redis.conf
# 將`daemonize`由`no`改為`yes`
daemonize yes
# 預設繫結的是迴環地址,預設不能被其他機器訪問
# bind 127.0.0.1
# 是否開啟保護模式,由yes該為no
protected-mode no
Redis-Slaver1:127.0.0.1 6380
#安裝redis-slaver1
mkdir redis-slaver1
cp -r /var/redis-ms/redis-master/* /var/redis-ms/redis-slaver1
#修改配置檔案
vim /var/redis-ms/redis-slaver1/redis.conf
port 6380
replicaof 127.0.0.1 6379
Redis-Slaver2:127.0.0.1 6381
#安裝redis-slaver2
mkdir redis-slaver2
cp -r /var/redis-ms/redis-master/* /var/redis-ms/redis-slaver2
#修改配置檔案
vim /var/redis-ms/redis-slaver2/redis.conf
port 6381
replicaof 127.0.0.1 6379
Redis-Sentinel1:127.0.0.1 26379
#安裝redis-sentinel1
mkdir redis-sentinel1
cp -r /var/redis-ms/redis-master/* /var/redis-ms/redis-sentinel1
#拷貝sentinel.conf 配置檔案並修改
cp /var/redis-5.0.5/sentinel.conf /var/redis-ms/redis-sentinel1
# 哨兵sentinel例項執行的埠 預設26379
port 26379
# 將`daemonize`由`no`改為`yes`
daemonize yes
# 哨兵sentinel監控的redis主節點的 ip port
# master-name 可以自己命名的主節點名字 只能由字母A-z、數字0-9 、這三個字元".-_"組成。
# quorum 當這些quorum個數sentinel哨兵認為master主節點失聯 那麼這時 客觀上認為主節點失聯了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# 當在Redis例項中開啟了requirepass foobared 授權密碼 這樣所有連線Redis例項的客戶端都要提
供密碼
# 設定哨兵sentinel 連線主從的密碼 注意必須為主從設定一樣的驗證密碼
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之後 主節點沒有應答哨兵sentinel 此時 哨兵主觀上認為主節點下線 預設30秒,改成3秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 3000
# 這個配置項指定了在發生failover主備切換時最多可以有多少個slave同時對新的master進行 同步,
這個數字越小,完成failover所需的時間就越長,
但是如果這個數字越大,就意味著越 多的slave因為replication而不可用。
可以通過將這個值設為 1 來保證每次只有一個slave 處於不能處理命令請求的狀態。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
# 故障轉移的超時時間 failover-timeout 可以用在以下這些方面:
#1. 同一個sentinel對同一個master兩次failover之間的間隔時間。
#2. 當一個slave從一個錯誤的master那裡同步資料開始計算時間。直到slave被糾正為向正確的master
那裡同步資料時。
#3.當想要取消一個正在進行的failover所需要的時間。
#4.當進行failover時,配置所有slaves指向新的master所需的最大時間。不過,即使過了這個超時,
slaves依然會被正確配置為指向master,但是就不按parallel-syncs所配置的規則來了
# 預設三分鐘
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
Redis-Sentinel2:127.0.0.1 26380
#安裝redis-sentinel2
mkdir redis-sentinel2
cp -r /var/redis-ms/redis-sentinel1/* /var/redis-ms/redis-sentinel2
#修改sentinel.conf
vim /var/redis-ms/redis-sentinel2/sentinel.conf
port 26380
Redis-Sentinel3:127.0.0.1 26381
#安裝redis-sentinel3
mkdir redis-sentinel3
cp -r /var/redis-ms/redis-sentinel1/* /var/redis-ms/redis-sentinel3
#修改sentinel.conf
vim /var/redis-ms/redis-sentinel3/sentinel.conf
port 26381
配置好後依次執行
redis-master、redis-slaver1、redis-slaver2、redis-sentinel1、redis-sentinel2、redis-sentinel3
#啟動redis-master和redis-slaver
在redis-master目錄下 ./redis-server redis.conf
在redis-slaver1目錄下 ./redis-server redis.conf
在redis-slaver2目錄下 ./redis-server redis.conf
#啟動redis-sentinel
在redis-sentinel1目錄下 ./redis-sentinel sentinel.conf
在redis-sentinel2目錄下 ./redis-sentinel sentinel.conf
在redis-sentinel3目錄下 ./redis-sentinel sentinel.conf
#檢視啟動狀態
[root@localhost bin]# ps -ef |grep redis
root 3602 1 0 01:33 ? 00:00:00 ./redis-server *:6379
root 3647 1 0 01:37 ? 00:00:00 ./redis-server *:6380
root 3717 1 0 01:40 ? 00:00:00 ./redis-server *:6381
root 3760 1 0 01:42 ? 00:00:00 ./redis-sentinel *:26379
[sentinel]
root 3765 1 0 01:42 ? 00:00:00 ./redis-sentinel *:26380
[sentinel]
root 3770 1 0 01:42 ? 00:00:00 ./redis-sentinel *:26381
[sentinel]
root 3783 2261 0 01:42 pts/0 00:00:00 grep --color=auto redis
執行流程
啟動並初始化Sentinel
Sentinel是一個特殊的Redis伺服器
不會進行持久化
Sentinel例項啟動後
每個Sentinel會建立2個連向主伺服器的網路連線
命令連線:用於向主伺服器傳送命令,並接收響應;
訂閱連線:用於訂閱主伺服器的—sentinel—:hello頻道。
獲取主伺服器資訊
Sentinel預設每10s一次,向被監控的主伺服器傳送info命令,獲取主伺服器和其下屬從伺服器的資訊
127.0.0.1:6379> info
# Server
redis_version:5.0.5
os:Linux 3.10.0-229.el7.x86_64 x86_64
run_id:a4e06ab61b4116660aa37b85079ed482b0b695b1
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=1571684,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=1571551,lag=1
master_replid:366322125dd7dc9bc95ed3467cfec841c112e207
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1571684
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:523109
repl_backlog_histlen:1048576
獲取從伺服器資訊
當Sentinel發現主伺服器有新的從伺服器出現時,Sentinel還會向從伺服器建立命令連線和訂閱連線。在命令連線建立之後,Sentinel還是預設10s一次,向從伺服器傳送info命令,並記錄從伺服器的資訊。
# Server
redis_version:5.0.5
os:Linux 3.10.0-229.el7.x86_64 x86_64
run_id:e289b3286352aaf8cc9f1ac7ebcc6d36131b8321
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:1699595
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:366322125dd7dc9bc95ed3467cfec841c112e207
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1699595
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:651020
repl_backlog_histlen:1048576
向主伺服器和從伺服器傳送訊息(以訂閱的方式)
預設情況下,Sentinel每2s一次,向所有被監視的主伺服器和從伺服器所訂閱的—sentinel—:hello頻道 上傳送訊息,訊息中會攜帶Sentinel自身的資訊和主伺服器的資訊。
PUBLISH _sentinel_:hello "< s_ip > < s_port >< s_runid >< s_epoch > < m_name > <
m_ip >< m_port ><m_epoch>"
接收來自主伺服器和從伺服器的頻道資訊
當Sentinel與主伺服器或者從伺服器建立起訂閱連線之後,Sentinel就會通過訂閱連線,向伺服器傳送以下命令:
subscribe —sentinel—:hello
Sentinel彼此之間只建立命令連線,而不建立訂閱連線,因為Sentinel通過訂閱主伺服器或從伺服器,就可以感知到新的Sentinel的加入,而一旦新Sentinel加入後,相互感知的Sentinel通過命令連線來通訊就可以了。
檢測主觀下線狀態
Sentinel每秒一次向所有與它建立了命令連線的例項(主伺服器、從伺服器和其他Sentinel)傳送PING命令
例項在down-after-milliseconds毫秒內返回無效回覆(除了+PONG、-LOADING、-MASTERDOWN外)
例項在down-after-milliseconds毫秒內無回覆(超時)
Sentinel就會認為該例項主觀下線(SDown)
檢查客觀下線狀態
當一個Sentinel將一個主伺服器判斷為主觀下線後
Sentinel會向同時監控這個主伺服器的所有其他Sentinel傳送查詢命令
主機的
SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>
其他Sentinel回覆
<down_state>< leader_runid >< leader_epoch >
判斷它們是否也認為主伺服器下線。如果達到Sentinel配置中的quorum數量的Sentinel例項都判斷主伺服器為主觀下線,則該主伺服器就會被判定為客觀下線(ODown)。
選舉Leader Sentinel
當一個主伺服器被判定為客觀下線後,監視這個主伺服器的所有Sentinel會通過選舉演算法(raft),選出一個Leader Sentinel去執行failover(故障轉移)操作。
哨兵leader選舉
Raft
Raft協議是用來解決分散式系統一致性問題的協議。
Raft協議描述的節點共有三種狀態:Leader, Follower, Candidate。
term:Raft協議將時間切分為一個個的Term(任期),可以認為是一種“邏輯時間”。
選舉流程:
-
Raft採用心跳機制觸發Leader選舉
-
系統啟動後,全部節點初始化為Follower,term為0。
-
節點如果收到了RequestVote或者AppendEntries,就會保持自己的Follower身份
-
節點如果一段時間內沒收到AppendEntries訊息,在該節點的超時時間內還沒發現Leader,Follower就會轉換成Candidate,自己開始競選Leader
-
一旦轉化為Candidate,該節點立即開始下面幾件事情:
- 增加自己的term。
- 啟動一個新的定時器
- 給自己投一票
- 向所有其他節點發送RequestVote,並等待其他節點的回覆
-
如果在計時器超時前,節點收到多數節點的同意投票,就轉換成Leader。同時向所有其他節點發送AppendEntries,告知自己成為了Leader。
每個節點在一個term內只能投一票,採取先到先得的策略,Candidate前面說到已經投給了自己,Follower會投給第一個收到RequestVote的節點。
Raft協議的定時器採取隨機超時時間,這是選舉Leader的關鍵。
在同一個term內,先轉為Candidate的節點會先發起投票,從而獲得多數票。
Sentinel的leader選舉流程
- 某Sentinel認定master客觀下線後,該Sentinel會先看看自己有沒有投過票,如果自己已經投過票給其他Sentinel了,在一定時間內自己就不會成為Leader。
- 如果該Sentinel還沒投過票,那麼它就成為Candidate
- Sentinel需要完成幾件事情:
- 更新故障轉移狀態為start
- 當前epoch加1,相當於進入一個新term,在Sentinel中epoch就是Raft協議中的term
- 向其他節點發送 is-master-down-by-addr 命令請求投票。命令會帶上自己的epoch
- 給自己投一票(leader、leader_epoch)
- 當其它哨兵收到此命令時,可以同意或者拒絕它成為領導者;(通過判斷epoch)
- Candidate會不斷的統計自己的票數,直到他發現認同他成為Leader的票數超過一半而且超過它配置的quorum,這時它就成為了Leader。
- 其他Sentinel等待Leader從slave選出master後,檢測到新的master正常工作後,就會去掉客觀下線的標識。
故障轉移
當選舉出Leader Sentinel後,Leader Sentinel會對下線的主伺服器執行故障轉移操作,主要有三個步驟:
- 它會將失效 Master 的其中一個 Slave 升級為新的 Master , 並讓失效 Master 的其他 Slave 改為複製新的 Master
- 當客戶端試圖連線失效的 Master 時,叢集也會向客戶端返回新 Master 的地址,使得叢集可以使用現在的 Master 替換失效 Master 。
- Master 和 Slave 伺服器切換後, Master 的 redis.conf 、 Slave 的 redis.conf 和sentinel.conf 的配置檔案的內容都會發生相應的改變,即, Master 主伺服器的 redis.conf配置檔案中會多一行 replicaof 的配置, sentinel.conf 的監控目標會隨之調換。
主伺服器的選擇
哨兵leader根據以下規則從客觀下線的主伺服器的從伺服器中選擇出新的主伺服器。
- 過濾掉主觀下線的節點
- 選擇slave-priority最高的節點,如果由則返回沒有就繼續選擇
- 選擇出複製偏移量最大的系節點,因為複製偏移量越大則資料複製的越完整,如果由就返回了,沒有就繼續
- 選擇run_id最小的節點,因為run_id越小說明重啟次數越少
總結
本節主要介紹了redis的主從模式和哨兵模式,現在哨兵模式已不再主流。因為reids3.0 之後官方提供了完整的叢集解決方案。