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