Redis緩存高可用集群詳解
一、Redis集群方案比較
1、哨兵模式
在redis3.0以前的版本要實現(xiàn)集群一般是借助哨兵sentinel工具來監(jiān)控master節(jié)點的狀態(tài),如果master節(jié)點異常,則會做主從切換,將某一臺slave作為master,哨兵的配置略微復(fù)雜,并且性能和高可用性等各方面表現(xiàn)一般,特別是在主從切換的瞬間存在訪問瞬斷的情況,而且哨兵模式只有一個主節(jié)點對外提供服務(wù),沒法支持很高的并發(fā),且單個主節(jié)點內(nèi)存也不宜設(shè)置得過大,否則會導(dǎo)致持久化文件過大,影響數(shù)據(jù)恢復(fù)或主從同步的效率
2、高可用集群模式
redis集群是一個由多個主從節(jié)點群組成的分布式服務(wù)器群,它具有復(fù)制、高可用和分片特性。
Redis集群不需要sentinel哨兵也能完成節(jié)點移除和故障轉(zhuǎn)移的功能。需要將每個節(jié)點設(shè)置成集群模式,這種集群模式?jīng)]有中心節(jié)點,可水平擴(kuò)展,據(jù)官方文檔稱可以線性擴(kuò)展到上萬個節(jié)點(官方推薦不超過1000個節(jié)點)。
redis集群的性能和高可用性均優(yōu)于之前版本的哨兵模式,且集群配置非常簡單
二、Redis高可用集群搭建
redis集群需要至少三個master節(jié)點,我們這里搭建三個master節(jié)點,并且給每個master再搭建一個slave節(jié)點,總共6個redis節(jié)點,這里用三臺機(jī)器部署6個redis實例,每臺機(jī)器一主一從,搭建集群的步驟如下:
1、在第一臺服務(wù)器建立兩個節(jié)點
mkdir -p /usr/local/redis-cluster mkdir 8001 8004
2、第一個節(jié)點的redis.conf配置
改配置的時候記得檢查原來的配置,查找的快捷鍵:Esc+“/+關(guān)鍵詞”+回車(按n找到下一個)
(1)基本配置
# 允許訪問的地址,默認(rèn)是127.0.0.1,會導(dǎo)致只能在本地訪問。修改為0.0.0.0則可以在任意IP訪問,生產(chǎn)環(huán)境不要設(shè)置為0.0.0.0 bind 0.0.0.0 # 守護(hù)進(jìn)程,修改為yes后即可后臺運行 daemonize yes # 監(jiān)聽的端口 port 8001 # 指定數(shù)據(jù)文件存放位置,必須要指定不同的目錄位置,不然會丟失數(shù)據(jù) dir /usr/local/redis-cluster/8001/ # 日志文件,默認(rèn)為空,不記錄日志,可以指定日志文件名 logfile "/usr/local/redis-cluster/8001/redis.log" # 把pid進(jìn)程號寫入pidfile配置的文件 pidfile /var/run/redis_8001.pid # 密碼,設(shè)置后訪問Redis必須輸入密碼 # requirepass whr # 設(shè)置集群節(jié)點間訪問密碼,跟上面一致 # masterauth whr # 設(shè)置為no,外圍機(jī)器可以訪問 protected-mode no # 開啟aof快照 appendonly yes
(2)核心配置
# 啟動集群模式 cluster-enabled yes # 集群節(jié)點信息文件,這里800x最好和port對應(yīng)上 cluster-config-file nodes-8001.conf # 超時時間 cluster-node-timeout 10000
(1)把修改后的配置文件,copy到8004,修改端口號信息,可以用批量替換:
:%s/源字符串/目的字符串/g
(2)另外兩臺服務(wù)器也需要做上面幾步操作,第二臺機(jī)器用8002和8005,第三臺機(jī)器用8003和8006
3、啟動6個redis實例
ps -ef | grep redis 查看是否啟動成功
4、關(guān)閉防火墻(也可以開放每一個reids所對應(yīng)的端口號)
# systemctl stop firewalld # 臨時關(guān)閉防火墻 # systemctl disable firewalld # 禁止開機(jī)啟動
5、用redis-cli創(chuàng)建整個redis集群
任意找一臺機(jī)器,輸入下面的指令:
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é)點(默認(rèn)會將我們命令中的前三個設(shè)置為主節(jié)點,從節(jié)點是隨機(jī)的。)
這里的三個段表示我們我們的三個主節(jié)點分別對應(yīng)這三個段,每當(dāng)我們set一個值時,會先計算這個key的hash值,根據(jù)這個位桶數(shù)組取模判斷是放在哪個節(jié)點中,三個節(jié)點的數(shù)據(jù)互相不重復(fù)。
6、驗證集群
(1)連接任意一個客戶端:redis-cli -a whr -c -h 192.168.92.201 -p 8001 (-a訪問服務(wù)端密碼,-c表示集群模式,-h指定ip地址,-p指定端口號)
(2)查看集群信息:cluster info
(3)查看節(jié)點列表:cluster nodes
(4)關(guān)閉集群(需要逐個進(jìn)行關(guān)閉):./redis-cli -a ale -c -h 192.168.231.134 -p 9001 shutdown
三、Java操作集群
1、引入相關(guān)的依賴
<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、引入相關(guān)的依賴
<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: 1000
3、案例
@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é)點負(fù)責(zé)其中一部分槽位。槽位的信息存儲于每個節(jié)點中。
(2)當(dāng) Redis Cluster 的客戶端來連接集群時,它也會得到一份集群的槽位配置信息并將其緩存在客戶端本地。這樣當(dāng)客戶端要查找某個 key 時,可以直接定位到目標(biāo)節(jié)點。
(3)同時因為槽位的信息可能會存在客戶端與服務(wù)器不一致的情況,還需要糾正機(jī)制來實現(xiàn)槽位信息的校驗調(diào)整。
1、槽位定位算法
Cluster 默認(rèn)會對 key 值使用 crc16 算法進(jìn)行 hash 得到一個整數(shù)值,然后用這個整數(shù)值對 16384 進(jìn)行取模來得到具體槽位:HASH_SLOT = CRC16(key) &(16384-1)
2、跳轉(zhuǎn)重定位
當(dāng)客戶端向一個錯誤的節(jié)點發(fā)出了指令,該節(jié)點會發(fā)現(xiàn)指令的 key 所在的槽位并不歸自己管理,這時它會向客戶端發(fā)送一個特殊的跳轉(zhuǎn)指令攜帶目標(biāo)操作的節(jié)點地址,告訴客戶端去連這個節(jié)點去獲取數(shù)據(jù)。
客戶端收到指令后除了跳轉(zhuǎn)到正確的節(jié)點上去操作,還會同步更新糾正本地的槽位映射表緩存,后續(xù)所有 key 將使用新的槽位映射表。
3、Redis集群節(jié)點間的通信機(jī)制
redis cluster節(jié)點間采取gossip協(xié)議進(jìn)行通信維護(hù)集群的元數(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ù)的更新壓力全部集中在一個地方,可能導(dǎo)致元數(shù)據(jù)的存儲壓力。
3.2、gossip
gossip協(xié)議包含多種消息,包括ping,pong,meet,fail等等。
(1)meet:某個節(jié)點發(fā)送meet給新加入的節(jié)點,讓新節(jié)點加入集群中,然后新節(jié)點就會開始與其他節(jié)點進(jìn)行通信;
(2)ping:每個節(jié)點都會頻繁給其他節(jié)點發(fā)送ping,其中包含自己的狀態(tài)還有自己維護(hù)的集群元數(shù)據(jù),互相通過ping交換元數(shù)據(jù)(類似自己感知到的集群節(jié)點增加和移除,hash slot信息等);
(3)pong: 對ping和meet消息的返回,包含自己的狀態(tài)和其他信息,也可以用于信息廣播和更新;
(4)fail: 某個節(jié)點判斷另一個節(jié)點fail之后,就發(fā)送fail給其他節(jié)點,通知其他節(jié)點,指定的節(jié)點宕機(jī)了。
優(yōu)點:元數(shù)據(jù)的更新比較分散,不是集中在一個地方,更新請求會陸陸續(xù)續(xù),打到所有節(jié)點上去更新,有一定的延時,降低了壓力;
缺點:元數(shù)據(jù)更新有延時可能導(dǎo)致集群的一些操作會有一些滯后。
補(bǔ)充知識:
- gossip通信的10000端口每個節(jié)點都有一個專門用于節(jié)點間gossip通信的端口,就是自己提供服務(wù)的端口號+10000,比如8001,那么用于節(jié)點間通信的就是18001端口。
- 每個節(jié)點每隔一段時間都會往另外幾個節(jié)點發(fā)送ping消息,同時其他節(jié)點接收到ping消息之后返回pong消息。
4、Redis集群選舉原理
當(dāng)slave發(fā)現(xiàn)自己的master變?yōu)镕AIL狀態(tài)時,便嘗試進(jìn)行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響應(yīng),判斷請求者的合法性,并發(fā)送FAILOVER_AUTH_ACK,對每一個epoch只發(fā)送一次ack
- (4)嘗試failover的slave收集master返回的FAILOVER_AUTH_ACK
- (5)slave收到超過半數(shù)master的ack后變成新Master(這里解釋了集群為什么至少需要三個主節(jié)點,如果只有兩個,當(dāng)其中一個掛了,只剩一個主節(jié)點是不能選舉成功的)
- (6)slave廣播Pong消息通知其他集群節(jié)點。
如果說從節(jié)點們都平票了怎么辦,平票就需要重新選舉,重新走一次上面的流程,那如果每一次都平票了怎么辦,其實這種問題redis有自己的一個解決辦法,那就是每一個從節(jié)點不是一旦感知到主節(jié)點沒有響應(yīng)就直接發(fā)的,而是會有一個延遲算法,在這個時間經(jīng)過之后,才會向外發(fā)送請求,自薦自己要當(dāng)主節(jié)點。
歷史版本有一個延遲時間算法:
DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
SLAVE_RANK表示此slave已經(jīng)從master復(fù)制數(shù)據(jù)的總量的rank。Rank越小代表已復(fù)制的數(shù)據(jù)越新。這種方式下,持有最新數(shù)據(jù)的slave將會首先發(fā)起選舉(理論上)
5、Redis集群為至少需要三個master節(jié)點,并且推薦節(jié)點數(shù)為奇數(shù)(也可以偶數(shù))的原因
因為新master的選舉需要大于半數(shù)的集群master節(jié)點同意才能選舉成功,如果只有兩個master節(jié)點,當(dāng)其中一個掛了,是達(dá)不到選舉新master的條件的。
奇數(shù)個master節(jié)點可以在滿足選舉該條件的基礎(chǔ)上節(jié)省一個節(jié)點,比如三個master節(jié)點和四個master節(jié)點的集群相比,大家如果都掛了一個master節(jié)點都能選舉新master節(jié)點,如果都掛了兩個master節(jié)點都沒法選舉新master節(jié)點了,所以奇數(shù)的master節(jié)點更多的是從節(jié)省機(jī)器資源角度出發(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集群相關(guān)的問題
1、網(wǎng)絡(luò)抖動
真實世界的機(jī)房網(wǎng)絡(luò)往往并不是風(fēng)平浪靜的,它們經(jīng)常會發(fā)生各種各樣的小問題。比如網(wǎng)絡(luò)抖動就是非常常見的一種現(xiàn)象,突然之間部分連接變得不可訪問,然后很快又恢復(fù)正常。
為解決這種問題,Redis Cluster 提供了一種選項cluster-node-timeout,表示當(dāng)某個節(jié)點持續(xù) timeout 的時間失聯(lián)時,才可以認(rèn)定該節(jié)點出現(xiàn)故障,需要進(jìn)行主從切換,若是該節(jié)點在這個時間段又恢復(fù)正常,則不需要主從切換。如果沒有這個選項,網(wǎng)絡(luò)抖動會導(dǎo)致主從頻繁切換 (數(shù)據(jù)的重新復(fù)制)。
2、集群腦裂數(shù)據(jù)丟失問題
redis集群沒有過半機(jī)制會有腦裂問題,網(wǎng)絡(luò)分區(qū)導(dǎo)致腦裂后多個主節(jié)點對外提供寫服務(wù),一旦網(wǎng)絡(luò)分區(qū)恢復(fù),會將其中一個主節(jié)點變?yōu)閺墓?jié)點,這時會有大量數(shù)據(jù)丟失。
規(guī)避方法可以在redis配置里加上參數(shù)(這種方法不可能百分百避免數(shù)據(jù)丟失,參考集群leader選舉機(jī)制)
min-replicas-to-write 1 //寫數(shù)據(jù)成功最少同步的slave數(shù)量,這個數(shù)量可以模仿大于半數(shù)機(jī)制配置, //比如集群總共三個節(jié)點可以配置1,加上leader就是2,超過了半數(shù)
注意:這個配置在一定程度上會影響集群的可用性,比如slave要是少于1個,這個集群就算leader正常也不能提供服務(wù)了,需要具體場景權(quán)衡選擇
3、集群是否完整才能對外提供服務(wù)
當(dāng)redis.conf的配置cluster-require-full-coverage為no時,意思是大集群里面有好多小集群,當(dāng)有一個小集群的主節(jié)點掛掉之后,已經(jīng)沒有從節(jié)點給它頂上的時候,說明這個小集群已經(jīng)癱瘓掉,當(dāng)上面的配置為no時,整個的集群還是可用的,如果為yes,整個集群就不能用了。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
使用Redis防止重復(fù)發(fā)送RabbitMQ消息的方法詳解
今天遇到一個問題,發(fā)送MQ消息的時候需要保證不會重復(fù)發(fā)送,注意不是可靠到達(dá),這里保證的是不會生產(chǎn)多條一樣的消息,所以本文主要介紹了使用Redis防止重復(fù)發(fā)送RabbitMQ消息的方法,需要的朋友可以參考下2025-01-01Redis生成分布式系統(tǒng)全局唯一ID的實現(xiàn)
在互聯(lián)網(wǎng)系統(tǒng)中,并發(fā)越大的系統(tǒng),數(shù)據(jù)就越大,數(shù)據(jù)越大就越需要分布式,本文主要介紹了Redis生成分布式系統(tǒng)全局唯一ID的實現(xiàn),感興趣的可以了解一下2021-10-10