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