七、redis分布式集群
文章目錄
- 一、redis持久化
- 1.1.RDB持久化
- 1.1.1.執(zhí)行時機
- 1.1.2.RDB原理
- 1.1.3.小結
- 1.2.AOF持久化
- 1.2.1.AOF原理
- 1.2.2.AOF配置
- 1.2.3.AOF文件重寫
- 1.2.4.小結
- 1.3.RDB與AOF對比
- 二、Redis主從集群
- 2.1.集群結構
- 2.2.準備實例和配置
- 2.3.啟動
- 2.4.開啟主從關系
- 2.5.測試
- 2.6.主從數(shù)據(jù)同步原理
- 2.6.1.全量同步
- 2.6.2.增量同步
- 2.2.3.repl_backlog原理
- 2.7.主從同步優(yōu)化
- 2.8.小結
- 三、搭建哨兵集群
- 3.1.哨兵原理
- 3.1.1.集群結構和作用
- 3.1.2.集群監(jiān)控原理
- 3.1.3.集群故障恢復原理
- 3.1.4.小結
- 3.2.準備實例和配置
- 3.3.啟動
- 3.4.測試
- 3.5.RedisTemplate
- 3.5.1.引入依賴
- 3.5.2.配置Redis地址
- 3.5.3.配置讀寫分離
- 3.5.4.測試
- 四、Redis分片集群
- 4.1.搭建分片集群
- 4.1.1.準備實例和配置
- 4.1.2.啟動
- 4.1.3.創(chuàng)建集群
- 4.2.散列插槽
- 4.2.1.插槽原理
- 4.2.2.小結
- 4.3.集群伸縮
- 4.3.1.需求分析
- 4.3.2.創(chuàng)建新的redis實例
- 4.3.3.添加新節(jié)點到redis
- 4.3.4.轉移插槽
- 4.4.故障轉移
- 4.4.1.自動故障轉移
- 4.4.2.手動故障轉移
- 4.5.RedisTemplate訪問分片集群
一、redis持久化
Redis有兩種持久化方案:
- RDB持久化
- AOF持久化
1.1.RDB持久化
RDB全稱Redis Database Backup file(Redis數(shù)據(jù)備份文件),也被叫做Redis數(shù)據(jù)快照。簡單來說就是把內(nèi)存中的所有數(shù)據(jù)都記錄到磁盤中。當Redis實例故障重啟后,從磁盤讀取快照文件,恢復數(shù)據(jù)。快照文件稱為RDB文件,默認是保存在當前運行目錄。
1.1.1.執(zhí)行時機
RDB持久化在四種情況下會執(zhí)行:
- 執(zhí)行save命令
- 執(zhí)行bgsave命令
- redis停機時
- 觸發(fā)RDB條件時
1)save命令
執(zhí)行下面的命令,可以立即執(zhí)行一次RDB:
save命令會導致主進程執(zhí)行RDB,這個過程中其它所有命令都會被阻塞。只有在數(shù)據(jù)遷移時可能用到,數(shù)據(jù)遷移時redis不再接收新的請求,使用save可以阻塞請求。
2)bgsave命令
下面的命令可以異步執(zhí)行RDB:
這個命令執(zhí)行后會開啟獨立進程完成RDB,主進程可以持續(xù)處理用戶請求,不受影響。
3)停機時
redis停機時會執(zhí)行一次save命令,實現(xiàn)RDB持久化。
4)觸發(fā)RDB條件
Redis內(nèi)部有觸發(fā)RDB的機制,可以在redis.conf文件中找到,格式如下:
# 900秒內(nèi),如果至少有1個key被修改,則執(zhí)行bgsave , 如果是save "" 則表示禁用RDB save 900 1 save 300 10 save 60 10000RDB的其它配置也可以在redis.conf文件中設置:
# 是否壓縮 ,建議不開啟,壓縮也會消耗cpu,磁盤的話不值錢 rdbcompression yes# RDB文件名稱 dbfilename dump.rdb # 文件保存的路徑目錄 dir ./1.1.2.RDB原理
bgsave開始時會fork主進程得到子進程,子進程共享主進程的內(nèi)存數(shù)據(jù)。完成fork后讀取內(nèi)存數(shù)據(jù)并寫入 RDB 文件。
fork采用的是copy-on-write技術:
- 當主進程執(zhí)行讀操作時,訪問共享內(nèi)存;
- 當主進程執(zhí)行寫操作時,則會拷貝一份數(shù)據(jù),執(zhí)行寫操作。
主進程fork()子進程之后,內(nèi)核把主進程中所有的內(nèi)存頁的權限都設為read-only,然后子進程的地址空間指向主進程。這也就是共享了主進程的內(nèi)存,當其中某個進程寫內(nèi)存時(這里肯定是主進程寫,因為子進程只負責rdb文件持久化工作,不參與客戶端的請求),CPU硬件檢測到內(nèi)存頁是read-only的,于是觸發(fā)頁異常中斷(page-fault),陷入內(nèi)核的一個中斷例程。中斷例程中,內(nèi)核就會把觸發(fā)的異常的頁復制一份(這里僅僅復制異常頁,也就是所修改的那個數(shù)據(jù)頁,而不是內(nèi)存中的全部數(shù)據(jù)),于是主子進程各自持有獨立的一份。主進程修改了哪個數(shù)據(jù),指針就指向那個新的數(shù)據(jù),其余指針依舊指向和子進程共享的數(shù)據(jù)
1.1.3.小結
RDB方式bgsave的基本流程?
- fork主進程得到一個子進程,共享內(nèi)存空間
- 子進程讀取內(nèi)存數(shù)據(jù)并寫入新的RDB文件
- 用新RDB文件替換舊的RDB文件
RDB會在什么時候執(zhí)行?save 60 1000代表什么含義?
- 默認是服務停止時
- 代表60秒內(nèi)至少執(zhí)行1000次修改則觸發(fā)RDB
RDB的缺點?
- RDB是間隔一段時間進行持久化,如果持久化之間 redis 發(fā)生故障,會發(fā)生數(shù)據(jù)丟失風險
- fork子進程、壓縮、寫出RDB文件都比較耗時
1.2.AOF持久化
1.2.1.AOF原理
AOF全稱為Append Only File(追加文件)。Redis處理的每一個寫命令都會記錄在AOF文件,可以看做是命令日志文件。
1.2.2.AOF配置
AOF默認是關閉的,需要修改redis.conf配置文件來開啟AOF:
# 是否開啟AOF功能,默認是no appendonly yes # AOF文件的名稱 appendfilename "appendonly.aof"AOF的命令記錄的頻率也可以通過redis.conf文件來配:
# 表示每執(zhí)行一次寫命令,立即記錄到AOF文件 appendfsync always # 寫命令執(zhí)行完先放入AOF緩沖區(qū),然后表示每隔1秒將緩沖區(qū)數(shù)據(jù)寫到AOF文件,是默認方案 appendfsync everysec # 寫命令執(zhí)行完先放入AOF緩沖區(qū),由操作系統(tǒng)決定何時將緩沖區(qū)內(nèi)容寫回磁盤 appendfsync no三種策略對比:
1.2.3.AOF文件重寫
因為是記錄命令,AOF文件會比RDB文件大的多。而且AOF會記錄對同一個key的多次寫操作,但只有最后一次寫操作才有意義。通過執(zhí)行bgrewriteaof命令,可以讓AOF文件執(zhí)行重寫功能,用最少的命令達到相同效果。
如圖,AOF原本有三個命令,但是set num 123 和 set num 666都是對num的操作,第二次會覆蓋第一次的值,因此第一個命令記錄下來沒有意義。
所以重寫命令后,理論上AOF文件內(nèi)容就是:mset name jack num 666,實際上是一堆亂碼
Redis也會在觸發(fā)閾值時自動去重寫AOF文件。閾值也可以在redis.conf中配置:
# AOF文件比上次文件 增長超過多少百分比則觸發(fā)重寫 auto-aof-rewrite-percentage 100 # AOF文件體積最小多大以上才觸發(fā)重寫 auto-aof-rewrite-min-size 64mb1.2.4.小結
AOF的缺點?
- 比如在同樣數(shù)據(jù)規(guī)模的情況下,AOF文件要比RDB文件的體積大。而且,AOF 方式的恢復速度也要慢于RDB 方式。
1.3.RDB與AOF對比
RDB和AOF各有自己的優(yōu)缺點,如果對數(shù)據(jù)安全性要求較高,在實際開發(fā)中往往會結合兩者來使用。
二、Redis主從集群
2.1.集群結構
我們搭建的主從集群結構如圖:
共包含三個節(jié)點,一個主節(jié)點,兩個從節(jié)點。
這里我們會在同一臺虛擬機中開啟3個redis實例,模擬主從集群,信息如下:
| 192.168.150.101 | 7001 | master |
| 192.168.150.101 | 7002 | slave |
| 192.168.150.101 | 7003 | slave |
2.2.準備實例和配置
要在同一臺虛擬機開啟3個實例,必須準備三份不同的配置文件和目錄,配置文件所在目錄也就是工作目錄。
1)創(chuàng)建目錄
我們創(chuàng)建三個文件夾,名字分別叫7001、7002、7003:
# 進入/tmp目錄 cd /tmp # 創(chuàng)建目錄 mkdir 7001 7002 70032)拷貝配置文件到每個實例目錄
然后將redis-6.2.4/redis.conf文件拷貝到三個目錄中(在/tmp目錄執(zhí)行下列命令):
# 方式一:逐個拷貝 cp redis-6.2.4/redis.conf 7001 cp redis-6.2.4/redis.conf 7002 cp redis-6.2.4/redis.conf 7003# 方式二:管道組合命令,一鍵拷貝 echo 7001 7002 7003 | xargs -t -n 1 cp redis-6.2.4/redis.conf3)修改每個實例的端口、工作目錄
修改每個文件夾內(nèi)的配置文件,將端口分別修改為7001、7002、7003,將rdb文件保存位置都修改為自己所在目錄(在/tmp目錄執(zhí)行下列命令):
sed -i -e 's/6379/7001/g' -e 's/dir .\//dir \/tmp\/7001\//g' 7001/redis.conf sed -i -e 's/6379/7002/g' -e 's/dir .\//dir \/tmp\/7002\//g' 7002/redis.conf sed -i -e 's/6379/7003/g' -e 's/dir .\//dir \/tmp\/7003\//g' 7003/redis.conf4)修改每個實例的聲明IP
虛擬機本身有多個IP,為了避免將來混亂,我們需要在redis.conf文件中指定每一個實例的綁定ip信息,格式如下:
# redis實例的聲明 IP replica-announce-ip 192.168.150.101每個目錄都要改,我們一鍵完成修改(在/tmp目錄執(zhí)行下列命令):
# 逐一執(zhí)行 sed -i '1a replica-announce-ip 192.168.150.101' 7001/redis.conf sed -i '1a replica-announce-ip 192.168.150.101' 7002/redis.conf sed -i '1a replica-announce-ip 192.168.150.101' 7003/redis.conf# 或者一鍵修改 printf '%s\n' 7001 7002 7003 | xargs -I{} -t sed -i '1a replica-announce-ip 192.168.150.101' {}/redis.conf2.3.啟動
為了方便查看日志,我們打開3個ssh窗口,分別啟動3個redis實例,啟動命令:
# 第1個 redis-server 7001/redis.conf # 第2個 redis-server 7002/redis.conf # 第3個 redis-server 7003/redis.conf啟動后:
如果要一鍵停止,可以運行下面命令:
2.4.開啟主從關系
現(xiàn)在三個實例還沒有任何關系,要配置主從可以使用replicaof 或者slaveof(5.0以前)命令。
有臨時和永久兩種模式:
-
修改配置文件(永久生效)
- 在redis.conf中添加一行配置:slaveof <masterip> <masterport>
-
使用redis-cli客戶端連接到redis服務,執(zhí)行slaveof命令(重啟后失效):
slaveof <masterip> <masterport>
注意:在5.0以后新增命令replicaof,與salveof效果一致。
通過redis-cli命令連接7002,執(zhí)行下面命令:
# 連接 7002 redis-cli -p 7002 # 執(zhí)行slaveof slaveof 192.168.150.101 7001通過redis-cli命令連接7003,執(zhí)行下面命令:
# 連接 7003 redis-cli -p 7003 # 執(zhí)行slaveof slaveof 192.168.150.101 7001然后連接 7001節(jié)點,查看集群狀態(tài):
# 連接 7001 redis-cli -p 7001 # 查看狀態(tài) info replication結果:
注意:在linxu服務器搭建主從復制報錯
解決方案:
- 去云服務器安全組打開7001,7002,7003端口即可
2.5.測試
執(zhí)行下列操作以測試:
-
利用redis-cli連接7001,執(zhí)行set num 123
-
利用redis-cli連接7002,執(zhí)行get num,再執(zhí)行set num 666
-
利用redis-cli連接7003,執(zhí)行get num,再執(zhí)行set num 888
可以發(fā)現(xiàn),只有在7001這個master節(jié)點上可以執(zhí)行寫操作,7002和7003這兩個slave節(jié)點只能執(zhí)行讀操作。
2.6.主從數(shù)據(jù)同步原理
2.6.1.全量同步
主從第一次建立連接時,會執(zhí)行全量同步,將master節(jié)點的所有數(shù)據(jù)都拷貝給slave節(jié)點,流程:
這里有一個問題,master如何得知salve是第一次來連接呢??
有幾個概念,可以作為判斷依據(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,需要更新。
因此slave做數(shù)據(jù)同步,必須向master聲明自己的replication id 和offset,master才可以判斷到底需要同步哪些數(shù)據(jù)。
因為slave原本也是一個master,有自己的replid和offset,當?shù)谝淮巫兂蓅lave,與master建立連接時,發(fā)送的replid和offset是自己的replid和offset。
master判斷發(fā)現(xiàn)slave發(fā)送來的replid與自己的不一致,說明這是一個全新的slave,就知道要做全量同步了。
master會將自己的replid和offset都發(fā)送給這個slave,slave保存這些信息。以后slave的replid就與master一致了。
因此,master判斷一個節(jié)點是否是第一次同步的依據(jù),就是看replid是否一致。
如圖:
完整流程描述:
- slave節(jié)點請求增量同步
- master節(jié)點判斷replid,發(fā)現(xiàn)不一致,拒絕增量同步
- master將完整內(nèi)存數(shù)據(jù)生成RDB,發(fā)送RDB到slave
- slave清空本地數(shù)據(jù),加載master的RDB
- master將RDB期間的命令記錄在repl_baklog,并持續(xù)將log中的命令發(fā)送給slave
- slave執(zhí)行接收到的命令,保持與master之間的同步
2.6.2.增量同步
全量同步需要先做RDB,然后將RDB文件通過網(wǎng)絡傳輸個slave,成本太高了。因此除了第一次做全量同步,其它大多數(shù)時候slave與master都是做增量同步。
什么是增量同步?就是只更新slave與master存在差異的部分數(shù)據(jù)。如圖:
那么master怎么知道slave與自己的數(shù)據(jù)差異在哪里呢?
2.2.3.repl_backlog原理
master怎么知道slave與自己的數(shù)據(jù)差異在哪里呢?
這就要說到全量同步時的repl_baklog文件了。
這個文件是一個固定大小的數(shù)組,只不過數(shù)組是環(huán)形,也就是說角標到達數(shù)組末尾后,會再次從0開始讀寫,這樣數(shù)組頭部的數(shù)據(jù)就會被覆蓋。
repl_baklog中會記錄Redis處理過的命令日志及offset,包括master當前的offset,和slave已經(jīng)拷貝到的offset:
slave與master的offset之間的差異,就是salve需要增量拷貝的數(shù)據(jù)了。
隨著不斷有數(shù)據(jù)寫入,master的offset逐漸變大,slave也不斷的拷貝,追趕master的offset:
直到數(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ù),直到將slave現(xiàn)在的offset也覆蓋:
棕色框中的紅色部分,就是尚未同步,但是卻已經(jīng)被覆蓋的數(shù)據(jù)。此時如果slave恢復,需要同步,卻發(fā)現(xiàn)自己的offset都沒有了,無法完成增量同步了。只能做全量同步。
2.7.主從同步優(yōu)化
主從同步可以保證主從數(shù)據(jù)的一致性,非常重要。
可以從以下幾個方面來優(yōu)化Redis主從就集群:
- 在master中配置repl-diskless-sync yes啟用無磁盤復制,避免全量同步時的磁盤IO。
- Redis單節(jié)點上的內(nèi)存占用不要太大,減少RDB導致的過多磁盤IO
- 適當提高repl_baklog的大小,發(fā)現(xiàn)slave宕機時盡快實現(xiàn)故障恢復,盡可能避免全量同步
- 限制一個master上的slave節(jié)點數(shù)量,如果實在是太多slave,則可以采用主-從-從鏈式結構,減少master壓力
主從從架構圖:
2.8.小結
簡述全量同步和增量同步區(qū)別?
- 全量同步:master將完整內(nèi)存數(shù)據(jù)生成RDB,發(fā)送RDB到slave。后續(xù)命令則記錄在repl_baklog,逐個發(fā)送給slave。
- 增量同步:slave提交自己的offset到master,master獲取repl_baklog中從offset之后的命令給slave
什么時候執(zhí)行全量同步?
- slave節(jié)點第一次連接master節(jié)點時
- slave節(jié)點斷開時間太久,repl_baklog中的offset已經(jīng)被覆蓋時
什么時候執(zhí)行增量同步?
- slave節(jié)點斷開又恢復,并且在repl_baklog中能找到offset時
三、搭建哨兵集群
3.1.哨兵原理
3.1.1.集群結構和作用
這里我們搭建一個三節(jié)點形成的Sentinel集群,來監(jiān)管之前的Redis主從集群。如圖:
哨兵的作用如下:
- 監(jiān)控:Sentinel 會不斷檢查您的master和slave是否按預期工作
- 自動故障恢復:如果master故障,Sentinel會將一個slave提升為master。當故障實例恢復后也以新的master為主
- 通知:Sentinel充當Redis客戶端的服務發(fā)現(xiàn)來源,當集群發(fā)生故障轉移時,會將最新信息推送給Redis的客戶端
3.1.2.集群監(jiān)控原理
Sentinel基于心跳機制監(jiān)測服務狀態(tài),每隔1秒向集群的每個實例發(fā)送ping命令:
?主觀下線:如果某sentinel節(jié)點發(fā)現(xiàn)某實例未在規(guī)定時間響應,則認為該實例主觀下線。
?客觀下線:若超過指定數(shù)量(quorum)的sentinel都認為該實例主觀下線,則該實例客觀下線。quorum值最好超過Sentinel實例數(shù)量的一半。
3.1.3.集群故障恢復原理
一旦發(fā)現(xiàn)master故障,sentinel需要在salve中選擇一個作為新的master,選擇依據(jù)是這樣的:
- 首先會判斷slave節(jié)點與master節(jié)點斷開時間長短,如果超過指定值(down-after-milliseconds * 10)則會排除該slave節(jié)點
- 然后判斷slave節(jié)點的slave-priority值,越小優(yōu)先級越高,如果是0則永不參與選舉
- 如果slave-prority一樣,則判斷slave節(jié)點的offset值,越大說明數(shù)據(jù)越新,優(yōu)先級越高
- 最后是判斷slave節(jié)點的運行id大小,越小優(yōu)先級越高。
當選出一個新的master后,該如何實現(xiàn)切換呢?
流程如下:
- sentinel給備選的slave1節(jié)點發(fā)送slaveof no one命令,讓該節(jié)點成為master
- sentinel給所有其它slave發(fā)送slaveof 192.168.150.101 7002 命令,讓這些slave成為新master的從節(jié)點,開始從新的master上同步數(shù)據(jù)。
- 最后,sentinel將故障節(jié)點標記為slave,當故障節(jié)點恢復后會自動成為新的master的slave節(jié)點
3.1.4.小結
Sentinel的三個作用是什么?
- 監(jiān)控
- 故障轉移
- 通知
Sentinel如何判斷一個redis實例是否健康?
- 每隔1秒發(fā)送一次ping命令,如果超過一定時間沒有相向則認為是主觀下線
- 如果大多數(shù)sentinel都認為實例主觀下線,則判定服務下線
故障轉移步驟有哪些?
- 首先選定一個slave作為新的master,執(zhí)行slaveof no one
- 然后讓所有節(jié)點都執(zhí)行slaveof 新master
- 修改故障節(jié)點配置,添加slaveof 新master
3.2.準備實例和配置
要在同一臺虛擬機開啟3個實例,必須準備三份不同的配置文件和目錄,配置文件所在目錄也就是工作目錄。
三個sentinel實例信息如下:
| s1 | 192.168.150.101 | 27001 |
| s2 | 192.168.150.101 | 27002 |
| s3 | 192.168.150.101 | 27003 |
我們創(chuàng)建三個文件夾,名字分別叫s1、s2、s3:
# 進入/tmp目錄 cd /tmp # 創(chuàng)建目錄 mkdir s1 s2 s3然后我們在s1目錄創(chuàng)建一個sentinel.conf文件,添加下面的內(nèi)容:
port 27001 sentinel announce-ip 192.168.150.101 sentinel monitor mymaster 192.168.150.101 7001 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 60000 dir "/tmp/s1"解讀:
- port 27001:是當前sentinel實例的端口
- sentinel monitor mymaster 192.168.150.101 7001 2:指定主節(jié)點信息
- mymaster:主節(jié)點名稱,自定義,任意寫
- 192.168.150.101 7001:主節(jié)點的ip和端口
- 2:選舉master時的quorum值
- down-after-milliseconds mymaster:在指定的毫秒數(shù)內(nèi),若主節(jié)點沒有應答哨兵的 PING 命令,此時哨兵認為服務器主觀下線,默認時間為 30 秒。
- failover-timeout mymaster:在該時間內(nèi)未完成failover(故障轉移),則failover(故障轉移)失敗
然后將s1/sentinel.conf文件拷貝到s2、s3兩個目錄中(在/tmp目錄執(zhí)行下列命令):
# 方式一:逐個拷貝 cp s1/sentinel.conf s2 cp s1/sentinel.conf s3 # 方式二:管道組合命令,一鍵拷貝 echo s2 s3 | xargs -t -n 1 cp s1/sentinel.conf修改s2、s3兩個文件夾內(nèi)的配置文件,將端口分別修改為27002、27003:
sed -i -e 's/27001/27002/g' -e 's/s1/s2/g' s2/sentinel.conf sed -i -e 's/27001/27003/g' -e 's/s1/s3/g' s3/sentinel.conf3.3.啟動
為了方便查看日志,我們打開3個ssh窗口,分別啟動3個redis實例,啟動命令:
# 第1個 redis-sentinel s1/sentinel.conf # 第2個 redis-sentinel s2/sentinel.conf # 第3個 redis-sentinel s3/sentinel.conf啟動后:
3.4.測試
嘗試讓master節(jié)點7001宕機,查看sentinel日志:
查看7003,可以看到已經(jīng)變?yōu)橹鞴?jié)點了
再啟動7001,哨兵會讓7001成為7003的從節(jié)點
問題:讓7001宕機后,哨兵沒有進行故障轉移
解決方案
- 兩個從機的redis.conf 文件沒有配置這個
3.5.RedisTemplate
在Sentinel集群監(jiān)管下的Redis主從集群,其節(jié)點會因為自動故障轉移而發(fā)生變化,Redis的客戶端必須感知這種變化,及時更新連接信息。Spring的RedisTemplate底層利用lettuce實現(xiàn)了節(jié)點的感知和自動切換。
3.5.1.引入依賴
在項目的pom文件中引入依賴:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency>3.5.2.配置Redis地址
然后在配置文件application.yml中指定redis的sentinel相關信息:
spring:redis:sentinel:master: mymasternodes:- 192.168.150.101:27001- 192.168.150.101:27002- 192.168.150.101:270033.5.3.配置讀寫分離
在項目的啟動類中,添加一個新的bean:
@Bean public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED); }這個bean中配置的就是讀寫策略,包括四種:
- MASTER:從主節(jié)點讀取
- MASTER_PREFERRED:優(yōu)先從master節(jié)點讀取,master不可用才讀取replica
- REPLICA:從slave(replica)節(jié)點讀取
- REPLICA _PREFERRED:優(yōu)先從slave(replica)節(jié)點讀取,所有的slave都不可用才讀取master
3.5.4.測試
啟動項目,瀏覽器訪問:http://localhost:8080/get/num
查看日志
把日志清掉,刷新界面,查看結果
可以看到請讀求走的7003
瀏覽器發(fā)送:http://localhost:8080/set/num/111
看到寫走的7001,符合讀寫分離
四、Redis分片集群
4.1.搭建分片集群
主從和哨兵可以解決高可用、高并發(fā)讀的問題。但是依然有兩個問題沒有解決:
-
海量數(shù)據(jù)存儲問題
-
高并發(fā)寫的問題
使用分片集群可以解決上述問題,如圖:
分片集群特征:
-
集群中有多個master,每個master保存不同數(shù)據(jù)
-
每個master都可以有多個slave節(jié)點
-
master之間通過ping監(jiān)測彼此健康狀態(tài)
-
客戶端請求可以訪問集群任意節(jié)點,最終都會被轉發(fā)到正確節(jié)點
4.1.1.準備實例和配置
這里我們會在同一臺虛擬機中開啟6個redis實例,模擬分片集群,信息如下:
| 192.168.150.101 | 7001 | master |
| 192.168.150.101 | 7002 | master |
| 192.168.150.101 | 7003 | master |
| 192.168.150.101 | 8001 | slave |
| 192.168.150.101 | 8002 | slave |
| 192.168.150.101 | 8003 | slave |
刪除之前的7001、7002、7003這幾個目錄,重新創(chuàng)建出7001、7002、7003、8001、8002、8003目錄:
# 進入/tmp目錄 cd /tmp # 刪除舊的,避免配置干擾 rm -rf 7001 7002 7003 # 創(chuàng)建目錄 mkdir 7001 7002 7003 8001 8002 8003在/tmp下準備一個新的redis.conf文件,內(nèi)容如下:
port 6379 # 開啟集群功能 cluster-enabled yes # 集群的配置文件名稱,不需要我們創(chuàng)建,由redis自己維護 cluster-config-file /tmp/6379/nodes.conf # 節(jié)點心跳失敗的超時時間 cluster-node-timeout 5000 # 持久化文件存放目錄 dir /tmp/6379 # 綁定地址 bind 0.0.0.0 # 讓redis后臺運行 daemonize yes # 注冊的實例ip replica-announce-ip 192.168.150.101 # 保護模式 protected-mode no # 數(shù)據(jù)庫數(shù)量 databases 1 # 日志 logfile /tmp/6379/run.log將這個文件拷貝到每個目錄下:
# 進入/tmp目錄 cd /tmp # 執(zhí)行拷貝 echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf修改每個目錄下的redis.conf,將其中的6379修改為與所在目錄一致:
# 進入/tmp目錄 cd /tmp # 修改配置文件 printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf4.1.2.啟動
因為已經(jīng)配置了后臺啟動模式,所以可以直接啟動服務:
# 進入/tmp目錄 cd /tmp # 一鍵啟動所有服務 printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf通過ps查看狀態(tài):
ps -ef | grep redis如果要關閉所有進程,可以執(zhí)行命令:
ps -ef | grep redis | awk '{print $2}' | xargs kill或者(推薦這種方式):
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-cli -p {} shutdown4.1.3.創(chuàng)建集群
雖然服務啟動了,但是目前每個服務之間都是獨立的,沒有任何關聯(lián)。
我們需要執(zhí)行命令來創(chuàng)建集群,在Redis5.0之前創(chuàng)建集群比較麻煩,5.0之后集群管理命令都集成到了redis-cli中。
1)Redis5.0之前
Redis5.0之前集群命令都是用redis安裝包下的src/redis-trib.rb來實現(xiàn)的。因為redis-trib.rb是有ruby語言編寫的所以需要安裝ruby環(huán)境。
# 安裝依賴 yum -y install zlib ruby rubygems gem install redis然后通過命令來管理集群:
# 進入redis的src目錄 cd /tmp/redis-6.2.4/src # 創(chuàng)建集群 ./redis-trib.rb create --replicas 1 192.168.150.101:7001 192.168.150.101:7002 192.168.150.101:7003 192.168.150.101:8001 192.168.150.101:8002 192.168.150.101:80032)Redis5.0以后
我們使用的是Redis6.2.4版本,集群管理以及集成到了redis-cli中,格式如下:
redis-cli --cluster create --cluster-replicas 1 192.168.150.101:7001 192.168.150.101:7002 192.168.150.101:7003 192.168.150.101:8001 192.168.150.101:8002 192.168.150.101:8003命令說明:
- redis-cli --cluster或者./redis-trib.rb:代表集群操作命令
- create:代表是創(chuàng)建集群
- --replicas 1或者--cluster-replicas 1 :指定集群中每個master的副本個數(shù)為1,此時節(jié)點總數(shù) ÷ (replicas + 1) 得到的就是master的數(shù)量。因此節(jié)點列表中的前n個就是master,其它節(jié)點都是slave節(jié)點,隨機分配到不同master
運行后的樣子:
這里輸入yes,則集群開始創(chuàng)建:
通過命令可以查看集群狀態(tài):
創(chuàng)建集群時如果出現(xiàn)以下問題:
去安全組開放端口,我這里用的是7001,7002,7003,8001,8002,8003,安全組不僅要開放這6個端口,還要開放17001,17002,17003,18001,18002,18003
原因:
每個Redis集群中的節(jié)點都需要打開兩個TCP連接。一個連接用于正常的給Client提供服務,比如6379,還有一個額外的端口(通過在這個端口號上加10000)作為數(shù)據(jù)端口,比如16379。第二個端口(本例中就是16379)用于集群總線,這是一個用二進制協(xié)議的點對點通信信道。這個集群總線(Cluster bus)用于節(jié)點的失敗偵測、配置更新、故障轉移授權,等等。客戶端從來都不應該嘗試和這些集群總線端口通信,它們只應該和正常的Redis命令端口進行通信。注意,確保在你的防火墻中開放著兩個端口,否則,Redis集群節(jié)點之間將無法通信。
命令端口和集群總線端口的偏移量總是10000
集群操作時,需要給redis-cli加上-c參數(shù)才可以:
4.2.散列插槽
4.2.1.插槽原理
Redis會把每一個master節(jié)點映射到0~16383共16384個插槽(hash slot)上,查看集群信息時就能看到:
數(shù)據(jù)key不是與節(jié)點綁定,而是與插槽綁定。redis會根據(jù)key的有效部分計算插槽值,分兩種情況:
- key中包含"{}",且“{}”中至少包含1個字符,“{}”中的部分是有效部分
- key中不包含“{}”,整個key都是有效部分
例如:key是num,那么就根據(jù)num計算,如果是{itcast}num,則根據(jù)itcast計算。計算方式是利用CRC16算法得到一個hash值,然后對16384取余,得到的結果就是slot值。
如圖,在7001這個節(jié)點執(zhí)行set a 1時,對a做hash運算,對16384取余,得到的結果是15495,因此要存儲到103節(jié)點。
到了7003后,執(zhí)行get num時,對num做hash運算,對16384取余,得到的結果是2765,因此需要切換到7001節(jié)點
4.2.2.小結
Redis如何判斷某個key應該在哪個實例?
- 將16384個插槽分配到不同的實例
- 根據(jù)key的有效部分計算哈希值,對16384取余
- 余數(shù)作為插槽,尋找插槽所在實例即可
如何將同一類數(shù)據(jù)固定的保存在同一個Redis實例?
- 這一類數(shù)據(jù)使用相同的有效部分,例如key都以{typeId}為前綴
4.3.集群伸縮
redis-cli --cluster提供了很多操作集群的命令,可以通過下面方式查看:
redis-cli --cluster help4.3.1.需求分析
需求:向集群中添加一個新的master節(jié)點,并向其中存儲 num = 10
- 啟動一個新的redis實例,端口為7004
- 添加7004到之前的集群,并作為一個master節(jié)點
- 給7004節(jié)點分配插槽,使得num這個key可以存儲到7004實例
這里需要兩個新的功能:
- 添加一個節(jié)點到集群中
- 將部分插槽分配到新插槽
4.3.2.創(chuàng)建新的redis實例
創(chuàng)建一個文件夾:
mkdir 7004拷貝配置文件:
cp redis.conf 7004修改配置文件:
printf '%s\n' 7004 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf安全組開啟7004和17004端口后啟動
redis-server 7004/redis.conf4.3.3.添加新節(jié)點到redis
添加節(jié)點的語法如下:
執(zhí)行命令:
通過命令查看集群狀態(tài):
redis-cli -p 7001 cluster nodes如圖,7004加入了集群,并且默認是一個master節(jié)點:
但是,可以看到7004節(jié)點的插槽數(shù)量為0,因此沒有任何數(shù)據(jù)可以存儲到7004上
4.3.4.轉移插槽
我們要將num存儲到7004節(jié)點,因此需要先看看num的插槽是多少:num的插槽為2765.
我們可以將0~3000的插槽從7001轉移到7004,命令格式如下:
具體命令如下:
建立連接:
redis-cli --cluster reshard 192.168.150.101:7001得到下面的反饋:
2765<3000,轉移3000個就行
source1:填7001的id
這里詢問,你的插槽是從哪里移動過來的?
- all:代表全部,也就是三個節(jié)點各轉移一部分
- 具體的id:目標節(jié)點的id
- done:沒有了
通過命令查看結果:
4.4.故障轉移
集群現(xiàn)在狀態(tài)是這樣的:
其中7001、7002、7003、7004都是master,我們計劃讓7002宕機。
4.4.1.自動故障轉移
當集群中有一個master宕機會發(fā)生什么呢?
直接停止一個redis實例,例如7002:
1)首先是該實例與其它實例失去連接
2)然后是疑似宕機
3)最后是確定下線,自動提升一個slave為新的master:
4)當7002再次啟動,就會變?yōu)橐粋€slave節(jié)點了:
4.4.2.手動故障轉移
利用cluster failover命令可以手動讓集群中的某個master宕機,切換到執(zhí)行cluster failover命令的這個slave節(jié)點,實現(xiàn)無感知的數(shù)據(jù)遷移。其流程如下:
這種failover命令可以指定三種模式:
- 缺省:默認的流程,如圖1~6歩
- force:省略了對offset的一致性校驗
- takeover:直接執(zhí)行第5歩,忽略數(shù)據(jù)一致性、忽略master狀態(tài)和其它master的意見
案例需求:在7002這個slave節(jié)點執(zhí)行手動故障轉移,重新奪回master地位
步驟如下:
1)利用redis-cli連接7002這個節(jié)點
2)執(zhí)行cluster failover命令
如圖:
效果:
4.5.RedisTemplate訪問分片集群
RedisTemplate底層同樣基于lettuce實現(xiàn)了分片集群的支持,而使用的步驟與哨兵模式基本一致:
1)引入redis的starter依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId> </dependency>2)配置分片集群地址
3)配置讀寫分離
與哨兵模式相比,其中只有分片集群的配置方式略有差異,如下:
spring:redis:cluster:nodes:- 150.158.164.135:7001- 150.158.164.135:7002- 150.158.164.135:7003- 150.158.164.135:7004- 150.158.164.135:8001- 150.158.164.135:8002- 150.158.164.135:8003lettuce:pool:max-active: 20 # 連接池最大連接數(shù)(使用負值表示沒有限制)max-idle: 10 # 連接池中的最大空閑連接min-idle: 5 # 連接池中的最小空閑連接max-wait: 5000ms # 連接池最大阻塞等待時間(使用負值表示沒有限制)在瀏覽器訪問:http://localhost:8080/get/num
因為上面做插槽轉移num應該打到7004上,7004是主節(jié)點,沒有從節(jié)點,所以打到了7004上
訪問個get/b,8001是從節(jié)點,讀訪問從節(jié)點
set/b/qqq,應該寫7001
總結
以上是生活随笔為你收集整理的七、redis分布式集群的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Implementation Patte
- 下一篇: 沿江镇访古(二)