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