by shihang.mai
1. redis單節(jié)點(diǎn)問題
1. 單點(diǎn)故障
2. 容量有限
3. 壓力大(多連接)
2. AKF分析,解決redis單節(jié)點(diǎn)問題
主備:client只能訪問主,master掛了,備機(jī)能接上
主從:client同時(shí)能訪問主和從

- 沿x軸,做redis實(shí)例全量鏡像,1主多備,主做讀寫,備做讀,實(shí)現(xiàn)讀寫分離。解決單點(diǎn)問題。
- 沿y軸,業(yè)務(wù)功能拆分。解決容量問題。
- 沿z軸,同一業(yè)務(wù)數(shù)據(jù)分散。解決壓力問題。
通過AKF,一個(gè)redis變多個(gè),會引入新的問題:數(shù)據(jù)一致性
2.1 X軸推導(dǎo)最終一致性

如上圖的"情況1"
強(qiáng)一致性,破壞可用性
當(dāng)客戶端set值時(shí),set完主,所有節(jié)點(diǎn)阻塞直到數(shù)據(jù)同步一致,這樣是強(qiáng)一致性。但當(dāng)有一個(gè)從10分鐘后才返回,客戶端響應(yīng)慢,破壞了可用性。引入1變多,就是因?yàn)榻鉀Q單點(diǎn),即解決可用性問題,而現(xiàn)在強(qiáng)一致性破壞可用性,方案不可取
如上圖的"情況2"
丟失數(shù)據(jù)
既然情況1中強(qiáng)一致性會破壞可用性,那么我們就容忍丟失一部分?jǐn)?shù)據(jù)。同步數(shù)據(jù)改為異步。當(dāng)數(shù)據(jù)還沒同步完成,主down了,那么從備為主后,數(shù)據(jù)就丟失了。
如上圖的"情況3"
最終一致性
相對于情況2,可以保證數(shù)據(jù)不丟失。kafka不是唯一的實(shí)現(xiàn)方式,只要是可靠的,集群的,響應(yīng)速度足夠快。這樣做數(shù)據(jù)不會丟失,也可以及時(shí)向客戶端返回。
"情況3"看似很好,但是主還是單點(diǎn)。所以一般對主做HA(高可用)
高可用:主從切換
2.2 Sentinel推導(dǎo)

- 當(dāng)3個(gè)監(jiān)控節(jié)點(diǎn)時(shí),3個(gè)說沒down,redis才沒down.強(qiáng)一致性
- 因?yàn)闀W(wǎng)絡(luò)波動,可能會有些監(jiān)控程序無法連接,我們讓一步,一部分(n/2+1)給出結(jié)果即可。
- 當(dāng)3個(gè)節(jié)點(diǎn)時(shí),需要2個(gè)說redis沒down,redis才沒down.
- 當(dāng)4個(gè)節(jié)點(diǎn)時(shí),需要3個(gè)說redis沒down,redis才沒down.
3個(gè)和4個(gè)節(jié)點(diǎn)對比,都同樣容許一個(gè)down,但是4臺發(fā)生的風(fēng)險(xiǎn)比3臺高.所以我們一般使用奇數(shù)臺(n/2+1說沒down,才沒down).
- 可用性
3. 主從復(fù)制
“情況2”就是redis的主從復(fù)制模式
可以設(shè)置同步方式:
- 主redis,下一個(gè)rdb到磁盤,然后通過網(wǎng)絡(luò)給從redis
- 主redis,直接不下rdb,直接通過網(wǎng)絡(luò)發(fā)出rdb給從redis
3.1 Sentinel哨兵
看上面圖的"監(jiān)控",哨兵是監(jiān)控Master的。
每個(gè)哨兵只需配置監(jiān)控那個(gè)master,但是每個(gè)哨兵最終是知道有其他的哨兵的。這是通過redis發(fā)布訂閱功能,哨兵監(jiān)聽master中sentinel通道得知的。
以上利用Sentinel哨兵+主從復(fù)制,解決主單點(diǎn)問題,并且主掛掉后,也有從補(bǔ)上,但是容量問題未解決
4. 解決容量問題-在client端集成分發(fā)邏輯
解決容量問題,利用akf的y和z軸

4.1 方案1
邏輯:業(yè)務(wù)拆分,根據(jù)不同的業(yè)務(wù)分到不同的redis
用在數(shù)據(jù)可拆分的場景。
4.2 方案2
模式:modula模式
邏輯:hash+取模
它的弊端取模的數(shù)是固定的,影響分布式下的擴(kuò)展性。
4.3 方案
模式: random
用在消息隊(duì)列
4.4 方案
模式: ketama
邏輯:一致性哈希環(huán),映射算法不僅限于hash,crc16,crc32,fnv,md5。環(huán)上一共有0-2^32點(diǎn)
一致性是指,當(dāng)我新加入redis節(jié)點(diǎn)時(shí),不需要做全量redis節(jié)點(diǎn)的重哈希,只會一部分?jǐn)?shù)據(jù)緩存失效,對于大多數(shù)(數(shù)據(jù)-服務(wù)器)這個(gè)對應(yīng)關(guān)系前后一致
- node1和node2通過映射算法,將redis映射到哈希環(huán)上作為物理點(diǎn)
- 現(xiàn)在來一個(gè)data放入redis,data也通過映射算法,映射到哈希環(huán)上
- 可以利用treeMap等等一個(gè)具有排序的數(shù)據(jù)結(jié)構(gòu),將redis映射的物理點(diǎn)放進(jìn)去,然后用data映射出來的點(diǎn)去treeMap找,找出比自身大的點(diǎn),可能處在多個(gè)這樣的點(diǎn),找離自己點(diǎn)最近的物理點(diǎn),將data存進(jìn)去就可以了
- 此時(shí)放入一個(gè)node3,會導(dǎo)致(key->node3)段中的數(shù)據(jù)找不到,而(node3->node2)段的沒影響,原因如下:
before:另外一個(gè)key映射到(key->node3)段,此時(shí)并沒有node3,所以把數(shù)據(jù)放入到了node2
after: 此時(shí)加入node3,還是這個(gè)key,它還是映射到(key->node3)段,那么它就會在node3查找,發(fā)現(xiàn)數(shù)
據(jù)找不到
4.4.1 優(yōu)點(diǎn)
可以分擔(dān)壓力,不會造成全局洗牌(取模的方式會全部洗牌)
4.4.2 缺點(diǎn)
新增節(jié)點(diǎn),會導(dǎo)致一小部分?jǐn)?shù)據(jù)不能命中。正因如此,會有緩存擊穿的問題,將壓力壓到mysql??梢杂眠@樣的方案解決:查的時(shí)候,查離自己最近的兩個(gè)點(diǎn),沒得話再去mysql查
4.5 虛擬節(jié)點(diǎn)
如果只有兩個(gè)node,為避免數(shù)據(jù)傾斜到某一個(gè)node上,可以將兩個(gè)node的值分別加上1-10的數(shù)字,再hash,形成20個(gè)點(diǎn)
5. 解決容量問題-client不需要集成分發(fā)代碼
但是上述方案2 3 4在實(shí)際上會有多個(gè)客戶端,同時(shí)連接多個(gè)redis,redis的連接成本好高。發(fā)展為下面的lvs+proxy。將modula、random、ketama轉(zhuǎn)移到proxy層(無狀態(tài)的),這樣不用和業(yè)務(wù)代碼耦合。

方案234與lvs+proxy區(qū)別,只是將3中模式分在了業(yè)務(wù)和proxy端,他們的弊端都是不能做數(shù)據(jù)庫用。
此方案傾向于將redis作為緩存,而不是數(shù)據(jù)庫用。數(shù)據(jù)庫用cluster模式
6. redis-cluster模式
redis官方就是使用redis-cluster模式去解決容量問題的。每個(gè)實(shí)例均有一些虛擬槽,redis虛擬槽位有16384個(gè)。當(dāng)增加或者刪除實(shí)例節(jié)點(diǎn)時(shí),槽位會進(jìn)行遷移
6.1 cluster原理例子

- 預(yù)分區(qū),先取模為10,現(xiàn)在有redis1和redis2,然后通過mapping,redis1獲得了01234槽,redis2獲得56789槽
- 當(dāng)新增redis3時(shí),將redis1的(3,4),redis2的(8,9)給到redis3,redis3獲得3489,并形成上圖。這樣做的話,不用重新rehash,只需遷移數(shù)據(jù).
尋找槽的過程并不是一次就命中的,集群保證了最多兩次就能命中對應(yīng)槽所在的節(jié)點(diǎn)。因?yàn)槊總€(gè)實(shí)例中均有hash函數(shù)和其他節(jié)點(diǎn)負(fù)責(zé)的槽信息。
假設(shè)要獲取的key在redis3,4號槽位.但客戶端實(shí)際連接的是redis2
- 客戶端將key發(fā)向?qū)嶋H的redis2,然后在redis2做hash取模運(yùn)算,得到值為4。
- 4與自己的槽位(5,6,7)比較,發(fā)現(xiàn)不在自己槽位上,而是在redis3上。將這個(gè)信息告訴客戶端。
- 客戶端連接redis3,發(fā)出key,查找結(jié)果。
6.2 集群通訊
為什么某一個(gè)節(jié)點(diǎn)擁有其他節(jié)點(diǎn)的信息呢,這就涉及到集群是如何通訊的了。gossip協(xié)議

節(jié)點(diǎn)之間通過ping/pong交互信息
- 節(jié)點(diǎn)A對節(jié)點(diǎn)B發(fā)送一個(gè)meet操作,B返回后表示A和B之間能夠進(jìn)行溝通。
2.節(jié)點(diǎn)A對節(jié)點(diǎn)C發(fā)送meet操作,C返回后,A和C之間也能進(jìn)行溝通。 - 然后B根據(jù)對A的了解,就能找到C,B和C之間也建立了聯(lián)系。
直到所有節(jié)點(diǎn)都能建立聯(lián)系。
這樣每個(gè)節(jié)點(diǎn)都能互相直到對方負(fù)責(zé)哪些槽
6.3 集群伸縮
6.3.1 集群擴(kuò)容
當(dāng)有新的節(jié)點(diǎn)準(zhǔn)備好加入集群時(shí),這個(gè)新的節(jié)點(diǎn)還是孤立節(jié)點(diǎn),并且它不是主節(jié)點(diǎn)就是從節(jié)點(diǎn)。
- 主節(jié)點(diǎn)就要分擔(dān)槽位數(shù)據(jù)(原理例子中寫得很清楚了)
- 從節(jié)點(diǎn)作為故障轉(zhuǎn)移備份
這里詳細(xì)說說具體怎么做

6.3.2 集群縮容
- 當(dāng)是從節(jié)點(diǎn)下線,是不影響集群的,
各個(gè)主節(jié)點(diǎn)只是記錄這個(gè)從節(jié)點(diǎn)下線了。 - 當(dāng)是主節(jié)點(diǎn)下線,如果該主節(jié)點(diǎn)沒槽位,那么直接下線,其他主節(jié)點(diǎn)直接記錄這個(gè)主節(jié)點(diǎn)下線。
- 當(dāng)下線的主節(jié)點(diǎn)具有槽位,那么就需要觸發(fā)故障轉(zhuǎn)移
故障轉(zhuǎn)移是主觀下線+客戶下線實(shí)現(xiàn)的

上圖為主觀下線
- NodeA定時(shí)ping NodeB
- 如果ping通,那么NodeA會記錄和NodeB最后通訊時(shí)間
- 如果沒ping通,那么就要判斷上一次的最后通訊時(shí)間有沒超過配置的時(shí)間了,如果超了,那么NodeA就標(biāo)記NodeB為主觀下線了
- NodeA會擁有其他NodeB、NodeC....的信息。即NodeA有其他Node對NodeB是否主觀下線的信息,當(dāng)半數(shù)以上的持有槽的主節(jié)點(diǎn)都標(biāo)記某個(gè)節(jié)點(diǎn)主觀下線,就會嘗試客觀下線。
客觀下線

那么從節(jié)點(diǎn)如何成為master節(jié)點(diǎn)呢。
- 首先是進(jìn)行資格檢查,只有具備資格的從節(jié)點(diǎn)才能參加選舉:
- 故障節(jié)點(diǎn)的所有從節(jié)點(diǎn)檢查和故障主節(jié)點(diǎn)之間的斷線時(shí)間。超過cluster-node-timeout * cluster-slave-validati-factor(默認(rèn)10)則取消選舉資格
- 不同的從節(jié)點(diǎn),會有不同的偏移量。offset最大的slave節(jié)點(diǎn),優(yōu)先參與選舉,其他的從解節(jié)點(diǎn)按順序延遲選舉。所以說offset最大的slave節(jié)點(diǎn)最有機(jī)會當(dāng)選master節(jié)點(diǎn)。
- 當(dāng)發(fā)出選舉后,其他的主節(jié)點(diǎn)就開始投票了,過半當(dāng)選。當(dāng)選出了master節(jié)點(diǎn)后,該master向集群中其他節(jié)點(diǎn)廣播Pong消息,表明已完成故障轉(zhuǎn)移
6.4 數(shù)據(jù)分治弊端
聚合操作和事務(wù)很難實(shí)現(xiàn)。
cluster支持用hash tag將key分布到同一個(gè)節(jié)點(diǎn)上,這樣就可以實(shí)現(xiàn)類似單機(jī)的事務(wù)
7. 題外話
twitter/twemproxy和predixy可以做redis的代理,可以像單機(jī)一樣使用集群