Redis 集群實(shí)現(xiàn)的幾種方式
在業(yè)務(wù)場景中,要想做到有備無患,最好是用集群,沒有集群,至少也要做到主從,有了主從,當(dāng) master 掛掉的時(shí)候,讓從庫過來接管,服務(wù)就可以繼續(xù),否則 master 需要經(jīng)過數(shù)據(jù)恢復(fù)和重啟的過程,這就可能會(huì)拖很長的時(shí)間,影響線上業(yè)務(wù)的持續(xù)服務(wù)。在了解 Redis 集群實(shí)現(xiàn)之前需要了解redis 的主從復(fù)制,了解主從復(fù)制之前首先要了解分布式系統(tǒng)的理論基石 CAP 原理。
CAP
CAP是分布式存儲(chǔ)的理論基石.
C:一致性;A:可用性;P:分區(qū)容忍性;原理:當(dāng)網(wǎng)絡(luò)分區(qū)發(fā)生時(shí),一致性和可用性很難兩全.
redis 滿足可用性(AP);保證最終一致性。
Redis 保證「最終一致性」,從節(jié)點(diǎn)會(huì)努力追趕主節(jié)點(diǎn),最終從節(jié)點(diǎn)的狀態(tài)會(huì)和主節(jié)點(diǎn)
的狀態(tài)將保持一致。如果網(wǎng)絡(luò)斷開了,主從節(jié)點(diǎn)的數(shù)據(jù)將會(huì)出現(xiàn)大量不一致,一旦網(wǎng)絡(luò)恢
復(fù),從節(jié)點(diǎn)會(huì)采用多種策略努力追趕上落后的數(shù)據(jù),繼續(xù)盡力保持和主節(jié)點(diǎn)一致。
關(guān)于同步
主從同步
Redis 同步支持主從同步和從從同步,從從同步功能是 Redis 后續(xù)版本增加的功能,為
了減輕主庫的同步負(fù)擔(dān)。后面為了描述上的方便,統(tǒng)一理解為主從同步。
增量同步
同步是指令流,從節(jié)點(diǎn)一邊同步指令流,一邊反饋?zhàn)约旱钠屏浚瑀edis 的復(fù)制內(nèi)存buffer是一個(gè)定長的環(huán)形數(shù)組;如果內(nèi)存滿了就會(huì)從頭開始覆蓋前面的內(nèi)容。
快照同步
防止在網(wǎng)絡(luò)不好,主從無法及時(shí)同步。造成指令覆蓋,非常耗費(fèi)資源的同步,在主節(jié)點(diǎn)上調(diào)用一次bgsave,將當(dāng)前內(nèi)存中的數(shù)據(jù)全部快照到磁盤中。然后將內(nèi)容全部同步到從節(jié)點(diǎn),從節(jié)點(diǎn)接收完文件后,立即進(jìn)行一次全量加載,然后通知主節(jié)點(diǎn)同步buffer,如果復(fù)制buffer的大小過小,會(huì)造成快照同步死循環(huán);務(wù)必配置合適的buffer大小.
無盤復(fù)制
Redis 2.8.18 版開始支持無盤復(fù)制。所謂無盤復(fù)制是指主服務(wù)器直接通過套接字將快照內(nèi)容發(fā)送到從節(jié)點(diǎn),生成快照是一個(gè)遍歷的過程,主節(jié)點(diǎn)會(huì)一邊遍歷內(nèi)存,一遍將序列化的內(nèi)容發(fā)送到從節(jié)點(diǎn),從節(jié)點(diǎn)還是跟之前一樣,先將接收到的內(nèi)容存儲(chǔ)到磁盤文件中,再進(jìn)行一次性加載。
wait 指令
redis 3.0 以后才有,命令wait n t,表示 t 時(shí)間內(nèi)等待同步 n 個(gè)節(jié)點(diǎn),如果t=0 出現(xiàn)網(wǎng)路分區(qū),則redis 會(huì)喪失可用性。
sentinel 集群
可以將redis sentinel 集群看作是一個(gè)zookeeper集群,一般是由3-5個(gè)節(jié)點(diǎn)組成,Redis 主從采用異步復(fù)制,意味著當(dāng)主節(jié)點(diǎn)掛掉時(shí),從節(jié)點(diǎn)可能沒有收到全部的同步消息,這部分未同步的消息就丟失了。如果主從延遲特別大,那么丟失的數(shù)據(jù)就可能會(huì)特別多。Sentinel 無法保證消息完全不丟失,但是也盡可能保證消息少丟失。它有兩個(gè)選項(xiàng)可以
限制主從延遲過大。
- min-slaves-to-write 1
- min-slaves-max-lag 10
第一個(gè)參數(shù)表示主節(jié)點(diǎn)必須至少有一個(gè)從節(jié)點(diǎn)在進(jìn)行正常復(fù)制,否則就停止對外寫服務(wù),喪失可用性。sentinel 的默認(rèn)端口是26379,主節(jié)點(diǎn)掛掉,會(huì)斷開所有連接,重新與新的主節(jié)點(diǎn)建立連接,所有update 操作會(huì)報(bào)錯(cuò),捕獲一個(gè)readOnlyError。
主從切換后,之前的主庫被降級(jí)到從庫,所有的修改性的指令都會(huì)拋出 ReadonlyError。
如果沒有修改性指令,雖然連接不會(huì)得到切換,但是數(shù)據(jù)不會(huì)被破壞,所以即使不切換也沒關(guān)系。
codis 集群
單實(shí)例redis 只用到了一個(gè)cpu,無法完成海量數(shù)據(jù)的存儲(chǔ)和管理,codis 是redis 集群解決方案之一,是前豌豆莢團(tuán)隊(duì)開發(fā)的,項(xiàng)目負(fù)責(zé)人 劉奇 又開發(fā)了分布式數(shù)據(jù)庫TiDB,使用go 語言,他是一個(gè)代理中間件;使用redis 協(xié)議對外服務(wù)。
分片原理
默認(rèn) 1024個(gè)槽,可以調(diào)整,建議調(diào)整到2048、4096,slot 的計(jì)算方式:key->crc32 得到hash 值->hash%1024=slot,會(huì)在內(nèi)存中維護(hù)slot 和redis 實(shí)例的關(guān)系。
不同codis 實(shí)例直接槽位關(guān)系同步
使用zk、etcd 存儲(chǔ)槽位關(guān)系 從而實(shí)現(xiàn)共享槽位關(guān)系配置。
擴(kuò)容
codis 對redis 進(jìn)行了改造,增加了 slotsscan 命令可以遍歷指定slot 下的所有的key,codis 接收到正在遷移的key,會(huì)強(qiáng)制遷移然后將請求打到 新的實(shí)例上。
缺點(diǎn)
- 不是親兒子
- 單個(gè)key 不易過大
- 不支持事物
- 增加 代理層的網(wǎng)絡(luò)開銷
- 需要維護(hù)zk 集群
優(yōu)點(diǎn)
- slot自動(dòng)均衡
- 有很好的后臺(tái)管理系統(tǒng),qps 曲線,slot 狀態(tài),slot分到哪個(gè)實(shí)例 等等。
redis cluster 集群
RedisCluster 是 Redis 的親兒子,它是 Redis 作者自己提供的 Redis 集群化方案。相對于 Codis 的不同,它是去中心化的。每個(gè)節(jié)點(diǎn)負(fù)責(zé)不同的數(shù)據(jù),
Redis 集群節(jié)點(diǎn)采用 Gossip 協(xié)議來廣播自己的狀態(tài)以及自己對整個(gè)集群認(rèn)知的改變。比如一個(gè)節(jié)點(diǎn)發(fā)現(xiàn)某個(gè)節(jié)點(diǎn)失聯(lián)了 (PFail),它會(huì)將這條信息向整個(gè)集群廣播,其它節(jié)點(diǎn)也就可以收到這點(diǎn)失聯(lián)信息。如果一個(gè)節(jié)點(diǎn)收到了某個(gè)節(jié)點(diǎn)失聯(lián)的數(shù)量 (PFail Count) 已經(jīng)達(dá)到了集群的大多數(shù),就可以標(biāo)記該節(jié)點(diǎn)為確定下線狀態(tài) (Fail),然后向整個(gè)集群廣播,強(qiáng)迫其它節(jié)點(diǎn)也接收該節(jié)點(diǎn)已經(jīng)下線的事實(shí),并立即對該失聯(lián)節(jié)點(diǎn)進(jìn)行主從切換。
默認(rèn)分16384 個(gè)槽位,客戶端會(huì)存儲(chǔ)一份槽位配置信息。
槽位定位
- key->crc16得到hash值->hash%16384=slot
- 通過在key 字符串里嵌入tag 標(biāo)記;
- 可以強(qiáng)制key所在的槽位;
跳轉(zhuǎn)
當(dāng)槽位發(fā)生遷移時(shí),請求舊槽位 會(huì)返回一個(gè)MOVED 指令 后面跟一個(gè)目標(biāo)節(jié)點(diǎn)地址,客戶端收到MOVED 指令后,立刻糾正本地槽位映射表。
第二個(gè) asking 指令和 moved 不一樣,它是用來臨時(shí)糾正槽位的。如果當(dāng)前槽位正處于
遷移中,指令會(huì)先被發(fā)送到槽位所在的舊節(jié)點(diǎn),如果舊節(jié)點(diǎn)存在數(shù)據(jù),那就直接返回結(jié)果
了,如果不存在,那么它可能真的不存在也可能在遷移目標(biāo)節(jié)點(diǎn)上。所以舊節(jié)點(diǎn)會(huì)通知客戶
端去新節(jié)點(diǎn)嘗試一下拿數(shù)據(jù),看看新節(jié)點(diǎn)有沒有。這時(shí)候就會(huì)給客戶端返回一個(gè) asking error
攜帶上目標(biāo)節(jié)點(diǎn)的地址??蛻舳耸盏竭@個(gè) asking error 后,就會(huì)去目標(biāo)節(jié)點(diǎn)去嘗試??蛻舳?/p>
不會(huì)刷新槽位映射關(guān)系表,因?yàn)樗皇桥R時(shí)糾正該指令的槽位信息,不影響后續(xù)指令。
為了防止連續(xù)跳轉(zhuǎn),rt 過高,客戶端設(shè)置重試次數(shù)。
遷移
一個(gè)槽一個(gè)槽的進(jìn)行遷移,沒有有很友好的UI。大致過程如下:
從源節(jié)點(diǎn)獲取內(nèi)容-》存到目標(biāo)節(jié)點(diǎn)-》從源節(jié)點(diǎn)刪除內(nèi)容,整個(gè)過程是同步的,會(huì)造成阻塞。
集群變更感知
當(dāng)服務(wù)器節(jié)點(diǎn)變更時(shí),客戶端應(yīng)該即時(shí)得到通知以實(shí)時(shí)刷新自己的節(jié)點(diǎn)關(guān)系表。那客戶端是如何得到通知的呢?這里要分 2 種情況:
目標(biāo)節(jié)點(diǎn)掛掉了,客戶端會(huì)拋出一個(gè) ConnectionError,緊接著會(huì)隨機(jī)挑一個(gè)節(jié)點(diǎn)來重試,這時(shí)被重試的節(jié)點(diǎn)會(huì)通過 moved error 告知目標(biāo)槽位被分配到的新的節(jié)點(diǎn)地址。
運(yùn)維手動(dòng)修改了集群信息,將 master 切換到其它節(jié)點(diǎn),并將舊的 master 移除集群。這時(shí)打在舊節(jié)點(diǎn)上的指令會(huì)收到一個(gè) ClusterDown 的錯(cuò)誤,告知當(dāng)前節(jié)點(diǎn)所在集群不可用 (當(dāng)前節(jié)點(diǎn)已經(jīng)被孤立了,它不再屬于之前的集群)。這時(shí)客戶端就會(huì)關(guān)閉所有的連接,清空槽位映射關(guān)系表,然后向上層拋錯(cuò)。待下一條指令過來時(shí),就會(huì)重新嘗試初始化節(jié)點(diǎn)信息。
容錯(cuò)
每個(gè)主節(jié)點(diǎn)設(shè)置多個(gè)從節(jié)點(diǎn),主節(jié)點(diǎn)掛了之后從某個(gè)從節(jié)點(diǎn)中提拔一個(gè),沒有可用從節(jié)點(diǎn),可用設(shè)置cluster-require-full-coverage 允許部分錯(cuò)誤,其他節(jié)點(diǎn)正常對外服務(wù)。
網(wǎng)絡(luò)抖動(dòng)
設(shè)置主從切換松弛系數(shù)和cluster-node-timeout ,防止網(wǎng)絡(luò)抖動(dòng)導(dǎo)致頻繁的主從切換。