010.Redis 主從架構(gòu)搭建及原理詳解

1. redis 主從架構(gòu)原理詳解

(1) 讀寫分離

在redis主從架構(gòu)中,Master節(jié)點負責處理寫請求,Slave節(jié)點只處理讀請求。對于寫請求少,讀請求多的場景,例如電商詳情頁,通過這種讀寫分離的操作可以大幅提高并發(fā)量,通過增加redis從節(jié)點的數(shù)量可以使得redis的QPS達到10W+。

(2) 主從同步

Master節(jié)點接收到寫請求并處理后,需要告知Slave節(jié)點數(shù)據(jù)發(fā)生了改變,保持主從節(jié)點數(shù)據(jù)一致的行為稱為主從同步,所有的Slave都和Master通信去同步數(shù)據(jù)也會加大Master節(jié)點的負擔,實際上,除了主從同步,redis也可以從從同步,我們在這里統(tǒng)一描述為主從同步。

A. 主從同步的方法

  • 增量同步

redis 同步的是指令流,主節(jié)點會將那些對自己的狀態(tài)產(chǎn)生修改性影響的指令記錄在本地的內(nèi)存 buffer 中,然后異步將 buffer 中的指令同步到從節(jié)點,從節(jié)點一邊執(zhí)行同步的指令流來達到和主節(jié)點一樣的狀態(tài),一邊向主節(jié)點反饋自己同步到哪里了 (偏移量,這是redis-2.8之后才有的特性)。從節(jié)點同步數(shù)據(jù)的時候不會影響主節(jié)點的正常工作,也不會影響自己對外提供讀服務(wù)的功能,從節(jié)點會用舊的數(shù)據(jù)來提供服務(wù),當同步完成后,需要刪除舊數(shù)據(jù)集,加載新數(shù)據(jù),這個時候才會暫停對外服務(wù)。

因為內(nèi)存的 buffer 是有限的,所以 redis 主節(jié)點不能將所有的指令都記錄在內(nèi)存 buffer 中。redis 的復(fù)制內(nèi)存 buffer 是一個定長的環(huán)形數(shù)組,如果數(shù)組內(nèi)容滿
了,就會從頭開始覆蓋前面的內(nèi)容。

  • 快照同步

如果節(jié)點間網(wǎng)絡(luò)通信不好,那么當從節(jié)點同步的速度不如主節(jié)點接收新寫請求的速度時,buffer 中會丟失一部分指令,從節(jié)點中的數(shù)據(jù)將與主節(jié)點中的數(shù)據(jù)不一致,此時將會觸發(fā)快照同步。

快照同步是一個非常耗費資源的操作,它首先需要在主節(jié)點上進行一次 bgsave 將當前內(nèi)存的數(shù)據(jù)全部快照到RDB文件中,然后再將快照文件的內(nèi)容全部傳送到從節(jié)點。從節(jié)點將RDB文件接受完畢后,立即執(zhí)行一次全量加載,加載之前先要將當前內(nèi)存的數(shù)據(jù)清空。加載完畢后通知主節(jié)點繼續(xù)進行增量同步。

在整個快照同步進行的過程中,主節(jié)點的復(fù)制 buffer 還在不停的往前移動,如果快照同步的時間過長或者復(fù)制 buffer 太小,都會導(dǎo)致同步期間的增量指令在復(fù)制 buffer 中被覆蓋,這樣就會導(dǎo)致快照同步完成后無法進行增量復(fù)制,然后會再次發(fā)起快照同步,如此極有可能會陷入快照同步的死循環(huán)。所以需要配置一個合適的復(fù)制 buffer 大小參數(shù),避免快照復(fù)制的死循環(huán)。

  • 無盤復(fù)制

主節(jié)點在進行快照同步時,會進行大量的文件 IO 操作,特別是對于非 SSD 磁盤存儲時,快照會對系統(tǒng)的負載產(chǎn)生較大影響。特別是當系統(tǒng)正在進行 AOF 的 fsync 操作時如果發(fā)生快照復(fù)制,fsync 將會被推遲執(zhí)行,這就會嚴重影響主節(jié)點的服務(wù)效率。

從 Redis 2.8.18 版開始支持無盤復(fù)制。所謂無盤復(fù)制是主節(jié)點會一邊遍歷內(nèi)存,一遍將序列化的內(nèi)容發(fā)送到從節(jié)點,而不是生成完整的 RDB 文件后才進行 IO 傳輸從節(jié)點還是跟之前一樣,先將接收到的內(nèi)容存儲到磁盤文件中,再進行一次性加載。

B. 主從同步的詳細流程

(1) 在從節(jié)點的配置文件中的slaveof配置項中配置了主節(jié)點的IP和port后,從節(jié)點就知道自己要和那個主節(jié)點進行連接了。

(2) 從節(jié)點內(nèi)部有個定時任務(wù),會每秒檢查自己要連接的主節(jié)點是否上線,如果發(fā)現(xiàn)了主節(jié)點上線,就跟主節(jié)點進行網(wǎng)絡(luò)連接。注意,此時僅僅是取得連接,還沒有進行主從數(shù)據(jù)同步。

(3) 從節(jié)點發(fā)送ping命令給主節(jié)點進行連接,如果設(shè)置了口令認證(主節(jié)點設(shè)置了requirepass),那么從節(jié)點必須發(fā)送正確的口令(masterauth)進行認證。

(4) 主從節(jié)點連接成功后,主從節(jié)點進行一次快照同步。事實上,是否進行快照同步需要判斷主節(jié)點的run id,當從節(jié)點發(fā)現(xiàn)已經(jīng)連接過某個run id的主節(jié)點,那么視此次連接為重新連接,就不會進行快照同步。相同IP和port的主節(jié)點每次重啟服務(wù)都會生成一個新的run id,所以每次主節(jié)點重啟服務(wù)都會進行一次快照同步,如果想重啟主節(jié)點服務(wù)而不改變run id,使用redis-cli debug reload命令。

(5) 當開始進行快照同步后,主節(jié)點在本地生成一份rdb快照文件,并將這個rdb文件發(fā)送給從節(jié)點,如果復(fù)制時間超過60秒(配置項:repl-timeout),那么就會認為復(fù)制失敗,如果數(shù)據(jù)量比較大,要適當調(diào)大這個參數(shù)的值。主從節(jié)點進行快照同步的時候,主節(jié)點會把接收到的新請求命令寫在緩存 buffer 中,當快照同步完成后,再把 buffer 中的指令增量同步到從節(jié)點。如果在快照同步期間,內(nèi)存緩沖區(qū)大小超過256MB,或者超過64MB的狀態(tài)持續(xù)時間超過60s(配置項:client-output-buffer-limit slave 256MB 64MB 60),那么也會認為快照同步失敗。

(6) 從節(jié)點接收到RDB文件之后,清空自己的舊數(shù)據(jù),然后重新加載RDB到自己的內(nèi)存中,在這個過程中基于舊的數(shù)據(jù)對外提供服務(wù)。如果主節(jié)點開啟了AOF,那么在快照同步結(jié)束后會立即執(zhí)行BGREWRITEAOF,重寫AOF文件。

(7) 主節(jié)點維護了一個backlog文件,默認是1MB大小,主節(jié)點向從節(jié)點發(fā)送全量數(shù)據(jù)(RDB文件)時,也會同步往backlog中寫,這樣當發(fā)送全量數(shù)據(jù)這個過程意外中斷后,從backlog文件中可以得知數(shù)據(jù)有哪些是發(fā)送成功了,哪些還沒有發(fā)送,然后當主從節(jié)點再次連接后,從失敗的地方開始增量同步。這里需要注意的是,當快照同步連接中斷后,主從節(jié)點再次連接并非是第一次連接,所以進行增量同步,而不是繼續(xù)進行快照同步。

(8) 快照同步完成后,主節(jié)點后續(xù)接收到寫請求導(dǎo)致數(shù)據(jù)變化后,將和從節(jié)點進行增量同步,遇到 buffer 溢出則再觸發(fā)快照同步。

(9) 主從節(jié)點都會維護一個offset,隨著主節(jié)點的數(shù)據(jù)變化以及主從同步的進行,主從節(jié)點會不斷累加自己維護的offset,從節(jié)點每秒都會上報自己的offset給主節(jié)點,主節(jié)點也會保存每個從節(jié)點的offset,這樣主從節(jié)點就能知道互相之間的數(shù)據(jù)一致性情況。從節(jié)點發(fā)送psync runid offset命令給主節(jié)點從而開始主從同步,主節(jié)點會根據(jù)自身的情況返回響應(yīng)信息,可能是FULLRESYNC runid offset觸發(fā)全量復(fù)制,也可能是CONTINUE觸發(fā)增量復(fù)制。

(10) 主從節(jié)點因為網(wǎng)絡(luò)原因?qū)е聰嚅_,當網(wǎng)絡(luò)接通后,不需要手工干預(yù),可以自動重新連接。

(11) 主節(jié)點如果發(fā)現(xiàn)有多個從節(jié)點連接,在快照同步過程中僅僅會生成一個RDB文件,用一份數(shù)據(jù)服務(wù)所有從節(jié)點進行快照同步。

(12) 從節(jié)點不會處理過期key,當主節(jié)點處理了一個過期key,會模擬一條del命令發(fā)送給從節(jié)點。

(13) 主從節(jié)點會保持心跳來檢測對方是否在線,主節(jié)點默認每隔10秒發(fā)送一次heartbeat,從節(jié)點默認每隔1秒發(fā)送一個heartbeat。

(14) 建議在主節(jié)點使用AOF+RDB的持久化方式,并且在主節(jié)點定期備份RDB文件,而從節(jié)點不要開啟AOF機制,原因有兩個,一是從節(jié)點AOF會降低性能,二是如果主節(jié)點數(shù)據(jù)丟失,主節(jié)點數(shù)據(jù)同步給從節(jié)點后,從節(jié)點收到了空的數(shù)據(jù),如果開啟了AOF,會生成空的AOF文件,基于AOF恢復(fù)數(shù)據(jù)后,全部數(shù)據(jù)就都丟失了,而如果不開啟AOF機制,從節(jié)點啟動后,基于自身的RDB文件恢復(fù)數(shù)據(jù),這樣不至于丟失全部數(shù)據(jù)。RDB和AOF機制可以參考詳解 redis-4.x 持久化機制

2. redis 主從架構(gòu)搭建

使用3個虛擬機搭建一主二從的redis主從架構(gòu)集群。首先參考redis-4.0.12單節(jié)點安裝在每臺機器上安裝redis,然后修改redis配置文件,其中一個master節(jié)點的配置如下(未列出的保持默認即可):

# basic
daemonize yes
port 6379
logfile /home/hadoop/logs/redis/6379/redis_6379.log
pidfile /home/hadoop/pid/redis/6379/redis_6379.pid
dir /home/hadoop/data/redis/6379

# aof
# 主節(jié)點打開AOF機制
appendonly yes

# master
# 綁定本臺機器的IP,否則主從節(jié)點無法通信
bind 192.168.239.101
# 設(shè)置master的認證口令為redis
requirepass redis
# backlog大小
repl-backlog-size 1mb
# 快照同步的超時時間
repl-timeout 60
# 開啟無盤復(fù)制
repl-diskless-sync yes
# 無盤復(fù)制的延遲默認為5s,是為了等待更多的slave連接
repl-diskless-sync-delay 5
# 是否開啟主從節(jié)點復(fù)制數(shù)據(jù)的延遲機制
# 當關(guān)閉時,主節(jié)點產(chǎn)生的命令數(shù)據(jù)無論大小都會及時地發(fā)送給從節(jié)點,這樣主從之間延遲會變小
# 但增加了網(wǎng)絡(luò)帶寬的消耗。適用于主從之間的網(wǎng)絡(luò)環(huán)境良好的場景
# 當開啟時,主節(jié)點會合并較小的TCP數(shù)據(jù)包從而節(jié)省帶寬。
# 默認發(fā)送時間間隔取決于Linux的內(nèi)核,一般默認為40毫秒。
# 這種配置節(jié)省了帶寬但增大主從之間的延遲。適用于主從網(wǎng)絡(luò)環(huán)境復(fù)雜或帶寬緊張的場景
repl-disable-tcp-nodelay no
# 觸發(fā)快照同步的條件
# 如果增量同步的緩存大于256MB,或者超過60s大于64MB,則觸發(fā)快照同步
client-output-buffer-limit slave 256mb 64mb 60
# 主從節(jié)點進行心跳的時間間隔
repl-ping-slave-period 10

兩個slave節(jié)點的配置如下:

# basic
daemonize yes
port 6379
logfile /home/hadoop/logs/redis/6379/redis_6379.log
pidfile /home/hadoop/pid/redis/6379/redis_6379.pid
dir /home/hadoop/data/redis/6379

# slave
# 綁定本機的IP,另一個為192.168.239.103
bind 192.168.239.102
# 綁定master的ip和port
slaveof 192.168.239.101 6379
# 從節(jié)點只讀
slave-read-only yes
# 從節(jié)點在處于快照同步期間是否對外提供服務(wù)
slave-serve-stale-data yes
# 如果 master 檢測到 slave 的數(shù)量小于這個配置設(shè)置的值,將拒絕對外提供服務(wù),0 代表,無論 slave 有幾個都會對外提供服務(wù)
min-slaves-to-write 0
# 如果 master 發(fā)現(xiàn)大于等于 ${min-slaves-to-write} 個 slave 與自己的心跳超過此處配置的時間(單位s)
# 就拒絕對外提供服務(wù)
min-slaves-max-lag 10
# master的認證口令
masterauth redis

啟動3個redis服務(wù):

[hadoop@node01 ~]$ redis-server ~/apps/redis-4.0.12/redis_6379.conf
[hadoop@node01 ~]$ redis-server ~/apps/redis-4.0.12/redis_6379.conf
[hadoop@node01 ~]$ redis-server ~/apps/redis-4.0.12/redis_6379.conf

查看master日志:

# master 已經(jīng)準備就緒
7810:M 15 Feb 18:08:54.108 * Ready to accept connections
# slave 92.168.239.102:6379 請求主從同步
7810:M 15 Feb 18:08:54.770 * Slave 192.168.239.102:6379 asks for synchronization

# slave 92.168.239.102:6379 請求快照同步
7810:M 15 Feb 18:08:54.770 * Full resync requested by slave 192.168.239.102:6379
# master 開始寫 RDB 文件到本地磁盤
7810:M 15 Feb 18:08:54.771 * Starting BGSAVE for SYNC with target: disk
# 子進程(7816)開始寫 RDB 文件到磁盤
7810:M 15 Feb 18:08:54.771 * Background saving started by pid 7816
# RDB 文件寫到本地磁盤成功
7816:C 15 Feb 18:08:54.774 * DB saved on disk
7816:C 15 Feb 18:08:54.774 * RDB: 6 MB of memory used by copy-on-write
7810:M 15 Feb 18:08:54.812 * Background saving terminated with success
# master 和 slave 192.168.239.102:6379 主從同步成功
7810:M 15 Feb 18:08:54.813 * Synchronization with slave 192.168.239.102:6379 succeeded

# slave 92.168.239.103:6379 請求快照同步
7810:M 15 Feb 18:08:55.564 * Slave 192.168.239.103:6379 asks for synchronization
7810:M 15 Feb 18:08:55.564 * Full resync requested by slave 192.168.239.103:6379
7810:M 15 Feb 18:08:55.564 * Starting BGSAVE for SYNC with target: disk
7810:M 15 Feb 18:08:55.564 * Background saving started by pid 7817
7817:C 15 Feb 18:08:55.567 * DB saved on disk
7817:C 15 Feb 18:08:55.567 * RDB: 6 MB of memory used by copy-on-write
7810:M 15 Feb 18:08:55.619 * Background saving terminated with success
# master 和 slave 192.168.239.103:6379 主從同步成功
7810:M 15 Feb 18:08:55.620 * Synchronization with slave 192.168.239.103:6379 succeeded

看日志發(fā)現(xiàn)一個問題,我們在原理中介紹說:
主節(jié)點如果發(fā)現(xiàn)有多個從節(jié)點連接,在快照同步過程中僅僅會生成一個RDB文件,用一份數(shù)據(jù)服務(wù)所有從節(jié)點進行快照同步。
然而這里master的日志顯示寫了兩次RDB文件,這里我查一些資料再來更新。(?。?!待完善)

查看slave日志(這里只列出一個slave的日志):

7112:S 15 Feb 18:08:54.796 * DB loaded from disk: 0.027 seconds
7112:S 15 Feb 18:08:54.796 * Ready to accept connections
# 連接到 master 192.168.239.101:6379
7112:S 15 Feb 18:08:54.796 * Connecting to MASTER 192.168.239.101:6379
# 開始主從同步
7112:S 15 Feb 18:08:54.796 * MASTER <-> SLAVE sync started
7112:S 15 Feb 18:08:54.797 * Non blocking connect for SYNC fired the event.
7112:S 15 Feb 18:08:54.797 * Master replied to PING, replication can continue...
7112:S 15 Feb 18:08:54.798 * Partial resynchronization not possible (no cached master)
# 與run id 為 1b9f6081fa8cfd0d1c3771daa5224de2a734c5e5:0 的 master 進行快照同步
7112:S 15 Feb 18:08:54.800 * Full resync from master: 1b9f6081fa8cfd0d1c3771daa5224de2a734c5e5:0
7112:S 15 Feb 18:08:54.841 * MASTER <-> SLAVE sync: receiving 176 bytes from master
# 刪除舊數(shù)據(jù)
7112:S 15 Feb 18:08:54.841 * MASTER <-> SLAVE sync: Flushing old data
# 加載 RDB 到內(nèi)存中
7112:S 15 Feb 18:08:54.841 * MASTER <-> SLAVE sync: Loading DB in memory
# 同步成功
7112:S 15 Feb 18:08:54.841 * MASTER <-> SLAVE sync: Finished with success

測試主從架構(gòu):

(1) 使用redis-cli訪問3個redis服務(wù)

# 這里由于我們配置的時候設(shè)置了認證口令,所以 redis-cli 連接服務(wù)也需要認證,不認證可以進入命令行,但是無法進行操作,比如 set
[hadoop@node01 ~]$ redis-cli -h 192.168.239.101 -p 6379 -a redis
192.168.239.101:6379> 

[hadoop@node02 redis-4.0.12]$ redis-cli -h 192.168.239.102 -p 6379 -a redis
192.168.239.102:6379>

[hadoop@node03 bin]$ redis-cli -h 192.168.239.103 -p 6379 -a redis
192.168.239.103:6379> 

(2) 在 master 節(jié)點上 set 一個數(shù)據(jù)

192.168.239.101:6379> set name tom
OK

(3) 從節(jié)點上獲取數(shù)據(jù)

192.168.239.102:6379> get name
"tom"

192.168.239.103:6379> get name
"tom"

(4) 嘗試在slave上寫入數(shù)據(jù)

192.168.239.103:6379> set age 20
(error) READONLY You can't write against a read only slave.

redis主從架構(gòu)搭建成功!

3. wait 命令(擴展,redis-3.0新增)

wait m t

wait 提供兩個參數(shù),第一個參數(shù)是從節(jié)點的數(shù)量 m,第二個參數(shù)是時間 t,以毫秒
為單位。它表示等待 wait 指令之前的所有寫操作同步到 n 個子節(jié)點 (也就是確保
m 個子節(jié)點的同步?jīng)]有滯后),最多等待時間 t。如果時間 t=0,表示無限等待直到
N 個從庫同步完成達成一致。
假設(shè)此時某個子節(jié)點與主節(jié)點網(wǎng)絡(luò)斷開,wait 指令第二個參數(shù)時間 t = 0,主從同步無法繼續(xù)
進行,wait 指令會永遠阻塞,redis 服務(wù)器將喪失可用性。

node01:6379> set name bob
OK
node01:6379> wait 2 5000
(integer) 2
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 從這篇文章開始,將依次介紹Redis高可用相關(guān)的知識——持久化、復(fù)制(及讀寫分離)、哨兵、以及集群。 本文將先說明...
    不變甄心閱讀 737評論 0 4
  • 前言 在上一篇文章中,介紹了Redis內(nèi)存模型,從這篇文章開始,將依次介紹Redis高可用相關(guān)的知識——持久化、復(fù)...
    Java架構(gòu)閱讀 2,503評論 3 21
  • 一、Redis高可用概述 在介紹Redis高可用之前,先說明一下在Redis的語境中高可用的含義。 我們知道,在w...
    空語閱讀 1,680評論 0 2
  • 在Redis的持久化中曾提到,Redis高可用的方案包括持久化、主從復(fù)制(及讀寫分離)、哨兵和集群。其中持久化側(cè)重...
    不變甄心閱讀 1,581評論 0 5
  • 《君者寸寇草木歇》 平行苦扯無人錯, 哉獨齊楚霸王別。 坦然歸送銷車佐, 棄淚遺婉膽虧邪。 創(chuàng)白棋威黑子戰(zhàn), 匹北...
    春城怡景閱讀 427評論 4 12

友情鏈接更多精彩內(nèi)容