普通主從架構(gòu)(讀寫分離)
問題
持久化解決了單機(jī)redis的數(shù)據(jù)保存問題,但是redis還是存在以下兩個(gè)問題:
- 假如某天這臺(tái)redis服務(wù)器掛了,redis服務(wù)將徹底喪失
- redis的讀和寫都集中到一臺(tái)機(jī)上,如果請(qǐng)求量比較大時(shí),將可能被擊潰
解決
為了解決上述兩個(gè)問題,redis提供了主從架構(gòu),在主從架構(gòu)中,主服務(wù)器負(fù)責(zé)寫服務(wù),多臺(tái)從服務(wù)器負(fù)責(zé)讀服務(wù),緩解了單個(gè)redis服務(wù)器的壓力;主服務(wù)器將所有數(shù)據(jù)源源不斷的同步到從服務(wù)器上,一旦主服務(wù)器掛了,還有從服務(wù)器可以提供服務(wù),redis服務(wù)將不會(huì)間斷。
特點(diǎn)
- 一臺(tái)主服務(wù)器可以連接多臺(tái)從服務(wù)器
- 從服務(wù)器也可以連接其他redis服務(wù)器,作為其他redis服務(wù)器的主服務(wù)器,從而形成一條鏈
- 主從同步是異步的,從服務(wù)器不會(huì)阻塞,但是在數(shù)據(jù)寫到從服務(wù)器內(nèi)存的這段期間,從服務(wù)器對(duì)外提供的還是舊的數(shù)據(jù)
類型
-
全量同步指主服務(wù)器每次與從服務(wù)器同步都是同步全部數(shù)據(jù)。主服務(wù)器持久化數(shù)據(jù)為一個(gè)rdb文件,在此期間用緩存區(qū)把所有對(duì)主服務(wù)器的寫操作命令存儲(chǔ)起來了,然后再rdb傳給從服務(wù)器,再把儲(chǔ)存起來的命令也傳過去;從服務(wù)器從接收到的rdb文件加載數(shù)據(jù),然后再加載傳過來的命令。 -
部分同步指主服務(wù)器每次與從服務(wù)器同步都是只同步增量數(shù)據(jù)。
原理

- 從服務(wù)器收到客戶端的saveof命令,檢查是否存儲(chǔ)了主服務(wù)器的運(yùn)行id和復(fù)制偏移量。
- 如果沒綁存儲(chǔ),從服務(wù)器發(fā)送 psync 1命令和主服務(wù)器進(jìn)行一次全量復(fù)制,并且保存主服務(wù)器發(fā)過來的運(yùn)行id 和 復(fù)制偏移量
- 如果有存儲(chǔ)了,從服務(wù)器發(fā)送運(yùn)行id和復(fù)制偏移量,主服務(wù)器比較運(yùn)行id是否一致,復(fù)制偏移量是否正常,如果不一致或者不正常,進(jìn)行全量同步
- 如果運(yùn)行id和復(fù)制偏移量正常,那么二者進(jìn)行增量同步,同步根據(jù)傳過來復(fù)制偏移量,到復(fù)制緩存區(qū)找到對(duì)應(yīng)的字節(jié),并且把該字節(jié)對(duì)應(yīng)的之后的命令都同步過去
操作
-
slaveof master-ip master-port在從服務(wù)器的redis.conf中配置主服務(wù)器的host,port -
slave-read-only yes從服務(wù)器默認(rèn)只讀,這里可改成no為可寫(可選) -
auth從服務(wù)器配置主服務(wù)器的密碼(可選) -
requirepass主服務(wù)器配置密碼(可選)
從服務(wù)器配置好slaveof master-ip master-port 后重啟,就可以與主服務(wù)器進(jìn)行同步了。
# 主服務(wù)器信息
172.17.0.3:6379> info replication
role:master
connected_slaves:2
# 兩個(gè)從服務(wù)器復(fù)制偏移量為4908
slave0:ip=172.17.0.4,port=6379,state=online,offset=4908,lag=1
slave1:ip=172.17.0.5,port=6379,state=online,offset=4908,lag=1
# 主服務(wù)器的復(fù)制偏移量也為4908
master_repl_offset:4908
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:4907
哨兵模式
作用
- 不斷的檢查主從架構(gòu)中的redis服務(wù)中正常運(yùn)行。
- 如果出現(xiàn)問題會(huì)發(fā)信息提示你。
- 如果主服務(wù)器掛了的話,會(huì)從從服務(wù)器中重新選舉一臺(tái)作為主服務(wù)器。
原理
哨兵系統(tǒng)的分類
哨兵系統(tǒng)可以分為單哨兵和多哨兵;單哨兵是指只有一個(gè)哨兵進(jìn)程,多哨兵是指有多個(gè)哨兵進(jìn)程;單哨兵掛了的話,哨兵系統(tǒng)也就掛了,多哨兵只有一個(gè)哨兵掛了不影響哨兵系統(tǒng)繼續(xù)提供服務(wù)。大概原理
哨兵也是一個(gè)Linux的進(jìn)程;各個(gè)哨兵分布在不同的Linux服務(wù)器或者同一個(gè)Linux服務(wù)器上(一個(gè)風(fēng)險(xiǎn)比較大),它們不停的監(jiān)控redis的主服務(wù)器和從服務(wù)器與其它的哨兵進(jìn)程,一旦察覺redis主服務(wù)掛了,就會(huì)從從服務(wù)器中選出一個(gè)作為新的主服務(wù)器提供服務(wù)。哨兵怎么知道監(jiān)控其它哨兵和redis服務(wù)?
哨兵每秒鐘會(huì)向其它哨兵或者redis服務(wù)器發(fā)送ping命令,根據(jù)是否有返回來判斷服務(wù)是否已經(jīng)下線。我們只是在哨兵的配置文件里配置了主服務(wù)器信息,但是它怎么知道從服務(wù)器信息?
哨兵每十秒鐘會(huì)向redis主服務(wù)器或者從服務(wù)器執(zhí)行info replication的命令,來確認(rèn)它們的主從關(guān)系。我們沒有配置其他哨兵的地址,哨兵怎么知道其他哨兵地址?
哨兵每隔兩秒就會(huì)向redis主節(jié)點(diǎn)的sentinel:hello頻道發(fā)布哨兵對(duì)于主節(jié)點(diǎn)的判斷以及當(dāng)前哨兵的信息,其它哨兵也會(huì)也會(huì)如此,并且從中獲取所有的哨兵信息。確認(rèn)一臺(tái)redis服務(wù)器下線經(jīng)歷了什么流程?
哨兵不斷的PING redis服務(wù)器,當(dāng)發(fā)現(xiàn)服務(wù)器超過配置的down-after-milliseconds的時(shí)間都沒有響應(yīng),就會(huì)認(rèn)為這臺(tái)主觀下線;這時(shí)候哨兵會(huì)向其他哨兵發(fā)送is-master-down-by-addr命令詢問是否可以標(biāo)記為客觀下線,當(dāng)認(rèn)為這臺(tái)redis服務(wù)器主觀下線的哨兵超過我們配置的quorum(一般設(shè)為哨兵數(shù)量的一半加1)的值的時(shí)候,我們就可以認(rèn)為這臺(tái)redis服務(wù)器客觀下線。為什么還要去詢問其他哨兵呢?這是因?yàn)樯诒蛂edis服務(wù)器之間沒有ping成功也可以能網(wǎng)絡(luò)之間的問題。為什么要對(duì)哨兵進(jìn)行領(lǐng)導(dǎo)者選舉?
當(dāng)確定redis服務(wù)器確實(shí)掛了以后,哨兵要進(jìn)行故障轉(zhuǎn)移,并且只能有一個(gè)哨兵去完成該操作,所以這時(shí)候就要選舉出一名哨兵來當(dāng)此重任。那怎么選舉呢?
- 哨兵向其它哨兵發(fā)送
is-master-down-by-addr除了確認(rèn)是否機(jī)器是否可以下線以外,會(huì)有發(fā)起選舉的作用 - 其它哨兵收到命令以后,如果如果沒有答應(yīng)其它哨兵的選舉請(qǐng)求就會(huì)答應(yīng)該哨兵的請(qǐng)求
- 當(dāng)同意(包括自己)的哨兵個(gè)數(shù)達(dá)到
quorum,該哨兵就會(huì)成為領(lǐng)導(dǎo)者
-
怎么完成故障轉(zhuǎn)移?
當(dāng)確定原來的redis主服務(wù)器已經(jīng)客觀下線以后,就會(huì)從從服務(wù)器中選出一臺(tái)作為新的主服務(wù)器,選擇順序如下:
- 看配置的
slave-priority,如果從服務(wù)器不相等,返回最高的那臺(tái),如果相同看下一步 - 看offset,即復(fù)制偏移量,如果復(fù)制偏移量不同,返回最高那臺(tái),如果相同看下一步
- 看runid,程序id,runid越低可以看做是越早開啟,返回越低那臺(tái)
確定完是哪臺(tái)從服務(wù)器作為新的主服務(wù)器以后,會(huì)修改新的從服務(wù)器的slaveof與各個(gè)哨兵的監(jiān)控的主服務(wù)器的地址和ip。
舉個(gè)栗子
開啟三個(gè)redis服務(wù),一主兩從;開啟三個(gè)哨兵;把主服務(wù)器關(guān)閉掉,查看從服務(wù)器是否會(huì)產(chǎn)生新的主服務(wù)器。
redis配置
# 主服務(wù)器 redis-6379.conf
port 6379
daemonize yes
protected-mode no
logfile "6379.log"
dbfilename "dump-6379.rdb"
# 從服務(wù)器 redis-6380.conf
port 6380
daemonize yes
protected-mode no
logfile "6380.log"
dbfilename "dump-6380.rdb"
slaveof 139.199.168.61 6379
# 從服務(wù)器 redis-6381.conf
port 6381
daemonize yes
protected-mode no
logfile "6381.log"
dbfilename "dump-6381.rdb"
slaveof 139.199.168.61 6379
redis-server redis-6379.conf 啟動(dòng)各個(gè)redis服務(wù)。
哨兵配置
#sentinel-26379.conf
port 26379
daemonize yes
protected-mode no
logfile "26379.log"
#監(jiān)控的redis主服務(wù)器,最后面的2就是上面提到的quorum,當(dāng)哨兵響應(yīng)的個(gè)數(shù)超過這個(gè)數(shù),redis服務(wù)器才會(huì)被認(rèn)為是客觀下線
sentinel monitor mymaster 139.199.168.61 6379 2
#當(dāng)超過這個(gè)值redis服務(wù)器對(duì)哨兵的ping不做出響應(yīng)會(huì)被哨兵認(rèn)為是主觀下線
sentinel down-after-milliseconds mymaster 10000
#sentinel-26380.conf
port 26380
daemonize yes
protected-mode no
logfile "26380.log"
sentinel monitor mymaster 139.199.168.61 6379 2
sentinel down-after-milliseconds mymaster 10000
#sentinel-26381.conf
port 26381
daemonize yes
protected-mode no
logfile "26381.log"
sentinel monitor mymaster 139.199.168.61 6379 2
sentinel down-after-milliseconds mymaster 10000
redis-sentinel sentinel-26379.conf 啟動(dòng)各個(gè)哨兵服務(wù)。
哨兵啟動(dòng)后我們可以查看sentinel-26379.conf:
port 26379
daemonize yes
protected-mode no
logfile "26379.log"
sentinel myid f65e6f01127c838e023f48b73c0f9642548a176d
sentinel monitor mymaster 139.199.168.61 6379 2
# Generated by CONFIG REWRITE
dir "/usr/local/redis-3.2.6"
sentinel down-after-milliseconds mymaster 10000
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 0
sentinel known-slave mymaster 139.199.168.61 6381
sentinel known-slave mymaster 139.199.168.61 6380
sentinel known-sentinel mymaster 10.104.90.159 26381 a8ca7a98c5b462c5c341650386be589cf31a5761
sentinel known-sentinel mymaster 10.104.90.159 26380 eb637934a6e00a26c36fef681cb1e194127b7d2c
sentinel current-epoch 0
會(huì)發(fā)現(xiàn)哨兵已經(jīng)把redis的從服務(wù)器和其他哨兵加進(jìn)來了,我們關(guān)閉主服務(wù)器kill redis主服務(wù)器的進(jìn)程,再查看sentinel-26379.conf:
port 26379
daemonize yes
protected-mode no
logfile "26379.log"
sentinel myid f65e6f01127c838e023f48b73c0f9642548a176d
sentinel monitor mymaster 139.199.168.61 6381 2
# Generated by CONFIG REWRITE
dir "/usr/local/redis-3.2.6"
sentinel down-after-milliseconds mymaster 10000
sentinel config-epoch mymaster 1
sentinel leader-epoch mymaster 1
sentinel known-slave mymaster 139.199.168.61 6379
sentinel known-slave mymaster 139.199.168.61 6380
sentinel known-sentinel mymaster 10.104.90.159 26381 a8ca7a98c5b462c5c341650386be589cf31a5761
sentinel known-sentinel mymaster 10.104.90.159 26380 eb637934a6e00a26c36fef681cb1e194127b7d2c
sentinel current-epoch 1
我們可以看到監(jiān)控的主服務(wù)器已經(jīng)切換成 6381而不是之前的6379了。
Java使用
- Java使用redis的一般做法:
Jedis jedis = new Jedis("139.199.168.61", 6379);
System.out.println(jedis.get("hello"));
這種做法最大的弊端在于萬一139.199.168.61:6379 這個(gè)redis掛了,這個(gè)Java應(yīng)用就廢了。
- 如果我們現(xiàn)在知道了哨兵模式,我們可以改寫成以下的做法:
//聲明一個(gè)set 存放哨兵集群的地址和端口
Set<String> sentinels = new HashSet<String>();
sentinels.add("139.199.168.61:26379");
sentinels.add("139.199.168.61:26380");
sentinels.add("139.199.168.61:26381");
JedisSentinelPool sentinelPool = new JedisSentinelPool("mymaster", sentinels);
// 使用sentinelPool獲取jedis對(duì)象
Jedis master = sentinelPool.getResource();
System.out.println(master.get("hello"));
master.close();
sentinelPool.destroy();
這樣子,當(dāng)主服務(wù)器掛掉以后,哨兵集群會(huì)返回給程序新的主服務(wù)器地址,保證服務(wù)不會(huì)掛掉。