摸透 Redis 主从复制、哨兵、Cluster 三种模式
?
概述
Redis作為緩存的高效中間件,在我們?nèi)粘5拈_(kāi)發(fā)中被頻繁的使用,今天就來(lái)說(shuō)一說(shuō)Redis的四種模式,分別是「單機(jī)版、主從復(fù)制、哨兵、以及集群模式」。
可能,在一般公司的程序員使用單機(jī)版基本都能解決問(wèn)題,在Redis的官網(wǎng)給出的數(shù)據(jù)是10W QPS,這對(duì)于應(yīng)付一般的公司綽綽有余了,再不行就來(lái)個(gè)主從模式,實(shí)現(xiàn)讀寫分離,性能又大大提高。
但是,我們作為有抱負(fù)的程序員,僅限于單機(jī)版和主從模式的crud是不行的,至少也要了解「哨兵」和「集群模式」的原理,這樣面試的時(shí)候才能和面試官扯皮啊。
之前對(duì)于Redis方面也是寫了比較多的文章,如:「Redis的基本數(shù)據(jù)類型和底層的實(shí)現(xiàn)原理、事務(wù)、持久化、分布式鎖、訂閱預(yù)發(fā)布」等,可以說(shuō)是比較全面的教程了,這篇講完基本就全了,我會(huì)把文章系統(tǒng)的整理成pdf,分享給大家。
先來(lái)個(gè)整理的Redis大綱,可能還有不完整的地方,若是有不完整的,可以在留言區(qū)補(bǔ)充,我后續(xù)會(huì)加進(jìn)去。
單機(jī)
單機(jī)版的Redis就比較簡(jiǎn)單了,基本90%的程序員都是用過(guò)
優(yōu)點(diǎn)
單機(jī)版的Redis也有很多優(yōu)點(diǎn),比如實(shí)現(xiàn)實(shí)現(xiàn)簡(jiǎn)單、維護(hù)簡(jiǎn)單、部署簡(jiǎn)單、維護(hù)成本非常低,不需要其它額外的開(kāi)支。
缺點(diǎn)
但是,因?yàn)槭菃螜C(jī)版的Redis所以也存在很多的問(wèn)題,比如最明顯的單點(diǎn)故障問(wèn)題,一個(gè)Redis掛了,所有的請(qǐng)求就會(huì)直接打在了DB上。
并且一個(gè)Redis抗并發(fā)數(shù)量也是有限的,同時(shí)要兼顧讀寫兩種請(qǐng)求,只要訪問(wèn)量一上來(lái),Redis就受不了了,另一方面單機(jī)版的Redis數(shù)據(jù)量存儲(chǔ)也是有限的,數(shù)據(jù)量一大,再重啟Redis的時(shí)候,就會(huì)非常的慢,所以局限性也是比較大的。
由于,單機(jī)版的Redis在并發(fā)量比較大的時(shí)候,并且需要較高性能和可靠性的時(shí)候,單機(jī)版基本就不適合了,于是就出現(xiàn)了「主從模式」。
主從模式
原理
主從的原理還算是比較簡(jiǎn)單的,一主多從,「主數(shù)據(jù)庫(kù)(master)可以讀也可以寫(read/write),從數(shù)據(jù)庫(kù)僅讀(only read)」。
但是,主從模式一般實(shí)現(xiàn)「讀寫分離」,「主數(shù)據(jù)庫(kù)僅寫(only write)」,減輕主數(shù)據(jù)庫(kù)的壓力,下面一張圖搞懂主從模式的原理:
主從模式原理就是那么簡(jiǎn)單,那他執(zhí)行的過(guò)程(工作機(jī)制)又是怎么樣的呢?再來(lái)一張圖:
當(dāng)開(kāi)啟主從模式的時(shí)候,他的具體工作機(jī)制如下:
當(dāng)slave啟動(dòng)后會(huì)向master發(fā)送SYNC命令,master節(jié)點(diǎn)收到從數(shù)據(jù)庫(kù)的命令后通過(guò)bgsave保存快照(「RDB持久化」),并且期間的執(zhí)行的些命令會(huì)被緩存起來(lái)。
然后master會(huì)將保存的快照發(fā)送給slave,并且繼續(xù)緩存期間的寫命令。
slave收到主數(shù)據(jù)庫(kù)發(fā)送過(guò)來(lái)的快照就會(huì)加載到自己的數(shù)據(jù)庫(kù)中。
最后master講緩存的命令同步給slave,slave收到命令后執(zhí)行一遍,這樣master與slave數(shù)據(jù)就保持一致了。
優(yōu)點(diǎn)
之所以運(yùn)用主從,是因?yàn)橹鲝囊欢ǔ潭壬辖鉀Q了單機(jī)版并發(fā)量大,導(dǎo)致請(qǐng)求延遲或者redis宕機(jī)服務(wù)停止的問(wèn)題。
從數(shù)據(jù)庫(kù)分擔(dān)主數(shù)據(jù)庫(kù)的讀壓力,若是主數(shù)據(jù)庫(kù)是只寫模式,那么實(shí)現(xiàn)讀寫分離,主數(shù)據(jù)庫(kù)就沒(méi)有了讀壓力了。
另一方面解決了單機(jī)版單點(diǎn)故障的問(wèn)題,若是主數(shù)據(jù)庫(kù)掛了,那么從數(shù)據(jù)庫(kù)可以隨時(shí)頂上來(lái),綜上來(lái)說(shuō),主從模式一定程度上提高了系統(tǒng)的可用性和性能,是實(shí)現(xiàn)哨兵和集群的基礎(chǔ)。
主從同步以異步方式進(jìn)行同步,期間Redis仍然可以響應(yīng)客戶端提交的查詢和更新的請(qǐng)求。
缺點(diǎn)
主從模式好是好,他也有自己的缺點(diǎn),比如數(shù)據(jù)的一致性問(wèn)題,假如主數(shù)據(jù)庫(kù)寫操作完成,那么他的數(shù)據(jù)會(huì)被復(fù)制到從數(shù)據(jù)庫(kù),若是還沒(méi)有即使復(fù)制到從數(shù)據(jù)庫(kù),讀請(qǐng)求又來(lái)了,此時(shí)讀取的數(shù)據(jù)就不是最新的數(shù)據(jù)。
若是從主同步的過(guò)程網(wǎng)絡(luò)出故障了,導(dǎo)致主從同步失敗,也會(huì)出現(xiàn)問(wèn)題數(shù)據(jù)一致性的問(wèn)題。
主從模式不具備自動(dòng)容錯(cuò)和恢復(fù)的功能,一旦主數(shù)據(jù)庫(kù),從節(jié)點(diǎn)晉升為主數(shù)據(jù)庫(kù)的過(guò)程需要人為操作,維護(hù)的成本就會(huì)升高,并且主節(jié)點(diǎn)的寫能力、存儲(chǔ)能力都會(huì)受到限制。
實(shí)操搭建
安裝Redis
$ yum -y install gcc $ yum -y install gcc-c++ $ wget http://download.redis.io/releases/redis-5.0.4.tar.gz $ tar -zvxf redis-5.0.4.tar.gz $ cd redis-5.0.4 $ make這里我們將redis.conf文件復(fù)制兩份slave.conf和slave2.conf并修改配置
注意:不要用? bind 127.0.0.1,也不要這樣配置:bind 127.0.0.1 10.234.1.10?而要用?bind 10.234.1.10 127.0.0.1
# 服務(wù)器端口號(hào),主從分別修改為7001 7002 7003 port 7001 # 使得Redis可以跨網(wǎng)絡(luò)訪問(wèn) bind 0.0.0.0 # 配置reids的密碼 requirepass "111111" # 下面兩個(gè)配置只需要配置從節(jié)點(diǎn)(slave) # 配置主服務(wù)器地址、端口號(hào) replicaof 127.0.0.1 7001 # 主服務(wù)器密碼 masterauth "111111"分別啟動(dòng)這三個(gè)Redis服務(wù)
$ ./src/redis-server redis.conf $ ./src/redis-server slave.conf $ ./src/redis-server slave2.conf使用redis-cli工具連接redis服務(wù)查看主從節(jié)點(diǎn)是否搭建成功
$ ./src/redis-cli -h <主機(jī)名> -p <端口號(hào)> $ auth <password> $ info replication看到類似如下所示信息則主從搭建成功
############主節(jié)點(diǎn)(master)信息############# "# Replication role:master connected_slaves:2 slave0:ip=192.168.1.164,port=7002,state=online,offset=1015511,lag=0 slave1:ip=192.168.1.164,port=7003,state=online,offset=1015511,lag=0 master_replid:ffff866d17e11dcc9a9fd7bf3a487ad9e499fca9 master_replid2:1c8a6f05891dc72bbe4fefd9a54ff65eb46ce35d master_repl_offset:1015511 second_repl_offset:424773 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:99239 repl_backlog_histlen:916273 " ############從節(jié)點(diǎn)(slave)信息############# "# Replication role:slave master_host:192.168.1.164 master_port:7001 master_link_status:up master_last_io_seconds_ago:0 master_sync_in_progress:0 slave_repl_offset:560709 slave_priority:100 slave_read_only:1 connected_slaves:0 master_replid:ffff866d17e11dcc9a9fd7bf3a487ad9e499fca9 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:560709 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:549628 repl_backlog_histlen:11082?
哨兵模式
原理
哨兵模式是主從的升級(jí)版,因?yàn)橹鲝牡某霈F(xiàn)故障后,不會(huì)自動(dòng)恢復(fù),需要人為干預(yù),這就很蛋疼啊。
在主從的基礎(chǔ)上,實(shí)現(xiàn)哨兵模式就是為了監(jiān)控主從的運(yùn)行狀況,對(duì)主從的健壯進(jìn)行監(jiān)控,就好像哨兵一樣,只要有異常就發(fā)出警告,對(duì)異常狀況進(jìn)行處理。
?
所以,總的概括來(lái)說(shuō),哨兵模式有以下的優(yōu)點(diǎn)(功能點(diǎn)):
「監(jiān)控」:監(jiān)控master和slave是否正常運(yùn)行,以及哨兵之間也會(huì)相互監(jiān)控
「自動(dòng)故障恢復(fù)」:當(dāng)master出現(xiàn)故障的時(shí)候,會(huì)自動(dòng)選舉一個(gè)slave作為master頂上去。
哨兵模式的監(jiān)控配置信息,是通過(guò)配置從數(shù)據(jù)庫(kù)的sentinel monitor <master-name> <ip> <redis-port> <quorum>?來(lái)指定的,比如:
//?mymaster?表示給master數(shù)據(jù)庫(kù)定義了一個(gè)名字,后面的是master的ip和端口,1表示至少需要一個(gè)Sentinel進(jìn)程同意才能將master判斷為失效,如果不滿足這個(gè)條件,則自動(dòng)故障轉(zhuǎn)移(failover)不會(huì)執(zhí)行
sentinel?monitor?mymaster?127.0.0.1?6379?1
節(jié)點(diǎn)通信
當(dāng)哨兵啟動(dòng)后,會(huì)與master建立一條連接,用于訂閱master的_sentinel_:hello頻道。該頻道用于獲取監(jiān)控該master的其它哨兵的信息。哨兵定期向_sentinel_:hello頻道發(fā)送自己的信息,以便其它的哨兵能夠訂閱獲取自己的信息,發(fā)送的內(nèi)容包含「哨兵的ip和端口、運(yùn)行id、配置版本、master名字、master的ip端口還有master的配置版本」等信息。
哨兵還會(huì)與mastter建立一條獲取master信息的連接,定時(shí)向master發(fā)送INFO命令。「當(dāng)哨兵與master建立連接后,定期會(huì)向(10秒一次)master和slave發(fā)送INFO命令,若是master被標(biāo)記為主觀下線,頻率就會(huì)變?yōu)?秒一次?!?/strong>
以及,「定期的向master、slave和其它哨兵發(fā)送PING命令(每秒一次),以便檢測(cè)對(duì)象是否存活」,若是對(duì)方接收到了PING命令,無(wú)故障情況下,會(huì)回復(fù)PONG命令。
所以,哨兵通過(guò)建立這兩條連接、通過(guò)定期發(fā)送INFO、PING命令來(lái)實(shí)現(xiàn)哨兵與哨兵、哨兵與master之間的通信。
這里涉及到一些概念需要理解,INFO、PING、PONG等命令,后面還會(huì)有MEET、FAIL命令,以及主觀下線,當(dāng)然還會(huì)有客觀下線,這里主要說(shuō)一下這幾個(gè)概念的理解:
INFO:該命令可以獲取主從數(shù)據(jù)庫(kù)的最新信息,可以實(shí)現(xiàn)新結(jié)點(diǎn)的發(fā)現(xiàn)。
PING:該命令被使用最頻繁,該命令封裝了自身節(jié)點(diǎn)和其它節(jié)點(diǎn)的狀態(tài)數(shù)據(jù)。
PONG:當(dāng)節(jié)點(diǎn)收到MEET和PING,會(huì)回復(fù)PONG命令,也把自己的狀態(tài)發(fā)送給對(duì)方。
MEET:該命令在新結(jié)點(diǎn)加入集群的時(shí)候,會(huì)向老節(jié)點(diǎn)發(fā)送該命令,表示自己是個(gè)新人。
FAIL:當(dāng)節(jié)點(diǎn)下線,會(huì)向集群中廣播該消息。
上線和下線
當(dāng)哨兵與master相通之后就會(huì)定期一直保持聯(lián)系,若是某一時(shí)刻哨兵發(fā)送的PING在指定時(shí)間內(nèi)沒(méi)有收到回復(fù)(sentinel down-after-milliseconds master-name milliseconds?配置),那么發(fā)送PING命令的哨兵就會(huì)認(rèn)為該master「主觀下線」(Subjectively Down)。
因?yàn)橛锌赡苁巧诒c該master之間的網(wǎng)絡(luò)問(wèn)題造成的,而不是master本身的原因,所以哨兵同時(shí)會(huì)詢問(wèn)其它的哨兵是否也認(rèn)為該master下線,若是認(rèn)為該節(jié)點(diǎn)下線的哨兵達(dá)到一定的數(shù)量(「前面的quorum字段配置」),就會(huì)認(rèn)為該節(jié)點(diǎn)「客觀下線」(Objectively Down)。
若是沒(méi)有足夠數(shù)量的sentinel同意該master下線,則該master客觀下線的標(biāo)識(shí)會(huì)被移除;若是master重新向哨兵的PING命令回復(fù)了客觀下線的標(biāo)識(shí)也會(huì)被移除。
選舉算法
當(dāng)master被認(rèn)為客觀下線后,又是怎么進(jìn)行故障恢復(fù)的呢?原來(lái)哨兵中首先選舉出一個(gè)老大哨兵來(lái)進(jìn)行故障恢復(fù),選舉老大哨兵的算法叫做「Raft算法」:
發(fā)現(xiàn)master下線的哨兵(sentinelA)會(huì)向其它的哨兵發(fā)送命令進(jìn)行拉票,要求選擇自己為哨兵大佬。
若是目標(biāo)哨兵沒(méi)有選擇其它的哨兵,就會(huì)選擇該哨兵(sentinelA)為大佬。
若是選擇sentinelA的哨兵超過(guò)半數(shù)(半數(shù)原則),該大佬非sentinelA莫屬。
如果有多個(gè)哨兵同時(shí)競(jìng)選,并且可能存在票數(shù)一致的情況,就會(huì)等待下次的一個(gè)隨機(jī)時(shí)間再次發(fā)起競(jìng)選請(qǐng)求,進(jìn)行新的一輪投票,直到大佬被選出來(lái)。
選出大佬哨兵后,大佬哨兵就會(huì)對(duì)故障進(jìn)行自動(dòng)回復(fù),從slave中選出一名slave作為主數(shù)據(jù)庫(kù),選舉的規(guī)則如下所示:
所有的slave中slave-priority優(yōu)先級(jí)最高的會(huì)被選中。
若是優(yōu)先級(jí)相同,會(huì)選擇偏移量最大的,因?yàn)槠屏坑涗浿鴶?shù)據(jù)的復(fù)制的增量,越大表示數(shù)據(jù)越完整。
若是以上兩者都相同,選擇ID最小的。
通過(guò)以上的層層篩選最終實(shí)現(xiàn)故障恢復(fù),當(dāng)選的slave晉升為master,其它的slave會(huì)向新的master復(fù)制數(shù)據(jù),若是down掉的master重新上線,會(huì)被當(dāng)作slave角色運(yùn)行。
優(yōu)點(diǎn)
哨兵模式是主從模式的升級(jí)版,所以在系統(tǒng)層面提高了系統(tǒng)的可用性和性能、穩(wěn)定性。當(dāng)master宕機(jī)的時(shí)候,能夠自動(dòng)進(jìn)行故障恢復(fù),需不要人為的干預(yù)。
哨兵與哨兵之間、哨兵與master之間能夠進(jìn)行及時(shí)的監(jiān)控,心跳檢測(cè),及時(shí)發(fā)現(xiàn)系統(tǒng)的問(wèn)題,這都是彌補(bǔ)了主從的缺點(diǎn)。
缺點(diǎn)
哨兵一主多從的模式同樣也會(huì)遇到寫的瓶頸,已經(jīng)存儲(chǔ)瓶頸,若是master宕機(jī)了,故障恢復(fù)的時(shí)間比較長(zhǎng),寫的業(yè)務(wù)就會(huì)受到影響。
增加了哨兵也增加了系統(tǒng)的復(fù)雜度,需要同時(shí)維護(hù)哨兵模式。
實(shí)操搭建
新開(kāi)一臺(tái)服務(wù)器,并按上面的步驟下載安裝Redis。
將sentinel.conf文件復(fù)制兩份為sentinel2.conf、sentinel3.conf,并分別修改配置
# 三個(gè)配置文件分別配置不同的端口號(hào) port <端口號(hào)> # 設(shè)置為后臺(tái)啟動(dòng) daemonize yes # 主節(jié)點(diǎn)的名稱(可以自定義,與后面的配置保持一致即可) # 主機(jī)地址 # 端口號(hào) # 數(shù)量(2代表只有兩個(gè)或兩個(gè)以上的哨兵認(rèn)為主服務(wù)器不可用的時(shí)候,才會(huì)進(jìn)行failover操作) sentinel monitor mymaster 127.0.0.1 6379 2 # 多長(zhǎng)時(shí)間沒(méi)有響應(yīng)認(rèn)為主觀下線(SDOWN) sentinel down-after-milliseconds mymaster 60000 # 表示如果15秒后,mysater仍沒(méi)活過(guò)來(lái),則啟動(dòng)failover,從剩下從節(jié)點(diǎn)序曲新的主節(jié)點(diǎn) sentinel failover-timeout mymaster 15000 # 指定了在執(zhí)行故障轉(zhuǎn)移時(shí), 最多可以有多少個(gè)從服務(wù)器同時(shí)對(duì)新的主服務(wù)器進(jìn)行同步, 這個(gè)數(shù)字越小, 完成故障轉(zhuǎn)移所需的時(shí)間就越長(zhǎng) sentinel parallel-syncs mymaster 1啟動(dòng)三個(gè)sentinel
$ ./src/server-sentinel sentinel.conf $ ./src/server-sentinel sentinel2.conf $ ./src/server-sentinel sentinel3.conf然后手動(dòng)關(guān)閉主節(jié)點(diǎn)的redis服務(wù),并查看兩個(gè)slave信息是否有一個(gè)變成了master。
程序中使用
SpringBoot連接Redis主從集群配置
spring:redis:sentinel:master: mymasternodes: 192.168.1.167:26379,192.168.1.167:26380,192.168.1.167:26381host: 192.168.1.164port: 7003database: 0password: <password>?
Redis集群(Redis-Cluster)
Cluster模式
最后,Cluster是真正的集群模式了,哨兵解決和主從不能自動(dòng)故障恢復(fù)的問(wèn)題,但是同時(shí)也存在難以擴(kuò)容以及單機(jī)存儲(chǔ)、讀寫能力受限的問(wèn)題,并且集群之前都是一臺(tái)redis都是全量的數(shù)據(jù),這樣所有的redis都冗余一份,就會(huì)大大消耗內(nèi)存空間。
集群模式實(shí)現(xiàn)了Redis數(shù)據(jù)的分布式存儲(chǔ),實(shí)現(xiàn)數(shù)據(jù)的分片,每個(gè)redis節(jié)點(diǎn)存儲(chǔ)不同的內(nèi)容,并且解決了在線的節(jié)點(diǎn)收縮(下線)和擴(kuò)容(上線)問(wèn)題。
集群模式真正意義上實(shí)現(xiàn)了系統(tǒng)的高可用和高性能,但是集群同時(shí)進(jìn)一步使系統(tǒng)變得越來(lái)越復(fù)雜,接下來(lái)我們來(lái)詳細(xì)的了解集群的運(yùn)作原理。
數(shù)據(jù)分區(qū)原理
集群的原理圖還是很好理解的,在Redis集群中采用的是虛擬槽分區(qū)算法,會(huì)把redis集群分成16384 個(gè)槽(0 -16383)。
比如:下圖所示三個(gè)master,會(huì)把0 -16383范圍的槽可能分成三部分(0-5000)、(5001-11000)、(11001-16383)分別數(shù)據(jù)三個(gè)緩存節(jié)點(diǎn)的槽范圍。
當(dāng)客戶端請(qǐng)求過(guò)來(lái),會(huì)首先通過(guò)對(duì)key進(jìn)行CRC16 校驗(yàn)并對(duì) 16384 取模(CRC16(key)%16383)計(jì)算出key所在的槽,然后再到對(duì)應(yīng)的槽上進(jìn)行取數(shù)據(jù)或者存數(shù)據(jù),這樣就實(shí)現(xiàn)了數(shù)據(jù)的訪問(wèn)更新。
之所以進(jìn)行分槽存儲(chǔ),是將一整堆的數(shù)據(jù)進(jìn)行分片,防止單臺(tái)的redis數(shù)據(jù)量過(guò)大,影響性能的問(wèn)題。
節(jié)點(diǎn)通信
節(jié)點(diǎn)之間實(shí)現(xiàn)了將數(shù)據(jù)進(jìn)行分片存儲(chǔ),那么節(jié)點(diǎn)之間又是怎么通信的呢?這個(gè)和前面哨兵模式講的命令基本一樣。
首先新上線的節(jié)點(diǎn),會(huì)通過(guò) Gossip 協(xié)議向老成員發(fā)送Meet消息,表示自己是新加入的成員。
老成員收到Meet消息后,在沒(méi)有故障的情況下會(huì)恢復(fù)PONG消息,表示歡迎新結(jié)點(diǎn)的加入,除了第一次發(fā)送Meet消息后,之后都會(huì)發(fā)送定期PING消息,實(shí)現(xiàn)節(jié)點(diǎn)之間的通信。
通信的過(guò)程中會(huì)為每一個(gè)通信的節(jié)點(diǎn)開(kāi)通一條tcp通道,之后就是定時(shí)任務(wù),不斷的向其它節(jié)點(diǎn)發(fā)送PING消息,這樣做的目的就是為了了解節(jié)點(diǎn)之間的元數(shù)據(jù)存儲(chǔ)情況,以及健康狀況,以便及時(shí)發(fā)現(xiàn)問(wèn)題。
數(shù)據(jù)請(qǐng)求
上面說(shuō)到了槽信息,在Redis的底層維護(hù)了unsigned char myslots[CLUSTER_SLOTS/8]?一個(gè)數(shù)組存放每個(gè)節(jié)點(diǎn)的槽信息。因?yàn)樗且粋€(gè)二進(jìn)制數(shù)組,只有存儲(chǔ)0和1值,如下圖所示:
這樣數(shù)組只表示自己是否存儲(chǔ)對(duì)應(yīng)的槽數(shù)據(jù),若是1表示存在該數(shù)據(jù),0表示不存在該數(shù)據(jù),這樣查詢的效率就會(huì)非常的高,類似于布隆過(guò)濾器,二進(jìn)制存儲(chǔ)。
比如:集群節(jié)點(diǎn)1負(fù)責(zé)存儲(chǔ)0-5000的槽數(shù)據(jù),但是此時(shí)只有0、1、2存儲(chǔ)有數(shù)據(jù),其它的槽還沒(méi)有存數(shù)據(jù),所以0、1、2對(duì)應(yīng)的值為1。
并且,每個(gè)redis底層還維護(hù)了一個(gè)clusterNode數(shù)組,大小也是16384,用于儲(chǔ)存負(fù)責(zé)對(duì)應(yīng)槽的節(jié)點(diǎn)的ip、端口等信息,這樣每一個(gè)節(jié)點(diǎn)就維護(hù)了其它節(jié)點(diǎn)的元數(shù)據(jù)信息,便于及時(shí)的找到對(duì)應(yīng)的節(jié)點(diǎn)。
當(dāng)新結(jié)點(diǎn)加入或者節(jié)點(diǎn)收縮,通過(guò)PING命令通信,及時(shí)的更新自己clusterNode數(shù)組中的元數(shù)據(jù)信息,這樣有請(qǐng)求過(guò)來(lái)也就能及時(shí)的找到對(duì)應(yīng)的節(jié)點(diǎn)。
有兩種其它的情況就是,若是請(qǐng)求過(guò)來(lái)發(fā)現(xiàn),數(shù)據(jù)發(fā)生了遷移,比如新節(jié)點(diǎn)加入,會(huì)使舊的緩存節(jié)點(diǎn)數(shù)據(jù)遷移到新結(jié)點(diǎn)。
請(qǐng)求過(guò)來(lái)發(fā)現(xiàn)舊節(jié)點(diǎn)已經(jīng)發(fā)生了數(shù)據(jù)遷移并且數(shù)據(jù)被遷移到新結(jié)點(diǎn),由于每個(gè)節(jié)點(diǎn)都有clusterNode信息,通過(guò)該信息的ip和端口。此時(shí)舊節(jié)點(diǎn)就會(huì)向客戶端發(fā)一個(gè)MOVED 的重定向請(qǐng)求,表示數(shù)據(jù)已經(jīng)遷移到新結(jié)點(diǎn)上,你要訪問(wèn)這個(gè)新結(jié)點(diǎn)的ip和端口就能拿到數(shù)據(jù),這樣就能重新獲取到數(shù)據(jù)。
倘若正在發(fā)生數(shù)據(jù)遷移呢?舊節(jié)點(diǎn)就會(huì)向客戶端發(fā)送一個(gè)ASK 重定向請(qǐng)求,并返回給客戶端遷移的目標(biāo)節(jié)點(diǎn)的ip和端口,這樣也能獲取到數(shù)據(jù)。
擴(kuò)容和收縮
擴(kuò)容和收縮也就是節(jié)點(diǎn)的上線和下線,可能節(jié)點(diǎn)發(fā)生故障了,故障自動(dòng)恢復(fù)的過(guò)程(節(jié)點(diǎn)收縮)。
節(jié)點(diǎn)的收縮和擴(kuò)容時(shí),會(huì)重新計(jì)算每一個(gè)節(jié)點(diǎn)負(fù)責(zé)的槽范圍,并根據(jù)虛擬槽算法,將對(duì)應(yīng)的數(shù)據(jù)更新到對(duì)應(yīng)的節(jié)點(diǎn)。
還有前面的講的新加入的節(jié)點(diǎn)會(huì)首先發(fā)送Meet消息,詳細(xì)可以查看前面講的內(nèi)容,基本一樣的模式。
以及發(fā)生故障后,哨兵老大節(jié)點(diǎn)的選舉,master節(jié)點(diǎn)的重新選舉,slave怎樣晉升為master節(jié)點(diǎn),可以查看前面哨兵模式選舉過(guò)程。
優(yōu)點(diǎn)
集群模式是一個(gè)無(wú)中心的架構(gòu)模式,將數(shù)據(jù)進(jìn)行分片,分布到對(duì)應(yīng)的槽中,每個(gè)節(jié)點(diǎn)存儲(chǔ)不同的數(shù)據(jù)內(nèi)容,通過(guò)路由能夠找到對(duì)應(yīng)的節(jié)點(diǎn)負(fù)責(zé)存儲(chǔ)的槽,能夠?qū)崿F(xiàn)高效率的查詢。
并且集群模式增加了橫向和縱向的擴(kuò)展能力,實(shí)現(xiàn)節(jié)點(diǎn)加入和收縮,集群模式是哨兵的升級(jí)版,哨兵的優(yōu)點(diǎn)集群都有。
缺點(diǎn)
緩存的最大問(wèn)題就是帶來(lái)數(shù)據(jù)一致性問(wèn)題,在平衡數(shù)據(jù)一致性的問(wèn)題時(shí),兼顧性能與業(yè)務(wù)要求,大多數(shù)都是以最終一致性的方案進(jìn)行解決,而不是強(qiáng)一致性。
并且集群模式帶來(lái)節(jié)點(diǎn)數(shù)量的劇增,一個(gè)集群模式最少要6臺(tái)機(jī),因?yàn)橐獫M足半數(shù)原則的選舉方式,所以也帶來(lái)了架構(gòu)的復(fù)雜性。
slave只充當(dāng)冷備,并不能緩解master的讀的壓力。
搭建步驟
Redis5.0之后的版本放棄了 Ruby 的集群方式,改為使用 C 語(yǔ)言編寫的redis-cli的方式,使集群的構(gòu)建方式復(fù)雜度大大降低。
下載安裝Redis(見(jiàn)主從復(fù)制模式的搭建步驟)。
創(chuàng)建6個(gè)Redis的配置文件,如下所示:
/usr/local/redis-5.0.4/redis-cluster-conf/7001/redis.conf /usr/local/redis-5.0.4/redis-cluster-conf/7002/redis.conf /usr/local/redis-5.0.4/redis-cluster-conf/7003/redis.conf /usr/local/redis-5.0.4/redis-cluster-conf/7004/redis.conf /usr/local/redis-5.0.4/redis-cluster-conf/7005/redis.conf /usr/local/redis-5.0.4/redis-cluster-conf/7006/redis.conf配置文件內(nèi)容:
port 7001 # 端口,每個(gè)配置文件不同7001-7006 cluster-enabled yes # 啟用集群模式 cluster-config-file nodes.conf #節(jié)點(diǎn)配置文件 cluster-node-timeout 5000 # 超時(shí)時(shí)間 appendonly yes # 打開(kāi)aof持久化 daemonize yes # 后臺(tái)運(yùn)行 protected-mode no # 非保護(hù)模式 pidfile /var/run/redis_7001.pid # 根據(jù)端口修改啟動(dòng)6個(gè)Redis節(jié)點(diǎn)。
./src/redis-server redis-cluster-conf/7001/redis.conf ./src/redis-server redis-cluster-conf/7002/redis.conf ./src/redis-server redis-cluster-conf/7003/redis.conf ./src/redis-server redis-cluster-conf/7004/redis.conf ./src/redis-server redis-cluster-conf/7005/redis.conf ./src/redis-server redis-cluster-conf/7006/redis.conf此時(shí)啟動(dòng)的6個(gè)Redis服務(wù)是相互獨(dú)立運(yùn)行的,通過(guò)以下命令配置集群。
./src/redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006配置完看到如下圖所示信息表示集群搭建成功:
從圖中可以看到啟動(dòng)了3個(gè)master節(jié)點(diǎn),3個(gè)slave節(jié)點(diǎn),16384個(gè)槽點(diǎn)平均分配到了3個(gè)master節(jié)點(diǎn)上。圖中很長(zhǎng)的一串字母數(shù)字的組合(07000b3a90......)為節(jié)點(diǎn)的ID。后面對(duì)節(jié)點(diǎn)的操作中會(huì)用到。
集群重新分片
如果對(duì)默認(rèn)的平均分配不滿意,我們可以對(duì)集群進(jìn)行重新分片。執(zhí)行如下命令,只需要指定集群中的其中一個(gè)節(jié)點(diǎn)地址即可,它會(huì)自動(dòng)找到集群中的其他節(jié)點(diǎn)。(如果設(shè)置了密碼則需要加上 -a <password>,沒(méi)有密碼則不需要,后面的命令我會(huì)省略這個(gè),設(shè)置了密碼的自己加上就好)。
./src/redis-cli -a <password> --cluster reshard 127.0.0.1:7001輸入你想重新分配的哈希槽數(shù)量
How many slots do you want to move (from 1 to 16384)?輸入你想接收這些哈希槽的節(jié)點(diǎn)ID
What is the receiving node ID?輸入想從哪個(gè)節(jié)點(diǎn)移動(dòng)槽點(diǎn),選擇all表示所有其他節(jié)點(diǎn),也可以依次輸入節(jié)點(diǎn)ID,以done結(jié)束。
Please enter all the source node IDs.Type 'all' to use all the nodes as source nodes for the hash slots.Type 'done' once you entered all the source nodes IDs. Source node #1:輸入yes執(zhí)行重新分片
Do you want to proceed with the proposed reshard plan (yes/no)?自動(dòng)故障轉(zhuǎn)移
當(dāng)運(yùn)行中的master節(jié)點(diǎn)掛掉了,集群會(huì)在該master節(jié)點(diǎn)的slave節(jié)點(diǎn)中選出一個(gè)作為新的master節(jié)點(diǎn)。這里不做演示。
手動(dòng)故障轉(zhuǎn)移
有的時(shí)候在主節(jié)點(diǎn)沒(méi)有任何問(wèn)題的情況下強(qiáng)制手動(dòng)故障轉(zhuǎn)移也是很有必要的,比如想要升級(jí)主節(jié)點(diǎn)的Redis進(jìn)程,我們可以通過(guò)故障轉(zhuǎn)移將其轉(zhuǎn)為slave再進(jìn)行升級(jí)操作來(lái)避免對(duì)集群的可用性造成很大的影響。
Redis集群使用 cluster failover 命令來(lái)進(jìn)行故障轉(zhuǎn)移,不過(guò)要在被轉(zhuǎn)移的主節(jié)點(diǎn)的從節(jié)點(diǎn)上執(zhí)行該命令(使用redis-cli連接slave節(jié)點(diǎn)并執(zhí)行 cluster failover命令進(jìn)行轉(zhuǎn)移)。
添加一個(gè)主節(jié)點(diǎn)
按之前的方式再?gòu)?fù)制一份配置文件,并修改配置
/usr/local/redis-5.0.4/redis-cluster-conf/7007/redis.conf然后啟動(dòng)該Redis服務(wù),執(zhí)行以下命令將該節(jié)點(diǎn)添加到集群中去
./src/redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7001第一個(gè)參數(shù)為新增加的節(jié)點(diǎn)的IP和端口,第二個(gè)參數(shù)為任意一個(gè)已經(jīng)存在的節(jié)點(diǎn)的IP和端口。
此時(shí)該新節(jié)點(diǎn)已經(jīng)成為集群的一份子
127.0.0.1:7007> cluster nodes 90c78386c39c8258435b5b61f49a623b358ec8a6 127.0.0.1:7007@17007 myself,master - 0 1553239975877 10 connected c4180e02149e2d853a80683433773ef4bceffc78 127.0.0.1:7001@17001 master - 0 1553240017595 11 connected 0-5544 10923-11004 b6584514edbf57331a65f367304f33ad1bd0903e 192.168.1.164:7005@17005 slave 3bdbc4ac0d5902dcf8a5ebbfb88db8fad224c066 0 1553240016992 2 connected 07000b3a905df0ab0c86361adcb2774a487ce650 192.168.1.164:7004@17004 slave c4180e02149e2d853a80683433773ef4bceffc78 0 1553240016000 11 connected 28fa7bbf6b2a46991c7a5fe8eec53db1a5f1e9f6 192.168.1.164:7003@17003 master - 0 1553240017595 3 connected 11005-16383 3bdbc4ac0d5902dcf8a5ebbfb88db8fad224c066 192.168.1.164:7002@17002 master - 0 1553240017000 2 connected 5545-10922 fd9cba359d94ba6c9beecc91fbd491f9cf7a39ca 192.168.1.164:7006@17006 slave 28fa7bbf6b2a46991c7a5fe8eec53db1a5f1e9f6 0 1553240016000 3 connected但是該節(jié)點(diǎn)沒(méi)有包含任何的哈希槽,所以沒(méi)有數(shù)據(jù)會(huì)存到該主節(jié)點(diǎn)。我們可以通過(guò)上面的集群重新分片給該節(jié)點(diǎn)分配哈希槽,那么該節(jié)點(diǎn)就成為了一個(gè)真正的主節(jié)點(diǎn)了。
添加一個(gè)從節(jié)點(diǎn)
跟添加主節(jié)點(diǎn)一樣添加一個(gè)節(jié)點(diǎn)7008,然后連接上該節(jié)點(diǎn)并執(zhí)行如下命令
cluster replicate <nodeId>這樣就可以指定該節(jié)點(diǎn)成為哪個(gè)節(jié)點(diǎn)的從節(jié)點(diǎn)。
節(jié)點(diǎn)的移除
可以使用如下命令來(lái)移除節(jié)點(diǎn)
./src/redis-cli --cluster del-node 127.0.0.1:7001 <nodeId>第一個(gè)參數(shù)是任意一個(gè)節(jié)點(diǎn)的地址,第二個(gè)參數(shù)是你想要移除的節(jié)點(diǎn)ID。如果是移除主節(jié)點(diǎn),需要確保這個(gè)節(jié)點(diǎn)是空的,如果不是空的則需要將這個(gè)節(jié)點(diǎn)上的數(shù)據(jù)重新分配到其他節(jié)點(diǎn)上。
程序中使用
SpringBoot中連接Redis集群配置
spring:redis:cluster:nodes: 192.168.1.164:7001,192.168.1.164:7002,192.168.1.164:7003,192.168.1.164:7004,192.168.1.164:7005,192.168.1.164:7006database: 0password: <password>Redis常見(jiàn)數(shù)據(jù)丟失情況分析及解決
情況分析
(1)異步復(fù)制導(dǎo)致的數(shù)據(jù)丟失
因?yàn)閙aster->slave的數(shù)據(jù)同步是異步的,所以可能存在部分?jǐn)?shù)據(jù)還沒(méi)有同步到slave,master就宕機(jī)了,此時(shí)這部分?jǐn)?shù)據(jù)就丟失了。
(2)腦裂導(dǎo)致的數(shù)據(jù)丟失
當(dāng)master所在的機(jī)器突然脫離的正常的網(wǎng)絡(luò),與其他slave、sentinel失去了連接,但是master還在運(yùn)行著。此時(shí)sentinel就會(huì)認(rèn)為master宕機(jī)了,會(huì)開(kāi)始選舉把slave提升為新的master,這個(gè)時(shí)候集群中就會(huì)出現(xiàn)兩個(gè)master,也就是所謂的腦裂。
此時(shí)雖然產(chǎn)生了新的master節(jié)點(diǎn),但是客戶端可能還沒(méi)來(lái)得及切換到新的master,會(huì)繼續(xù)向舊的master寫入數(shù)據(jù)。
當(dāng)網(wǎng)絡(luò)恢復(fù)正常時(shí),舊的master會(huì)變成新的master的從節(jié)點(diǎn),自己的數(shù)據(jù)會(huì)清空,重新從新的master上復(fù)制數(shù)據(jù)。
解決方案
Redis提供了這兩個(gè)配置用來(lái)降低數(shù)據(jù)丟失的可能性
min-slaves-to-write 1 min-slaves-max-lag 10上面兩行配置的意思是,要求至少有1個(gè)slave,數(shù)據(jù)復(fù)制和同步的延遲不能超過(guò)10秒,如果不符合這個(gè)條件,那么master將不會(huì)接收任何請(qǐng)求。
(1)減少異步復(fù)制的數(shù)據(jù)丟失
有了min-slaves-max-lag這個(gè)配置,就可以確保,一旦slave復(fù)制數(shù)據(jù)和ack延時(shí)太長(zhǎng),就認(rèn)為master宕機(jī)后損失的數(shù)據(jù)太多了,那么就拒絕寫請(qǐng)求,這樣可以把master宕機(jī)時(shí)由于部分?jǐn)?shù)據(jù)未同步到slave導(dǎo)致的數(shù)據(jù)丟失降低到可控范圍內(nèi)。
(2)減少腦裂的數(shù)據(jù)丟失
如果一個(gè)master出現(xiàn)了腦裂,跟其他slave丟了連接,那么上面兩個(gè)配置可以確保,如果不能繼續(xù)給指定數(shù)量的slave發(fā)送數(shù)據(jù),而且slave超過(guò)10秒沒(méi)有給自己ack消息,那么就直接拒絕客戶端的寫請(qǐng)求
這樣腦裂后的舊master就不會(huì)接受client的新數(shù)據(jù),也就避免了數(shù)據(jù)丟失。
Redis并不能保證數(shù)據(jù)的強(qiáng)一致性,看官方文檔的說(shuō)明
?
參考文檔:
http://www.redis.cn/topics/cluster-tutorial.html
總結(jié)
以上是生活随笔為你收集整理的摸透 Redis 主从复制、哨兵、Cluster 三种模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: go应用程序写kafka阻塞(大坑)
- 下一篇: Redis设计与实现之SDS和链表