Redis緩存高可用集群詳解
一、Redis集群方案比較
1、哨兵模式

在redis3.0以前的版本要實現(xiàn)集群一般是借助哨兵sentinel工具來監(jiān)控master節(jié)點的狀態(tài),如果master節(jié)點異常,則會做主從切換,將某一臺slave作為master,哨兵的配置略微復雜,并且性能和高可用性等各方面表現(xiàn)一般,特別是在主從切換的瞬間存在訪問瞬斷的情況,而且哨兵模式只有一個主節(jié)點對外提供服務,沒法支持很高的并發(fā),且單個主節(jié)點內存也不宜設置得過大,否則會導致持久化文件過大,影響數(shù)據(jù)恢復或主從同步的效率
2、高可用集群模式

redis集群是一個由多個主從節(jié)點群組成的分布式服務器群,它具有復制、高可用和分片特性。
Redis集群不需要sentinel哨兵也能完成節(jié)點移除和故障轉移的功能。需要將每個節(jié)點設置成集群模式,這種集群模式?jīng)]有中心節(jié)點,可水平擴展,據(jù)官方文檔稱可以線性擴展到上萬個節(jié)點(官方推薦不超過1000個節(jié)點)。
redis集群的性能和高可用性均優(yōu)于之前版本的哨兵模式,且集群配置非常簡單
二、Redis高可用集群搭建
redis集群需要至少三個master節(jié)點,我們這里搭建三個master節(jié)點,并且給每個master再搭建一個slave節(jié)點,總共6個redis節(jié)點,這里用三臺機器部署6個redis實例,每臺機器一主一從,搭建集群的步驟如下:
1、在第一臺服務器建立兩個節(jié)點
mkdir -p /usr/local/redis-cluster mkdir 8001 8004
2、第一個節(jié)點的redis.conf配置
改配置的時候記得檢查原來的配置,查找的快捷鍵:Esc+“/+關鍵詞”+回車(按n找到下一個)
(1)基本配置
# 允許訪問的地址,默認是127.0.0.1,會導致只能在本地訪問。修改為0.0.0.0則可以在任意IP訪問,生產(chǎn)環(huán)境不要設置為0.0.0.0 bind 0.0.0.0 # 守護進程,修改為yes后即可后臺運行 daemonize yes # 監(jiān)聽的端口 port 8001 # 指定數(shù)據(jù)文件存放位置,必須要指定不同的目錄位置,不然會丟失數(shù)據(jù) dir /usr/local/redis-cluster/8001/ # 日志文件,默認為空,不記錄日志,可以指定日志文件名 logfile "/usr/local/redis-cluster/8001/redis.log" # 把pid進程號寫入pidfile配置的文件 pidfile /var/run/redis_8001.pid # 密碼,設置后訪問Redis必須輸入密碼 # requirepass whr # 設置集群節(jié)點間訪問密碼,跟上面一致 # masterauth whr # 設置為no,外圍機器可以訪問 protected-mode no # 開啟aof快照 appendonly yes
(2)核心配置
# 啟動集群模式 cluster-enabled yes # 集群節(jié)點信息文件,這里800x最好和port對應上 cluster-config-file nodes-8001.conf # 超時時間 cluster-node-timeout 10000
(1)把修改后的配置文件,copy到8004,修改端口號信息,可以用批量替換:
:%s/源字符串/目的字符串/g
(2)另外兩臺服務器也需要做上面幾步操作,第二臺機器用8002和8005,第三臺機器用8003和8006
3、啟動6個redis實例
ps -ef | grep redis 查看是否啟動成功
4、關閉防火墻(也可以開放每一個reids所對應的端口號)
# systemctl stop firewalld # 臨時關閉防火墻 # systemctl disable firewalld # 禁止開機啟動
5、用redis-cli創(chuàng)建整個redis集群
任意找一臺機器,輸入下面的指令:
redis-cli -a whr --cluster create --cluster-replicas 1 192.168.92.201:8001 192.168.92.202:8002 192.168.92.203:8003 192.168.92.201:8004 192.168.92.202:8005 192.168.92.203:8006
(1)-a whr: 密碼是whr
(2)–cluster-replicas 1:每一個master下面都有一個從節(jié)點(默認會將我們命令中的前三個設置為主節(jié)點,從節(jié)點是隨機的。)

這里的三個段表示我們我們的三個主節(jié)點分別對應這三個段,每當我們set一個值時,會先計算這個key的hash值,根據(jù)這個位桶數(shù)組取模判斷是放在哪個節(jié)點中,三個節(jié)點的數(shù)據(jù)互相不重復。
6、驗證集群
(1)連接任意一個客戶端:redis-cli -a whr -c -h 192.168.92.201 -p 8001 (-a訪問服務端密碼,-c表示集群模式,-h指定ip地址,-p指定端口號)

(2)查看集群信息:cluster info
(3)查看節(jié)點列表:cluster nodes
(4)關閉集群(需要逐個進行關閉):./redis-cli -a ale -c -h 192.168.231.134 -p 9001 shutdown
三、Java操作集群
1、引入相關的依賴
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>2、案例
public class JedisClusterTest {
public static void main(String[] args) {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(20);
config.setMaxIdle(10);
config.setMinIdle(5);
Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
jedisClusterNode.add(new HostAndPort("192.168.92.201", 8001));
jedisClusterNode.add(new HostAndPort("192.168.92.202", 8002));
jedisClusterNode.add(new HostAndPort("192.168.92.203", 8003));
jedisClusterNode.add(new HostAndPort("192.168.92.201", 8004));
jedisClusterNode.add(new HostAndPort("192.168.92.202", 8005));
jedisClusterNode.add(new HostAndPort("192.168.92.203", 8006));
JedisCluster jedisCluster = null;
try {
//connectionTimeout:指的是連接一個url的連接等待時間
//soTimeout:指的是連接上一個url,獲取response的返回等待時間
jedisCluster = new JedisCluster(jedisClusterNode, 6000, 5000, 10,'whr', config);
System.out.println(jedisCluster.set("nihao","halou"));
System.out.println(jedisCluster.get("nihao"));
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedisCluster != null) {
jedisCluster.close();
}
}
}
}四、SpringBoot操作集群
1、引入相關的依賴
<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、springboot項目核心配置
server:
port: 8080
spring:
redis:
database: 0
timeout: 3000
password: whr
cluster:
nodes: 192.168.92.201:8001,192.168.92.202:8002,192.168.92.203:8003,192.168.92.201:8004,192.168.92.202:8005,192.168.92.203:8006
lettuce:
pool:
max-idle: 50
min-idle: 10
max-active: 100
max-wait: 10003、案例
@RestController
public class IndexController {
private static final Logger logger = LoggerFactory.getLogger(IndexController.class);
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/test_cluster")
public void testCluster() throws InterruptedException {
stringRedisTemplate.opsForValue().set("nihao", "666");
System.out.println(stringRedisTemplate.opsForValue().get("nihao"));
}
}五、Redis集群原理分析
(1)Redis Cluster 將所有數(shù)據(jù)劃分為 16384 個 slots(槽位),每個節(jié)點負責其中一部分槽位。槽位的信息存儲于每個節(jié)點中。
(2)當 Redis Cluster 的客戶端來連接集群時,它也會得到一份集群的槽位配置信息并將其緩存在客戶端本地。這樣當客戶端要查找某個 key 時,可以直接定位到目標節(jié)點。
(3)同時因為槽位的信息可能會存在客戶端與服務器不一致的情況,還需要糾正機制來實現(xiàn)槽位信息的校驗調整。
1、槽位定位算法
Cluster 默認會對 key 值使用 crc16 算法進行 hash 得到一個整數(shù)值,然后用這個整數(shù)值對 16384 進行取模來得到具體槽位:HASH_SLOT = CRC16(key) &(16384-1)
2、跳轉重定位
當客戶端向一個錯誤的節(jié)點發(fā)出了指令,該節(jié)點會發(fā)現(xiàn)指令的 key 所在的槽位并不歸自己管理,這時它會向客戶端發(fā)送一個特殊的跳轉指令攜帶目標操作的節(jié)點地址,告訴客戶端去連這個節(jié)點去獲取數(shù)據(jù)。
客戶端收到指令后除了跳轉到正確的節(jié)點上去操作,還會同步更新糾正本地的槽位映射表緩存,后續(xù)所有 key 將使用新的槽位映射表。

3、Redis集群節(jié)點間的通信機制
redis cluster節(jié)點間采取gossip協(xié)議進行通信維護集群的元數(shù)據(jù)(集群節(jié)點信息,主從角色,節(jié)點數(shù)量,各節(jié)點共享的數(shù)據(jù)等)有兩種方式:集中式和gossip
3.1、集中式
很多中間件都會借助zookeeper集中式存儲元數(shù)據(jù)。
(1)優(yōu)點:元數(shù)據(jù)的更新和讀取,時效性非常好,一旦元數(shù)據(jù)出現(xiàn)變更立即就會更新到集中式的存儲中,其他節(jié)點讀取的時候立即就可以立即感知到;
(2)缺點:所有的元數(shù)據(jù)的更新壓力全部集中在一個地方,可能導致元數(shù)據(jù)的存儲壓力。
3.2、gossip

gossip協(xié)議包含多種消息,包括ping,pong,meet,fail等等。
(1)meet:某個節(jié)點發(fā)送meet給新加入的節(jié)點,讓新節(jié)點加入集群中,然后新節(jié)點就會開始與其他節(jié)點進行通信;
(2)ping:每個節(jié)點都會頻繁給其他節(jié)點發(fā)送ping,其中包含自己的狀態(tài)還有自己維護的集群元數(shù)據(jù),互相通過ping交換元數(shù)據(jù)(類似自己感知到的集群節(jié)點增加和移除,hash slot信息等);
(3)pong: 對ping和meet消息的返回,包含自己的狀態(tài)和其他信息,也可以用于信息廣播和更新;
(4)fail: 某個節(jié)點判斷另一個節(jié)點fail之后,就發(fā)送fail給其他節(jié)點,通知其他節(jié)點,指定的節(jié)點宕機了。
優(yōu)點:元數(shù)據(jù)的更新比較分散,不是集中在一個地方,更新請求會陸陸續(xù)續(xù),打到所有節(jié)點上去更新,有一定的延時,降低了壓力;
缺點:元數(shù)據(jù)更新有延時可能導致集群的一些操作會有一些滯后。
補充知識:
- gossip通信的10000端口每個節(jié)點都有一個專門用于節(jié)點間gossip通信的端口,就是自己提供服務的端口號+10000,比如8001,那么用于節(jié)點間通信的就是18001端口。
- 每個節(jié)點每隔一段時間都會往另外幾個節(jié)點發(fā)送ping消息,同時其他節(jié)點接收到ping消息之后返回pong消息。
4、Redis集群選舉原理
當slave發(fā)現(xiàn)自己的master變?yōu)镕AIL狀態(tài)時,便嘗試進行Failover,以期成為新的master。
由于掛掉的master可能會有多個slave,從而存在多個slave競爭成為master節(jié)點的過程, 其過程如下:
- (1)slave發(fā)現(xiàn)自己的master變?yōu)镕AIL
- (2)將自己記錄的集群currentEpoch加1,并廣播FAILOVER_AUTH_REQUEST 信息
- (3)其他節(jié)點收到該信息,只有master響應,判斷請求者的合法性,并發(fā)送FAILOVER_AUTH_ACK,對每一個epoch只發(fā)送一次ack
- (4)嘗試failover的slave收集master返回的FAILOVER_AUTH_ACK
- (5)slave收到超過半數(shù)master的ack后變成新Master(這里解釋了集群為什么至少需要三個主節(jié)點,如果只有兩個,當其中一個掛了,只剩一個主節(jié)點是不能選舉成功的)
- (6)slave廣播Pong消息通知其他集群節(jié)點。
如果說從節(jié)點們都平票了怎么辦,平票就需要重新選舉,重新走一次上面的流程,那如果每一次都平票了怎么辦,其實這種問題redis有自己的一個解決辦法,那就是每一個從節(jié)點不是一旦感知到主節(jié)點沒有響應就直接發(fā)的,而是會有一個延遲算法,在這個時間經(jīng)過之后,才會向外發(fā)送請求,自薦自己要當主節(jié)點。
歷史版本有一個延遲時間算法:
DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
SLAVE_RANK表示此slave已經(jīng)從master復制數(shù)據(jù)的總量的rank。Rank越小代表已復制的數(shù)據(jù)越新。這種方式下,持有最新數(shù)據(jù)的slave將會首先發(fā)起選舉(理論上)
5、Redis集群為至少需要三個master節(jié)點,并且推薦節(jié)點數(shù)為奇數(shù)(也可以偶數(shù))的原因
因為新master的選舉需要大于半數(shù)的集群master節(jié)點同意才能選舉成功,如果只有兩個master節(jié)點,當其中一個掛了,是達不到選舉新master的條件的。
奇數(shù)個master節(jié)點可以在滿足選舉該條件的基礎上節(jié)省一個節(jié)點,比如三個master節(jié)點和四個master節(jié)點的集群相比,大家如果都掛了一個master節(jié)點都能選舉新master節(jié)點,如果都掛了兩個master節(jié)點都沒法選舉新master節(jié)點了,所以奇數(shù)的master節(jié)點更多的是從節(jié)省機器資源角度出發(fā)說的。
6、Redis集群對批量操作命令的支持
對于類似mset,mget這樣的多個key的原生批量操作命令,redis集群只支持所有key落在同一slot的情況,如果有多個key一定要用mset命令在redis集群上操作,則可以在key的前面加上{XX},這樣參數(shù)數(shù)據(jù)分片hash計算的只會是大括號里的值,這樣能確保不同的key能落到同一slot里去,
示例如下:
mset {user1}:1:name zhuge {user1}:1:age 18六、Redis集群相關的問題
1、網(wǎng)絡抖動
真實世界的機房網(wǎng)絡往往并不是風平浪靜的,它們經(jīng)常會發(fā)生各種各樣的小問題。比如網(wǎng)絡抖動就是非常常見的一種現(xiàn)象,突然之間部分連接變得不可訪問,然后很快又恢復正常。
為解決這種問題,Redis Cluster 提供了一種選項cluster-node-timeout,表示當某個節(jié)點持續(xù) timeout 的時間失聯(lián)時,才可以認定該節(jié)點出現(xiàn)故障,需要進行主從切換,若是該節(jié)點在這個時間段又恢復正常,則不需要主從切換。如果沒有這個選項,網(wǎng)絡抖動會導致主從頻繁切換 (數(shù)據(jù)的重新復制)。
2、集群腦裂數(shù)據(jù)丟失問題
redis集群沒有過半機制會有腦裂問題,網(wǎng)絡分區(qū)導致腦裂后多個主節(jié)點對外提供寫服務,一旦網(wǎng)絡分區(qū)恢復,會將其中一個主節(jié)點變?yōu)閺墓?jié)點,這時會有大量數(shù)據(jù)丟失。
規(guī)避方法可以在redis配置里加上參數(shù)(這種方法不可能百分百避免數(shù)據(jù)丟失,參考集群leader選舉機制)
min-replicas-to-write 1 //寫數(shù)據(jù)成功最少同步的slave數(shù)量,這個數(shù)量可以模仿大于半數(shù)機制配置,
//比如集群總共三個節(jié)點可以配置1,加上leader就是2,超過了半數(shù)注意:這個配置在一定程度上會影響集群的可用性,比如slave要是少于1個,這個集群就算leader正常也不能提供服務了,需要具體場景權衡選擇
3、集群是否完整才能對外提供服務
當redis.conf的配置cluster-require-full-coverage為no時,意思是大集群里面有好多小集群,當有一個小集群的主節(jié)點掛掉之后,已經(jīng)沒有從節(jié)點給它頂上的時候,說明這個小集群已經(jīng)癱瘓掉,當上面的配置為no時,整個的集群還是可用的,如果為yes,整個集群就不能用了。
總結
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Caffeine結合Redis空值緩存實現(xiàn)多級緩存
本文介紹了SpringBoot整合Caffeine本地緩存+Redis分布式緩存+空值緩存的三級緩存方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2026-01-01
Redis數(shù)據(jù)庫的使用場景介紹(避免誤用Redis)
這篇文章主要介紹了Redis數(shù)據(jù)庫的使用場景介紹(避免誤用Redis),本文用簡要的語言總結了Redis數(shù)據(jù)庫的適應場合,人而避免錯誤的使用它而產(chǎn)生昂貴的維護代價,需要的朋友可以參考下2015-03-03
NoSQL和Redis簡介及Redis在Windows下的安裝和使用教程
這篇文章主要介紹了NoSQL和Redis簡介及Redis在Windows下的安裝和使用教程,本文同時講解了python操作redis,并給出了操作實例,需要的朋友可以參考下2015-01-01

