在分布式系統架構設計中高可用是必須考慮的因素之一。高可用通常是指,通過設計減少系統不能提供服務的時間。而單點是系統高可用的最大的敗筆,如果單點出現問題的話,那么整個服務就不能使用了,所以應該盡量在系統設計的過程中避免單點。對於 redis 服務也是這樣,今天我們就來實現 Redis 的高可用的基礎 --> 主從配置。
主從概念
有多台 Redis 服務器(至少兩台或以上),其中一台是主服務器(master) 負責寫指令的操作,其他都是從服務器(slave)負責讀指令的操作。
主從服務器之間會進行數據的同步(即:主服務器會將數據同步到從服務器去),保證數據是一致的。從服務器將讀指令操作分流減少服務器壓力,而且其中一台從服務器出現錯誤,不影響其他從服務器的使用。
如果是主服務器出現問題,那么就需要實現故障轉移(手動/自動)將其中一台從服務器提升為主服務器,其他從服務器都從這個新的主服務器同步數據。
從上面這段話中我們可以知道 Redis 實現高可用的兩個功能點:
- 主從復制
- 故障轉移
主從復制
主從復制流程
1)從服務器連接主服務器,發送SYNC命令;
2)主服務器接收到SYNC命名后,開始執行BGSAVE命令生成RDB文件並使用緩沖區記錄此后執行的所有寫命令;
3)主服務器BGSAVE執行完后,向所有從服務器發送快照文件,並在發送期間繼續記錄被執行的寫命令;
4)從服務器收到快照文件后丟棄所有舊數據(如果有的話),載入收到的快照;
5)主服務器快照發送完畢后開始向從服務器發送緩沖區中的寫命令;
6)從服務器完成對快照的載入,開始接收命令請求,並執行來自主服務器緩沖區的寫命令;
7) 主服務器接收到的每一個寫指令都向從服務器發送,從服務器接收並執行收到的寫命令。
注:主從服務器剛連接的時候,會進行全量同步;全同步結束后,進行增量同步。當然,如果有需要,slave 在任何時候都可以發起全量同步。redis 策略是,無論如何,首先會嘗試進行增量同步,如不成功,要求從機進行全量同步。
主從復制實現方式
- slaveof 命令
- 配置文件
主從復制實踐
前期准備
1)復制兩份 redis-6379.conf
配置文件,分別命名為 redis-7000.conf
和 redis-7001.conf
2) 編輯 redis-7000.conf
和 redis-7001.conf
文件將端口分別修改為 7000 和7001並將日志文件格式以端口命名,以 redis-6379.conf
為例:
# 允許遠程訪問
## bind 127.0.0.1
protected-mode no
# 服務端口號
port 6379
# 以守護進程啟動
daemonize yes
# 日志文件名稱
logfile "redis-6379.log"
# 啟用日志文件
syslog-enabled yes
3)分別啟動 6379,7000 和 7001 Redis 服務
主從復制實踐之 slaveof 命令
我們將會讓 6379
成為主服務器,7000
和 7001
為從服務器。
1)進入 7000
並將其設置為 6379
的從服務器
[root@VM_0_15_centos redis4]# ./redis-cli -p 7000
127.0.0.1:7000> SLAVEOF 127.0.0.1 6379
OK
127.0.0.1:7000> exit
2)查看 6379 信息
[root@VM_0_15_centos redis4]# ./redis-cli -p 6379 info replication
輸出如下:
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=7000,state=online,offset=210,lag=0
master_replid:9fe7d7ca84fe33cf8e916be1ee3a1b0423675ef8
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:210
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:210
2)查看 7000 信息
[root@VM_0_15_centos redis4]# ./redis-cli -p 7000 info replication
輸出如下:
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:336
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:9fe7d7ca84fe33cf8e916be1ee3a1b0423675ef8
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:336
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:336
可以看到 6379
的 role
是 master
表示為主服務器,而 7000
的 role
是 slave
表示為從服務器,證明我們剛剛的配置成功了。
3)我們嘗試在 6379
設置一些值
[root@VM_0_15_centos redis4]# ./redis-cli -p 6379
127.0.0.1:6379> set name MarkLogZhu
OK
127.0.0.1:6379> set age 18
OK
然后進 7000
看看是不是會將數據同步過來(執行此操作前,先將兩邊的數據都清空)
[root@VM_0_15_centos redis4]# ./redis-cli -p 7000
127.0.0.1:7000> get name
"MarkLogZhu"
127.0.0.1:7000> get age
"18"
可以看到數據同步過來了,我們試試在 7000
上寫入下數據
127.0.0.1:7000> set name1 123
(error) READONLY You can't write against a read only slave.
可以看到從服務器上不能執行寫指令操作的。
- 啟動
7001
並將其也設置為6379
的從服務器,看看是否也會同步數據過來
127.0.0.1:7001> SLAVEOF 127.0.0.1 6379
OK
127.0.0.1:7001> get name
"MarkLogZhu"
127.0.0.1:7001> get age
"18"
可以看到會將 6379
之前的數據也同步過來。
- 在
7000
上將主從綁定取消
[root@VM_0_15_centos redis4]# ./redis-cli -p 7000
127.0.0.1:7000> slaveof no one
OK
6)查看主服務器信息
[root@VM_0_15_centos redis4]# ./redis-cli -p 6379 info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=7001,state=online,offset=70,lag=0
master_replid:b3a6488a9667dd3f80c9549146ed012a4ec1d465
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:70
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:70
[root@VM_0_15_centos redis4]#
可以看到就一個從服務器,端口號為 7001
,要注意的是 7000
上的數據還是存在的不會主動清空。
主從復制實踐之配置文件
1)將 Redis 服務都關閉
[root@VM_0_15_centos redis4]# ps -ef |grep redis
root 4220 1 0 15:10 ? 00:00:00 ./redis-server *:6379
root 4238 1 0 15:11 ? 00:00:00 ./redis-server *:7000
root 4325 1 0 15:13 ? 00:00:00 ./redis-server *:7001
root 4830 1579 0 15:18 pts/0 00:00:00 grep --color=auto redis
[root@VM_0_15_centos redis4]# ./redis-cli -p 6379 shutdown
[root@VM_0_15_centos redis4]# ./redis-cli -p 7000 shutdown
[root@VM_0_15_centos redis4]# ./redis-cli -p 7001 shutdown
- 編輯
redis-7000.conf
增加如下內容:
slaveof 127.0.0.1 6379
- 編輯
redis-7001.conf
增加如下內容:
slaveof 127.0.0.1 6379
- 依次啟動服務
[root@VM_0_15_centos redis4]# ./redis-server redis-6379.conf
[root@VM_0_15_centos redis4]# ./redis-server redis-7000.conf
[root@VM_0_15_centos redis4]# ./redis-server redis-7001.conf
- 查看主從狀態
[root@VM_0_15_centos redis4]# ./redis-cli -p 6379 info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=7000,state=online,offset=84,lag=1
slave1:ip=127.0.0.1,port=7001,state=online,offset=84,lag=0
master_replid:645cc6d0869b23998c1265af1d556e671330d2f0
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:84
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:84
可以看到服務一啟動就已經自動配置為一主二從了,在實際項目中通過配置文件的形式更常見。
主從故障
什么是主從故障
主從故障就是主服務器出現問題,導致無法執行寫命令。我們來演示下主服務器故障看看
- 啟動三個服務
[root@VM_0_15_centos redis4]# ./redis-server redis-6379.conf
[root@VM_0_15_centos redis4]# ./redis-server redis-7000.conf
[root@VM_0_15_centos redis4]# ./redis-server redis-7001.conf
- 將主服務器進程關閉(模擬突然出現問題)
[root@VM_0_15_centos redis4]# ps -ef |grep redis
root 5538 1 0 15:25 ? 00:00:00 ./redis-server *:6379
root 5545 1 0 15:25 ? 00:00:00 ./redis-server *:7000
root 5551 1 0 15:25 ? 00:00:00 ./redis-server *:7001
root 6179 1579 0 15:34 pts/0 00:00:00 grep --color=auto redis
[root@VM_0_15_centos redis4]# kill -9 5538
3)查看主從狀態
[root@VM_0_15_centos redis4]# ./redis-cli -p 7000 info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:891
master_link_down_since_seconds:216
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:645cc6d0869b23998c1265af1d556e671330d2f0
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:891
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:891
可以看到 Redis 是無法自動切換的,只能通過人為執行命令解除主從配置,並將其中一個從服務器提升為主服務器。除了人為來操作之外 Redis 提供了一個 sentinel
(哨兵)來實現故障自動轉移。
Sentinel(哨兵)
什么是 sentinel
sentinel
就相當於是一個哨兵,當 Redis
主服務器出現故障的時候,代替我們人為的將其中一個服務器提升為主服務器,當主服務器修復故障后自動降格為從服務器加入進來。
多個哨兵節點(至少三個,並且是奇數)會使用 Raft算法(選舉算法)實現選舉機制,選出一個哨兵節點當作領導者來完成轉移和通知。
Sentinel 是如何實現故障轉移的?
Sentinel 做到故障轉移是基於三個定時任務的執行:
- 每隔 10s 每個 sentinel 會對 master 節點和 slave 節點執行 info 命令
作用就是發現 slave 節點,並且確認主從關系,因為 redis-Sentinel 節點啟動的時候是知道master節點的,只是沒有配置相應的 slave 節點的信息
- 每隔兩秒,sentinel 都會通過 master節點內部的 channel 來交換信息(基於發布訂閱)
作用是通過master節點的頻道來交互每個Sentinel對master節點的判斷信息
- 每隔一秒每個 sentinel 對其他的redis節點(master,slave,sentinel)執行ping操作
對於 master 來說若超過 30s 內沒有回復,就對該 master進行主觀下線並詢問其他的 Sentinel節點是否可以客觀下線
客觀下線和主觀下線
- 主觀下線:每個Sentinel節點對Redis節點失敗的“偏見”
- 客觀下線:所有Sentinel節點對Redis節點失敗達成共識
在進行領導者選舉之后進行故障轉移,這個過程由成為 leader 的 Sentinel 的來完成。
Sentinel 的選舉流程(Raft算法)
1)每個做出主觀下線操作的 Sentinel 節點向其他 Sentinel 節點發送命令,要求成為領導者。
2)收到命令的 Sentinel 節點默認同意第一個收到的請求,其他拒絕
3)當其中一個 Sentinel 節點獲取到的票數一次超過 Sentinel 集合半數並且超過配置里的 quorum,那么它將成為領導者。
4)如果此過程中存在多個 Sentinel 節點成為領導者,那么將等待一段時間后重新進行選舉。
故障轉移
1)從 slave
節點中選出一個 “合適的” 節點作為新的 master
節點
2)對選中的 slave
節點執行 slaveof no one
命令,讓其成為 master
節點
3)對剩余的 slave
節點發送命令,讓他們成為新的 master
節點的slave
節點,復制規則和 parallel-syncs
參數有關。
4)將原來的 master
節點配置為 slave
,並保持對其的 "關注",當它恢復后令它去復制新的 master
節點。
“合適的” slave 節點
1.選擇 slave-priority(slave節點優先級)最高的 slave
節點,如果存在則返回,否則繼續。
2.選擇復制偏移量最大的 slave
節點(復制的最完整的),如果存在則返回,否則繼續。
3.選擇 runId
最小的 slave
節點。
實踐
1)編輯 sentinel 配置文件
vim sentinel-26379.conf
內容如下:
# 配置 sentinel 端口號
port 26379
# 以守護進程啟動
daemonize yes
# 綁定只在本地使用
bind 127.0.0.1
# 日志文件名稱
logfile "sentinel_26379.log"
# 日志文件存放地址
dir "./"
# 監控 master 名字叫做 mymaster,地址是 127.0.0.1 端口號是 6379,1 表示有幾個 sentinel 認為該 master 出現故障,觸發主備切換動作
sentinel monitor mymaster 127.0.0.1 6379 1
# 3000 毫秒沒有響應就判斷出現故障
sentinel down-after-milliseconds mymaster 30000
# 主備切換時,多少個從服務器同步更新數據,數值越小越好
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
- 啟動 sentinel
[root@VM_0_15_centos redis4]# ./redis-sentinel sentinel-26379.conf
3)將主服務器進程關閉(模擬突然出現問題)
[root@VM_0_15_centos redis4]# kill -9 9149
[root@VM_0_15_centos redis4]# ps -ef |grep redis
root 5550 1 0 16:17 ? 00:00:00 ./redis-server *:7000
root 5551 1 0 15:25 ? 00:00:03 ./redis-server *:7001
root 13044 1 0 16:14 ? 00:00:00 ./redis-sentinel 127.0.0.1:26379 [sentinel]
root 13306 1579 0 16:17 pts/0 00:00:00 grep --color=auto redis
- 查看
7000
主從信息
[root@VM_0_15_centos redis4]# ./redis-cli -p 7000 info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:7001
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:17931
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:0219097153045d031f7bcfdccebd52b336e542e5
master_replid2:66aa7374642058a6bb26c9c0258e3c8b2fc7d760
master_repl_offset:17931
second_repl_offset:12757
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:17931
[root@VM_0_15_centos redis4]#
可以看到現在主服務器是端口 7001
了
5)將 6379
端口服務重新啟動,然后查看 7001
主從狀態
[root@VM_0_15_centos redis4]# ./redis-cli -p 7001 info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=7000,state=online,offset=29364,lag=1
slave1:ip=127.0.0.1,port=6379,state=online,offset=29364,lag=0
master_replid:0219097153045d031f7bcfdccebd52b336e542e5
master_replid2:66aa7374642058a6bb26c9c0258e3c8b2fc7d760
master_repl_offset:29364
second_repl_offset:12757
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset
可以看到 6379
端口也變成了一個從服務器添加進來了。要注意的是這個時候配置文件已經被 sentinel
修改了,就算你重啟服務,也是按目前的主從來設置,而不是重新以 6379
為主服務器,我們可以來看看:
[root@VM_0_15_centos redis4]# ./redis-cli -p 6379 shutdown
[root@VM_0_15_centos redis4]# ./redis-cli -p 7000 shutdown
[root@VM_0_15_centos redis4]# ./redis-cli -p 7001 shutdown
[root@VM_0_15_centos redis4]# ./redis-server redis-6379.conf
[root@VM_0_15_centos redis4]# ./redis-server redis-7000.conf
[root@VM_0_15_centos redis4]# ./redis-server redis-7001.conf
[root@VM_0_15_centos redis4]# ./redis-cli -p 6379 info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:7001
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:835
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:f3ed896873d5a9024a8139dac59b86381e53ad08
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:835
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:835
重啟服務后我們可以看到 6379
還是充當從服務器的角色。