1.主從復(fù)制的原理:
1) 連接階段
1.slave node 啟動時(執(zhí)行 slaveof 命令),會在自己本地保存 master node 的信息,包括 master node 的 host 和 ip。
2.slave node 內(nèi)部有個定時任務(wù) replicationCron(源碼 replication.c),每隔 1秒鐘檢查是否有新的 master node 要連接和復(fù)制,如果發(fā)現(xiàn),就跟 master node 建立socket 網(wǎng)絡(luò)連接,如果連接成功,從節(jié)點為該 socket 建立一個專門處理復(fù)制工作的文件事件處理器,負責后續(xù)的復(fù)制工作,如接收 RDB 文件、接收命令傳播等。當從節(jié)點變成了主節(jié)點的一個客戶端之后,會給主節(jié)點發(fā)送 ping 請求。
2)數(shù)據(jù)同步階段
1.全量復(fù)制:master node 第一次執(zhí)行全量復(fù)制,通過 bgsave 命令在本地生成一份 RDB 快照,將 RDB 快照文件發(fā)給 slave node(如果超時會重連,可以調(diào)大 repl-timeout 的值)。slave node 首先清除自己的舊數(shù)據(jù),然后用 RDB 文件加載數(shù)據(jù)。
2.問題:生成 RDB 期間,master 接收到的命令怎么處理?開始生成 RDB 文件時,master 會把所有新的寫命令緩存在內(nèi)存中。在 slave node保存了 RDB 之后,再將新的寫命令復(fù)制給 slave node。
3)命令傳播階段
1.原理:master node 持續(xù)將寫命令,異步復(fù)制給 slave node。
2.延遲:延遲是不可避免的,只能通過優(yōu)化網(wǎng)絡(luò)。
3.延遲的配置:repl-disable-tcp-nodelay? 參數(shù)---》當設(shè)置為 yes 時,TCP 會對包進行合并從而減少帶寬,但是發(fā)送的頻率會降低,從節(jié)點數(shù)據(jù)延遲增加,一致性變差;具體發(fā)送頻率與 Linux 內(nèi)核的配置有關(guān),默認配置為40ms。當設(shè)置為 no 時,TCP 會立馬將主節(jié)點的數(shù)據(jù)發(fā)送給從節(jié)點,帶寬增加但延遲變小。
4.延遲的配置應(yīng)用:一般來說,只有當應(yīng)用對 Redis 數(shù)據(jù)不一致的容忍度較高,且主從節(jié)點之間網(wǎng)絡(luò)狀況不好時,才會設(shè)置為 yes;多數(shù)情況使用默認值 no。
5.問題:如果從節(jié)點有一段時間斷開了與主節(jié)點的連接是不是要重新全量復(fù)制一遍?如果可以增量復(fù)制,怎么知道上次復(fù)制到哪里?
通過 master_repl_offset 記錄的偏移量

4)主從復(fù)制的缺點:
主從模式解決了數(shù)據(jù)備份和性能(通過讀寫分離)的問題,但是還是存在一些不足:1、RDB 文件過大的情況下,同步非常耗時。2、在一主一從或者一主多從的情況下,如果主服務(wù)器掛了,對外提供的服務(wù)就不可用了,單點問題沒有得到解決。如果每次都是手動把之前的從服務(wù)器切換成主服務(wù)器,這個比較費時費力,還會造成一定時間的服務(wù)不可用。
2.可用性保證之 Sentinel(哨兵)
1)原理
1.思路: 通過運行監(jiān)控服務(wù)器來保證服務(wù)的可用性。
2.Sentinel流程: 為了保證監(jiān)控服務(wù)器的可用性,我們會對 Sentinel 做集群的部署。Sentinel 既監(jiān)控所有的 Redis 服務(wù),Sentinel 之間也相互監(jiān)控。

3.注意點: Sentinel 本身沒有主從之分,只有 Redis 服務(wù)節(jié)點有主從之分。
2)如何判斷服務(wù)下線?
1.主觀下線:Sentinel 默認以每秒鐘 1 次的頻率向 Redis 服務(wù)節(jié)點發(fā)送 PING 命令。如果在down-after-milliseconds 內(nèi)都沒有收到有效回復(fù),Sentinel 會將該服務(wù)器標記為下線!

2.客觀下線: Sentinel 節(jié)點會繼續(xù)詢問其他的 Sentinel 節(jié)點,確認這個節(jié)點是否下線,如果多數(shù) Sentinel 節(jié)點都認為 master 下線,master 才真正確認被下線(客觀下線),這個時候就需要重新選舉 master!
3)故障轉(zhuǎn)移
1.原理:如果 master 被標記為客觀下線,就會開始故障轉(zhuǎn)移流程。故障轉(zhuǎn)移流程的第一步就是在 Sentinel 集群選擇一個 Leader,由 Leader 完成故障轉(zhuǎn)移流程。Sentinle 通過 Raft 算法,實現(xiàn) Sentinel 選舉。
2.問題1:怎么讓一個原來的 slave 節(jié)點成為主節(jié)點? 1)選出 Sentinel Leader 之后,由 Sentinel Leader 向某個節(jié)點發(fā)送 slaveof no one命令,讓它成為獨立節(jié)點。2)然后向其他節(jié)點發(fā)送 slaveof x.x.x.x xxxx(本機服務(wù)),讓它們成為這個節(jié)點的子節(jié)點,故障轉(zhuǎn)移完成。
3.問題2:這么多從節(jié)點,選誰成為主節(jié)點? 關(guān)于從節(jié)點選舉,一共有四個因素影響選舉的結(jié)果,分別是斷開連接時長、優(yōu)先級排序、復(fù)制數(shù)量、進程 id。如果與哨兵連接斷開的比較久,超過了某個閾值,就直接失去了選舉權(quán)。如果擁有選舉權(quán),那就看誰的優(yōu)先級高,這個在配置文件里可以設(shè)置(replica-priority 100),數(shù)值越小優(yōu)先級越高。如果優(yōu)先級相同,就看誰從 master 中復(fù)制的數(shù)據(jù)最多(復(fù)制偏移量最大),選最多的那個,如果復(fù)制數(shù)量也相同,就選擇進程 id 最小的那個。
4)Raft算法
1.目的:通過復(fù)制的方式,使所有節(jié)點達成一致!
2.步驟:領(lǐng)導(dǎo)選舉,數(shù)據(jù)復(fù)制
3.核心思想:先到先得,少數(shù)服從多數(shù)。
4.演示:http://thesecretlivesofdata.com/raft/
5.總結(jié):1)master 客觀下線觸發(fā)選舉,而不是過了 election timeout 時間開始選舉。2)Leader 并不會把自己成為 Leader 的消息發(fā)給其他 Sentinel。其他 Sentinel 等待 Leader 從 slave 選出 master 后,檢測到新的 master 正常工作后,就會去掉客觀下線的標識,從而不需要進入故障轉(zhuǎn)移流程
5)哨兵機制的不足
1.主從切換的過程中會丟失數(shù)據(jù),因為只有一個 master。只能單點寫,沒有解決水平擴容的問題。如果數(shù)據(jù)量非常大,這個時候我們需要多個 master-slave 的 group,把數(shù)據(jù)分布到不同的 group 中。數(shù)據(jù)怎么分片?分片之后,怎么實現(xiàn)路由?且看下一節(jié)Redis 分布式方案!
3.Redis 分布式方案
1)Redis 數(shù)據(jù)的分片的方案:
1.客戶端實現(xiàn)相關(guān)的邏輯,例如用取?;蛘咭恢滦怨?key 進行分片,查詢和修改都先判斷 key 的路由。
2.做分片處理的邏輯抽取出來,運行一個獨立的代理服務(wù),客戶端連接到這個代理服務(wù),代理服務(wù)做請求的轉(zhuǎn)發(fā)。
3.基于服務(wù)端實現(xiàn)。
2)客戶端-Sharding
1.代碼展示:

2.shardjedis分片的原理:
1)采用一致性hash算法----》數(shù)據(jù)分布均勻的解決方案
2)關(guān)鍵點:實現(xiàn)hash環(huán),創(chuàng)建虛擬結(jié)點,順時針定位第一個結(jié)點
3)關(guān)鍵代碼:

在getResource()中,獲取了一個Jedis實例。它最終調(diào)用了redis.clients.util.Sharded類的initialize()方法。

在jedis.getShard(“k”+i).getClient()獲取到真正的客戶端。

Sharded用紅黑樹實現(xiàn)了哈希環(huán)??蛻舳藦墓-h(huán)中獲取Redis節(jié)點的信息,虛擬節(jié)點也是映射到對應(yīng)的Redis實例。
3)代理服務(wù)-codis
4)服務(wù)端-Redis Cluster
5)如何保證數(shù)據(jù)分布均勻
1.哈希后取模:例如,hash(key)%N,根據(jù)余數(shù),決定映射到那一個節(jié)點。這種方式比較簡單,屬于靜態(tài)的分片規(guī)則。但是一旦節(jié)點數(shù)量變化,新增或者減少,由于取模的 N 發(fā)生變化,數(shù)據(jù)需要重新分布。
2.一致性哈希:把所有的哈希值空間組織成一個虛擬的圓環(huán)(哈希環(huán)),整個空間按順時針方向組織。因為是環(huán)形空間,0 和 2^32-1 是重疊的。




問題:一致性哈希算法有一個缺點,因為節(jié)點不一定是均勻地分布的,特別是在節(jié)點數(shù)比較少的情況下,所以數(shù)據(jù)不能得到均勻分布。解決這個問題的辦法是引入虛擬節(jié)點(Virtual Node)。


3.Redis 虛擬槽分區(qū)
原理:Redis 創(chuàng)建了 16384 個槽(slot),每個節(jié)點負責一定區(qū)間的 slot。比如 Node1 負責 0-5460,Node2 負責 5461-10922,Node3 負責 10923-16383。

怎么標記:Redis 的每個 master 節(jié)點維護一個 16384 位(2048bytes=2KB)的位序列,比如:序列的第 0 位是 1,就代表第一個 slot 是它負責;序列的第 1 位是 0,代表第二個 slot不歸它負責。對象分布到 Redis 節(jié)點上時,對 key 用 CRC16 算法計算再%16384,得到一個 slot的值,數(shù)據(jù)落到負責這個 slot 的 Redis 節(jié)點上。
問題:怎么讓相關(guān)的數(shù)據(jù)落到同一個節(jié)點上?---》比如有些 multi key 操作是不能跨節(jié)點的,如果要讓某些數(shù)據(jù)分布到一個節(jié)點上,例如用戶 2673 的基本信息和金融信息,怎么辦?
在 key 里面加入{hash tag}即可。Redis 在計算槽編號的時候只會獲取{}之間的字符串進行槽編號計算,這樣由于上面兩個不同的鍵,{}里面的字符串是相同的,因此他們可以被計算出相同的槽。
例:user{2673}base=...? ?user{2673}fin=...