1. 程式人生 > >Redis主從複製介紹

Redis主從複製介紹

1.Redis主從複製

Redis複製功能簡單介紹

1)使用非同步複製。
2)一個主伺服器可以有多個從伺服器。
3)從伺服器也可以有自己的從伺服器。
4)複製功能不會阻塞主伺服器。
5)可以通過複製功能來讓主伺服器免於執行持久化操作,由從伺服器去執行持久化操作即可。

Redis複製功能介紹(重點了解)

1)Redis 使用非同步複製。從 Redis2.8開始,從伺服器會以每秒一次的頻率向主伺服器報告複製流(replication stream)的處理進度。

2)一個主伺服器可以有多個從伺服器。

3)不僅主伺服器可以有從伺服器,從伺服器也可以有自己的從伺服器,多個從伺服器之間可以構成一個圖狀結構。

4)複製功能不會阻塞主伺服器:即使有一個或多個從伺服器正在進行初次同步, 主伺服器也可以繼續處理命令請求。

5)複製功能也不會阻塞從伺服器:只要在 redis.conf 檔案中進行了相應的設定, 即使從伺服器正在進行初次同步, 伺服器也可以使用舊版本的資料集來處理命令查詢。

6)在從伺服器刪除舊版本資料集並載入新版本資料集的那段時間內,連線請求會被阻塞。

7)還可以配置從伺服器,讓它在與主伺服器之間的連線斷開時,向客戶端傳送一個錯誤。

8)複製功能可以單純地用於資料冗餘(data redundancy),也可以通過讓多個從伺服器處理只讀命令請求來提升擴充套件性(scalability): 比如說,繁重的SORT

命令可以交給附屬節點去執行。

9)可以通過複製功能來讓主伺服器免於執行持久化操作:只要關閉主伺服器的持久化功能,然後由從伺服器去執行持久化操作即可。

關閉主伺服器持久化時,複製功能的資料安全

1.當配置Redis複製功能時,強烈建議開啟主伺服器的持久化功能。 否則的話,由於延遲等問題,部署的服務應該要避免自動拉起。

2.為了幫助理解主伺服器關閉持久化時自動拉起的危險性,參考一下以下會導致主從伺服器資料全部丟失的例子:

1)假設節點A為主伺服器,並且關閉了持久化。並且節點B和節點C從節點A複製資料

2)節點A崩潰,然後由自動拉起服務重啟了節點A. 由於節點A的持久化被關閉了,所以重啟之後沒有任何資料

3)節點B和節點C將從節點A複製資料,但是A的資料是空的,於是就把自身儲存的資料副本刪除。

結論:

1)在關閉主伺服器上的持久化,並同時開啟自動拉起程序的情況下,即便使用Sentinel來實現Redis的高可用性,也是非常危險的。因為主伺服器可能拉起得非常快,以至於Sentinel在配置的心跳時間間隔內沒有檢測到主伺服器已被重啟,然後還是會執行上面的資料丟失的流程。

2)無論何時,資料安全都是極其重要的,所以應該禁止主伺服器關閉持久化的同時自動拉起。

主從複製的原理

1)從伺服器向主伺服器傳送 SYNC 命令。

2)接到 SYNC 命令的主伺服器會呼叫BGSAVE 命令,建立一個 RDB 檔案,並使用緩衝區記錄接下來執行的所有寫命令。

3)當主伺服器執行完 BGSAVE 命令時,它會向從伺服器傳送 RDB 檔案,而從伺服器則會接收並載入這個檔案。

4)主伺服器將緩衝區儲存的所有寫命令傳送給從伺服器執行。

開啟主從複製

#開啟主從複製(在從庫上執行)
127.0.0.1:6379> SLAVEOF 10.0.0.51 6379
OK
#檢視主從資訊
127.0.0.1:6379> INFO replication
# Replication
role:slave //角色是從庫
master_host:10.0.0.51 //主庫IP是10.0.0.51
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:1
master_link_down_since_seconds:1540419761
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

2.Redis主從複製工作機制

SYNC命令執行示例

命令傳播

在主從伺服器完成同步之後,主伺服器每執行一個寫命令,它都會將被執行的寫命令傳送給從伺服器執行,這個操作被稱為“命令傳播”(command propagate)。

命令傳播是一個持續的過程:只要複製仍在繼續,命令傳播就會一直進行,使得主從伺服器的狀態可以一直保持一致。

SYNC與PSYNC

1)在 Redis2.8版本之前,斷線之後重連的從伺服器總要執行一次完整重同步(fullresynchronization)操作。

2)從 Redis2.8開始,Redis使用PSYNC命令代替SYNC命令。

3)PSYNC比起SYNC的最大改進在於PSYNC實現了部分重同步(partial resync)特性:
在主從伺服器斷線並且重新連線的時候,只要條件允許,PSYNC可以讓主伺服器只向從伺服器同步斷線期間缺失的資料,而不用重新向從伺服器同步整個資料庫。

注:
PSYNC這個特性需要主伺服器為被髮送的複製流建立一個記憶體緩衝區(in-memory backlog), 並且主伺服器和所有從伺服器之間都記錄一個複製偏移量(replication offset)和一個主伺服器 ID(master run id),當出現網路連線斷開時,從伺服器會重新連線,並且向主伺服器請求繼續執行原來的複製程序:

1)如果從伺服器記錄的主伺服器ID和當前要連線的主伺服器的ID相同,並且從伺服器記錄的偏移量所指定的資料仍然儲存在主伺服器的複製流緩衝區裡面,那麼主伺服器會向從伺服器傳送斷線時缺失的那部分資料,然後複製工作可以繼續執行。

2)否則的話,從伺服器就要執行完整重同步操作。

SYNC處理斷線重連示例

如果我們仔細地觀察整個斷線並重連的過程,就會發現:
從伺服器在斷線之前已經擁有主伺服器的絕大部分資料,要讓主從伺服器重新回到一致狀態,從伺服器真正需要的是 k10087、k10088和k10089這三個鍵的資料,而不是主伺服器整個資料庫的資料。SYNC 命令在處理斷線並重連時的做法——將主伺服器的整個資料庫重新同步給從伺服器,是極度浪費的!

PSYNC處理斷線重連示例

1)PSYNC只會將從伺服器斷線期間缺失的資料傳送給從伺服器。兩個例子的情況是相同的,但SYNC 需要傳送包含整個資料庫的 RDB 檔案,而PSYNC 只需要傳送三個命令。

2)如果主從伺服器所處的網路環境並不那麼好的話(經常斷線),那麼請儘量使用 Redis 2.8 或以上版本:通過使用 PSYNC 而不是 SYNC 來處理斷線重連線,可以避免因為重複建立和傳輸 RDB檔案而浪費大量的網路資源、計算資源和記憶體資源。

複製的一致性問題

1)在讀寫分離環境下,客戶端向主伺服器傳送寫命令 SET k10086 v10086,主伺服器在執行這個寫命令之後,向客戶端返回回覆,並將這個寫命令傳播給從伺服器。

2)接到回覆的客戶端繼續向從伺服器傳送讀命令 GET k10086 ,並且因為網路狀態的原因,客戶端的 GET命令比主伺服器傳播的 SET 命令更快到達了從伺服器。

3)因為從伺服器鍵k10086的值還未被更新,所以客戶端在從伺服器讀取到的將是一個錯誤(過期)的k10086值。

Redis是怎麼保證資料安全的呢?

1)主伺服器只在有至少N個從伺服器的情況下,才執行寫操作

2)從Redis 2.8開始,為了保證資料的安全性,可以通過配置,讓主伺服器只在有至少N個當前已連線從伺服器的情況下,才執行寫命令。

3)不過,因為 Redis 使用非同步複製,所以主伺服器傳送的寫資料並不一定會被從伺服器接收到,因此, 資料丟失的可能性仍然是存在的。

4)通過以下兩個引數保證資料的安全:

#執行寫操作所需的至少從伺服器數量

min-slaves-to-write <number of slaves>

#指定網路延遲的最大值

min-slaves-max-lag <number of seconds>

這個特性的運作原理:

1)從伺服器以每秒一次的頻率 PING 主伺服器一次, 並報告複製流的處理情況。主伺服器會記錄各個從伺服器最後一次向它傳送 PING 的時間。使用者可以通過配置, 指定網路延遲的最大值 min-slaves-max-lag , 以及執行寫操作所需的至少從伺服器數量 min-slaves-to-write 。

2)如果至少有 min-slaves-to-write 個從伺服器, 並且這些伺服器的延遲值都少於 min-slaves-max-lag 秒, 那麼主伺服器就會執行客戶端請求的寫操作。你可以將這個特性看作 CAP 理論中的 C 的條件放寬版本: 儘管不能保證寫操作的永續性, 但起碼丟失資料的視窗會被嚴格限制在指定的秒數中。

3)另一方面, 如果條件達不到 min-slaves-to-write 和 min-slaves-max-lag 所指定的條件, 那麼寫操作就不會被執行, 主伺服器會向請求執行寫操作的客戶端返回一個錯誤

Redis主從實踐

配置多例項

 

 

#建立多例項目錄
[[email protected] ~]# /etc/redis/{6379,6380,6381}
#編輯多例項配置檔案
[[email protected] ~]# cat /etc/redis/6379/redis.conf /etc/redis/6380/redis.conf /etc/redis/6381/redis.conf

 

#redis 6379 配置檔案
port 6379
daemonize yes
pidfile /etc/redis/6379/redis.pid
loglevel notice
logfile /etc/redis/6379/redis.log
dbfilename dump.rdb
dir /etc/redis/6379
bind 127.0.0.1 10.0.0.51
protected-mode no

 

#redis 6380 配置檔案
port 6380
daemonize yes
pidfile /etc/redis/6380/redis.pid
loglevel notice
logfile /etc/redis/6380/redis.log
dbfilename dump.rdb
dir /etc/redis/6380
bind 127.0.0.1 10.0.0.51
protected-mode no

 

#redis 6381 配置檔案
port 6381
daemonize yes
pidfile /etc/redis/6381/redis.pid
loglevel notice
logfile /etc/redis/6381/redis.log
dbfilename dump.rdb
dir /etc/redis/6381
bind 127.0.0.1 10.0.0.51
protected-mode no

 

#啟動redis多例項
[[email protected] ~]# redis-server /etc/redis/6379/redis.conf
[[email protected] ~]# redis-server /etc/redis/6380/redis.conf
[[email protected] ~]# redis-server /etc/redis/6381/redis.conf

 

#檢視程序
[[email protected] ~]# ps -ef|grep redis
root 3570 1 0 22:44 ? 00:00:00 redis-server 127.0.0.1:6379
root 3574 1 0 22:44 ? 00:00:00 redis-server 127.0.0.1:6380
root 3578 1 0 22:44 ? 00:00:00 redis-server 127.0.0.1:6381
開啟主從

 

#連線從庫slave01(6380)
[[email protected] ~]# redis-cli -p 6380
#開啟主從
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
OK
#查從資訊
127.0.0.1:6380> INFO replication
# Replication
role:slave //角色變成了從庫
master_host:127.0.0.1 //主庫的ip
master_port:6379 //主庫的埠
master_link_status:up
master_last_io_seconds_ago:7
master_sync_in_progress:0
slave_repl_offset:15
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

 


#連線從庫slave02(6381)
[[email protected] ~]# redis-cli -p 6381
#開啟主從
127.0.0.1:6381> SLAVEOF 127.0.0.1 6379
OK
#檢視主從資訊
127.0.0.1:6381> INFO replication
# Replication
role:slave //角色變成了從庫
master_host:127.0.0.1 //主庫的ip
master_port:6379 //主庫的埠
master_link_status:up
master_last_io_seconds_ago:9
master_sync_in_progress:0
slave_repl_offset:225
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

 


#連線master(6379)
[[email protected] ~]# redis-cli -p 6379
#在主庫上檢視主從複製資訊
127.0.0.1:6379> INFO replication
# Replication
role:master //角色master
connected_slaves:2 //兩臺slave
slave0:ip=127.0.0.1,port=6380,state=online,offset=337,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=337,lag=1
master_repl_offset:337
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:336

主從切換

 

#連線master(6379)
[[email protected] ~]# redis-cli -p 6379
#關閉主庫
127.0.0.1:6379> shutdown
#連線從庫slave01(6380)
[[email protected] ~]# 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:down //連線主庫的狀態是:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:1877
master_link_down_since_seconds:58
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
#取消6380的主從關係
127.0.0.1:6380> SLAVEOF no one
OK
127.0.0.1:6380> info replication
# Replication
role:master //此時6380的角色就變成了master
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

 


#將其他從庫重新指向新主(6380)
#連線6381從庫
[[email protected] ~]# redis-cli -p 6381
#將6381從庫變成6380的從庫
127.0.0.1:6381> SLAVEOF 127.0.0.1 6380
OK
#檢視主從資訊
127.0.0.1:6381> INFO replication
# Replication
role:slave //角色還是slave
master_host:127.0.0.1
master_port:6380 //主庫的埠已經變成了6380
master_link_status:up
master_last_io_seconds_ago:4
master_sync_in_progress:0
slave_repl_offset:1
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0