第5章 Redis分布式緩存
單機Redis存在四大問題:
- 1)數(shù)據(jù)丟失問題;
- 2)并發(fā)能力問題;
- 3)故障恢復問題;
- 4)存儲能力問題。
而Redis分布式緩存,即基于Redis集群來解決單機Redis存在的問題:
- 1)數(shù)據(jù)丟失問題:實現(xiàn)Redis數(shù)據(jù)持久化;
- 2)并發(fā)能力問題:搭建主從集群,實現(xiàn)讀寫分離;
- 3)故障恢復問題;利用Redis哨兵,實現(xiàn)健康監(jiān)測和自動恢復;
- 4)存儲能力問題:搭建分片集群,利用插槽機制實現(xiàn)動態(tài)擴容。
5.1 Redis持久化
Redis有兩種持久化方案:
- RDB持久化
- AOF持久化
5.1.1 RDB持久化
RDB全稱Redis Database Backup file(Redis數(shù)據(jù)備份文件),也被叫做Redis數(shù)據(jù)快照。簡單來說,就是把內(nèi)存中的所有數(shù)據(jù)都記錄到磁盤中。當Redis實例故障重啟后,從磁盤讀取快照文件,恢復數(shù)據(jù)。快照文件稱為RDB文件,默認是保存在當前運行目錄。
5.1.1.1 執(zhí)行時機
RDB持久化在四種情況下會執(zhí)行:執(zhí)行save命令、執(zhí)行bgsave命令、Redis停機時、觸發(fā)RDB條件時。
- 1)執(zhí)行save命令
127.0.0.1:6379> save
OK
執(zhí)行save命令,會立即執(zhí)行一次RDB。該命令會使用主進程執(zhí)行RDB,從而阻塞其他所有命令。
Redis是單線程的,所以執(zhí)行save命令時整個服務會阻塞,不能繼對外提供請求,數(shù)據(jù)量小的話肯定影響不大,數(shù)據(jù)量大的時候就相當于停機很長一段時間。
- 2)執(zhí)行bgsave命令
127.0.0.1:6379> bgsave
Background saving started
執(zhí)行bgsave命令,可以異步執(zhí)行RDB,會開啟一個獨立進程完成RDB,主進程可以繼續(xù)處理用戶請求,不受影響。
- 3)Redis停機時
Redis停機時會執(zhí)行一次save命令,實現(xiàn)RDB持久化。
- 4)觸發(fā)RDB條件時
Redis內(nèi)部有觸發(fā)RDB的機制,可以在redis.conf文件中找到,格式如下:
# 禁用RDB
save ""
# 3600秒內(nèi),至少1個Key被修改時執(zhí)行RDB
# 300秒內(nèi),至少100個Key被修改時執(zhí)行RDB
# 60秒內(nèi),至少10000個Key被修改時執(zhí)行RDB
save 3600 1 300 100 60 10000
# 是否壓縮,默認yes
# 建議改為no以避免消耗CPU,但相應占用磁盤空間會增加
rdbcompression yes
# RDB文件名稱
dbfilename dump.rdb
# 文件保存的路徑目錄(默認是 ./)
dir /var/lib/redis
達到觸發(fā)條件時,Redis會自動執(zhí)行bgsave命令進行異步RDB。 而執(zhí)行RDB后,可以在/var/lib/redis
目錄下看到RDB文件:
ll
total 8
-rw-r--r--. 1 root root 88 Apr 10 11:38 dump.rdb
-rw-r--r--. 1 root root 677 Apr 10 11:38 redis.log
5.1.1.2 bgsave原理
bgsave命令的原理是:fork() + Copy-On-Write。
- fork()
fork()是Unix和Linux這種操作系統(tǒng)的一個api,而不是Redis本身的api。其特點有:
(1)fork()用于創(chuàng)建一個子進程,注意是子進程,不是子線程。
(2)fork()出來的子進程共享其父進行的內(nèi)存數(shù)據(jù),但僅僅是共享創(chuàng)建完成那一時刻的內(nèi)存數(shù)據(jù),后期主進程修改數(shù)據(jù)對子進程不可見,子進程修改的數(shù)據(jù)對主進程也不可見。
(3)子進程掛了,對主進程完全沒影響,它依然可以對外提供服務;但是主進程掛了,子進程也必須跟隨一起掛。
(4)主進程和子進程共享內(nèi)存空間,但為什么它們之后對內(nèi)存數(shù)據(jù)的修改操作對彼此不可見呢?答案是采取了copy-on-write技術。
- Copy-On-Write(寫時復制技術)
如上圖所示,主進程fork()出子進程之后,主進程中的所有內(nèi)存頁表的權限都會被設為read-only,然后子進程的地址空間指向主進程,這也就是實現(xiàn)了主進程內(nèi)存空間的共享。
當主進程執(zhí)行讀操作時,直接訪問共享內(nèi)存;當主進程執(zhí)行寫操作時(子進程只負責RDB,不會有寫操作),CPU會檢測到要操作的內(nèi)存頁表是read-only的,于是觸發(fā)頁表異常中斷(page-fault),進行一個中斷例程。
在中斷例程中,CPU會把異常頁表復制一份(這里僅僅復制異常頁表,也就是要修改的那個數(shù)據(jù)頁表,而不是內(nèi)存中的全部數(shù)據(jù)),于是主、子進程各自持有獨立的一份頁表,主進程繼續(xù)修改自己的那一份,子進程繼續(xù)讀取原來那份read-only的頁表。
5.1.2 AOF持久化
5.1.2.1 AOF原理
AOF全稱為Append Only File(追加文件)。Redis處理的每一個寫命令都會記錄在AOF文件,可以看做是命令日志文件。
5.1.2.2 AOF配置
AOF默認是關閉的,需要修改redis.conf配置文件來開啟AOF:
# 是否開啟AOF功能,默認是no
appendonly yes
# AOF文件的名稱
appendfilename "appendonly.aof"
# AOF記錄的頻率
# 表示每執(zhí)行一次寫命令,立即記錄到AOF文件
# appendfsync always
# 寫命令執(zhí)行完先放入AOF緩沖區(qū),然后每隔1秒將緩沖區(qū)數(shù)據(jù)寫到AOF文件,是默認方案
appendfsync everysec
# 寫命令執(zhí)行完先放入AOF緩沖區(qū),由操作系統(tǒng)決定何時將緩沖區(qū)內(nèi)容寫回磁盤
# appendfsync no
AOF記錄的頻率三種策略的對比如下:
AOF的作用如下例:
127.0.0.1:6379> set test:name aaaa
OK
此時/var/lib/redis
目錄下創(chuàng)建了AOF相關目錄及文件:
cd /var/lib/redis/
ls
appendonlydir dump.rdb redis.log
cd appendonlydir/
ll
total 12
-rw-r--r--. 1 root root 88 Apr 10 13:11 appendonly.aof.1.base.rdb
-rw-r--r--. 1 root root 61 Apr 10 13:11 appendonly.aof.1.incr.aof
-rw-r--r--. 1 root root 88 Apr 10 13:11 appendonly.aof.manifest
more appendonly.aof.1.incr.aof
$3
set
$9
test:name
$4
aaaa
5.1.2.3 AOF文件重寫
AOF文件會記錄每一個寫命令,即使是對同一個Key的多次寫操作也會全部記錄下來,因此文件大小比RDB文件大得多。但是對同一個Key的多次寫操作中,只有最后一次寫操作才是有意義的,因此AOF將對同一個Key的全部寫操作都記錄下來的意義不大。
通過執(zhí)行bgrewriteaof命令,可以讓AOF文件執(zhí)行重寫功能,用最少的命令達到相同的效果。
Redis也會在觸發(fā)閾值時自動重寫AOF文件。閾值也可以在redis.conf中配置:
# AOF文件比上次文件增長超過多少百分比則觸發(fā)重寫
auto-aof-rewrite-percentage 100
# AOF文件體積最小多大以上才觸發(fā)重寫
auto-aof-rewrite-min-size 64mb
5.1.3 RDB和AOF的對比
RDB和AOF各有自己的優(yōu)缺點,如果對數(shù)據(jù)安全性要求較高,在實際開發(fā)中往往會結(jié)合兩者來使用。
5.2 Redis主從
5.2.1 搭建主從結(jié)構
單節(jié)點Redis的并發(fā)能力是有上限的,要進一步提高Redis的并發(fā)能力,就需要搭建主從集群,實現(xiàn)讀寫分離。
由上圖可知,我們準備搭建的結(jié)構包含三個節(jié)點:一個主節(jié)點,兩個從節(jié)點。我們可以在同一臺虛擬機中開啟3個不同端口的Redis實例來模擬主從集群。
其搭建步驟如下:
- 1)在
/usr/local/redis
目錄下創(chuàng)建3個目錄,名字分別是7001、7002、7003
- 2)分別拷貝配置文件redis.conf到這3個目錄下
cp /root/redis-7.2.4/redis.conf /usr/local/redis/7001
cp /root/redis-7.2.4/redis.conf /usr/local/redis/7002
cp /root/redis-7.2.4/redis.conf /usr/local/redis/7003
- 3)修改3個目錄下的配置文件(以7001為例,7002、7003類推)
# 關閉AOF
appendonly no
# 開啟RDB
# save ""
save 3600 1 300 100 60 10000
# 修改RDB文件保存路徑
dir /var/local/redis/7001
# 修改端口
port 7001
要將7002、7003實例配置為從節(jié)點,還需要在7002、7003的配置文件中增加兩行配置:
# 配置為從節(jié)點,主節(jié)點的IP為192.168.146.128,端口為7001
slaveof 192.168.146.128 7001
# 配置主節(jié)點的密碼(未設置密碼時不需要)
masterauth 123321
- 4)啟動3個實例
/usr/local/bin/redis-server /usr/local/redis/7001/redis.conf
/usr/local/bin/redis-server /usr/local/redis/7002/redis.conf
/usr/local/bin/redis-server /usr/local/redis/7003/redis.conf
- 5)查詢集群狀態(tài)
redis-cli -p 7001 -a 123321
至此,Redis主從集群搭建完成。
另外,對于開啟主從關系,有臨時和永久兩種模式:
永久模式:在redis.conf中添加一行配置:slaveof <masterip> <masterport>
,即上面使用的模式。
臨時模式:使用redis-cli客戶端連接到redis服務后,執(zhí)行slaveof <masterip> <masterport>
命令,這種模式在Redis重啟后失效。
- 6)主從集群測試
利用redis-cli
連接7001:
127.0.0.1:7001> set test:master 7001
OK
127.0.0.1:7001> get test:master
"7001"
利用redis-cli
連接7002:
127.0.0.1:7002> set test:slave1 7002
(error) READONLY You can't write against a read only replica.
127.0.0.1:7002> get test:master
"7001"
利用redis-cli
連接7003:
127.0.0.1:7003> set test:slave2 7003
(error) READONLY You can't write against a read only replica.
127.0.0.1:7003> get test:master
"7001"
由測試結(jié)果可知,只有在7001這個master節(jié)點上可以執(zhí)行寫操作和讀操作,7002和7003這兩個slave節(jié)點只能執(zhí)行讀操作。
5.2.2 主從數(shù)據(jù)同步原理
5.2.2.1 全量同步
主從節(jié)點第一次建立連接時,會執(zhí)行全量同步,將master節(jié)點的所有數(shù)據(jù)都拷貝被slave節(jié)點。 其流程如下:
由上圖所示,全量同步主要有三個階段:
-
1)第一階段:slave節(jié)點請求數(shù)據(jù)同步,master節(jié)點判斷是否是第一次同步,如果是第一次同步則返回master節(jié)點的數(shù)據(jù)版本信息,slave節(jié)點保存版本信息;
-
2)第二階段:master節(jié)點執(zhí)行bgsave命令,生成RDB文件,并記錄RDB期間的所有命令到repl_baklog中,然后將RDB文件發(fā)送給slave節(jié)點,slave節(jié)點收到后清空本地數(shù)據(jù),加載RDB文件;
-
3)第三階段:master節(jié)點繼續(xù)發(fā)送到repl_baklog中保存的命令,slave節(jié)點收到后執(zhí)行這些命令。
這里有一個問題:master如何得知salve是第一次請求數(shù)據(jù)呢?
在解答之前,先來了解兩個概念:
-
Replication Id:簡稱replid,是數(shù)據(jù)集的標記,id一致則說明是同一數(shù)據(jù)集。每一個master都有唯一的replid,slave則會繼承master節(jié)點的replid。
-
offset:偏移量,隨著記錄在repl_baklog中的命令數(shù)據(jù)增多而逐漸增大。slave完成同步時也會記錄當前同步的offset,如果slave的offset小于master的offset,說明slave數(shù)據(jù)落后于master,需要更新。
一個節(jié)點在變成slave之前,也算一個master節(jié)點,也有自己的replid。在與master建立連接時,要發(fā)送自己的replid。如果master判斷發(fā)現(xiàn)slave送過來的replid與自己的不一致,說明這是一個全新的slave,就知道要做全量同步了。
同步完成后,master會將自己的replid和offset都發(fā)送給這個slave,slave保存這些信息,以后slave的replid就與master一致了。
因此,master判斷一個節(jié)點是否是第一次同步的依據(jù),就是看replid是否一致。那么全量同步的流程可以進行如下優(yōu)化:
5.2.2.2 增量同步
全量同步需要先做RDB,然后將RDB文件通過網(wǎng)絡傳輸給slave,成本是比較高的。因此除了第一次做全量同步,其它大多數(shù)時候master與slave都是做增量同步。
增量同步就是只更新master與slave存在差異的部分數(shù)據(jù),其流程如下圖:
由上圖所示,增量同步主要有兩個階段:
-
1)第一階段:slave節(jié)點請求數(shù)據(jù)同步,發(fā)送replid和offset,master節(jié)點收到后判斷replid是否一致,如果一致說明要進行增量同步,回復continue;
-
2)第二階段:master節(jié)點去repl_baklog中獲取offset后的命令,并發(fā)送給slave節(jié)點,slave節(jié)點收到后執(zhí)行這些命令,即完成了增量同步。
那master怎么知道slave與自己的數(shù)據(jù)差異在哪里呢?很明顯就是依靠repl_baklog文件,該文件會記錄Redis處理過的命令日志及offset,包括master當前的offset,和slave已經(jīng)拷貝到的offset。
repl_baklog文件是一個固定大小的環(huán)形數(shù)組,也就是說下標到達數(shù)組末尾后,會再次從0開始讀寫,這樣數(shù)組頭部的數(shù)據(jù)就會被覆蓋。
如上圖所示,隨著不斷有數(shù)據(jù)寫入,master的offset逐漸變大,slave也不斷地拷貝,追趕master的offset,其中紅色部分就是需要增量拷貝的數(shù)據(jù),直至整個數(shù)組被填滿。
此時,如果有新的數(shù)據(jù)寫入,就會覆蓋數(shù)組中的舊數(shù)據(jù)。不過,舊的數(shù)據(jù)只要是綠色的,說明是已經(jīng)被同步到slave的數(shù)據(jù),即便被覆蓋了也沒什么影響。
但是,如果slave出現(xiàn)網(wǎng)絡阻塞,導致master的offset遠遠超過了slave的offset。如下圖:
如果master繼續(xù)寫入新數(shù)據(jù),其offset就會覆蓋還未同步的舊的數(shù)據(jù),即圖中棕色框中的紅色部分——尚未同步但是卻已經(jīng)被覆蓋的數(shù)據(jù)。
此時如果slave恢復,需要同步,卻發(fā)現(xiàn)自己的offset都沒有了,已經(jīng)無法完成增量同步,就只能做全量同步。
5.2.3 小結(jié)
(1)簡述全量同步和增量同步的區(qū)別?
- 全量同步:master將完整內(nèi)存數(shù)據(jù)生成RDB,發(fā)送RDB到slave。后續(xù)命令則記錄在repl_baklog,逐個發(fā)送給slave。
- 增量同步:slave提交自己的offset到master,master獲取repl_baklog中從offset之后的命令給slave。
(2)什么時候執(zhí)行全量同步?
- slave節(jié)點第一次連接master節(jié)點時。
- slave節(jié)點斷開時間太久,repl_baklog中的offset已經(jīng)被覆蓋時。
(3)什么時候執(zhí)行增量同步?
- slave節(jié)點斷開又恢復,并且在repl_baklog中能找到offset時。
…
本節(jié)完,更多內(nèi)容請查閱分類專欄:Redis從入門到精通文章來源:http://www.zghlxwxcb.cn/news/detail-848599.html
感興趣的讀者還可以查閱我的另外幾個專欄:文章來源地址http://www.zghlxwxcb.cn/news/detail-848599.html
- SpringBoot源碼解讀與原理分析(已完結(jié))
- MyBatis3源碼深度解析(已完結(jié))
- 再探Java為面試賦能(持續(xù)更新中…)
到了這里,關于Redis從入門到精通(十三)Redis分布式緩存(一)RDB和AOF持久化、Redis主從集群的搭建與原理分析的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!