1.主從復(fù)制的原理:
1) 連接階段
1.slave node 啟動(dòng)時(shí)(執(zhí)行 slaveof 命令)利诺,會(huì)在自己本地保存 master node 的信息,包括 master node 的 host 和 ip羹与。
2.slave node 內(nèi)部有個(gè)定時(shí)任務(wù) replicationCron(源碼 replication.c),每隔 1秒鐘檢查是否有新的 master node 要連接和復(fù)制,如果發(fā)現(xiàn)呆馁,就跟 master node 建立socket 網(wǎng)絡(luò)連接,如果連接成功毁兆,從節(jié)點(diǎn)為該 socket 建立一個(gè)專門處理復(fù)制工作的文件事件處理器浙滤,負(fù)責(zé)后續(xù)的復(fù)制工作,如接收 RDB 文件气堕、接收命令傳播等纺腊。當(dāng)從節(jié)點(diǎn)變成了主節(jié)點(diǎn)的一個(gè)客戶端之后,會(huì)給主節(jié)點(diǎn)發(fā)送 ping 請(qǐng)求茎芭。
2)數(shù)據(jù)同步階段
1.全量復(fù)制:master node 第一次執(zhí)行全量復(fù)制揖膜,通過(guò) bgsave 命令在本地生成一份 RDB 快照,將 RDB 快照文件發(fā)給 slave node(如果超時(shí)會(huì)重連梅桩,可以調(diào)大 repl-timeout 的值)壹粟。slave node 首先清除自己的舊數(shù)據(jù),然后用 RDB 文件加載數(shù)據(jù)宿百。
2.問(wèn)題:生成 RDB 期間趁仙,master 接收到的命令怎么處理?開(kāi)始生成 RDB 文件時(shí),master 會(huì)把所有新的寫命令緩存在內(nèi)存中犀呼。在 slave node保存了 RDB 之后幸撕,再將新的寫命令復(fù)制給 slave node。
3)命令傳播階段
1.原理:master node 持續(xù)將寫命令外臂,異步復(fù)制給 slave node坐儿。
2.延遲:延遲是不可避免的,只能通過(guò)優(yōu)化網(wǎng)絡(luò)。
3.延遲的配置:repl-disable-tcp-nodelay? 參數(shù)---》當(dāng)設(shè)置為 yes 時(shí)貌矿,TCP 會(huì)對(duì)包進(jìn)行合并從而減少帶寬炭菌,但是發(fā)送的頻率會(huì)降低,從節(jié)點(diǎn)數(shù)據(jù)延遲增加逛漫,一致性變差;具體發(fā)送頻率與 Linux 內(nèi)核的配置有關(guān)黑低,默認(rèn)配置為40ms登渣。當(dāng)設(shè)置為 no 時(shí)浪蹂,TCP 會(huì)立馬將主節(jié)點(diǎn)的數(shù)據(jù)發(fā)送給從節(jié)點(diǎn),帶寬增加但延遲變小螃诅。
4.延遲的配置應(yīng)用:一般來(lái)說(shuō)枷踏,只有當(dāng)應(yīng)用對(duì) Redis 數(shù)據(jù)不一致的容忍度較高菩暗,且主從節(jié)點(diǎn)之間網(wǎng)絡(luò)狀況不好時(shí),才會(huì)設(shè)置為 yes;多數(shù)情況使用默認(rèn)值 no旭蠕。
5.問(wèn)題:如果從節(jié)點(diǎn)有一段時(shí)間斷開(kāi)了與主節(jié)點(diǎn)的連接是不是要重新全量復(fù)制一遍?如果可以增量復(fù)制停团,怎么知道上次復(fù)制到哪里?
通過(guò) master_repl_offset 記錄的偏移量
4)主從復(fù)制的缺點(diǎn):
主從模式解決了數(shù)據(jù)備份和性能(通過(guò)讀寫分離)的問(wèn)題,但是還是存在一些不足:1掏熬、RDB 文件過(guò)大的情況下佑稠,同步非常耗時(shí)。2旗芬、在一主一從或者一主多從的情況下舌胶,如果主服務(wù)器掛了,對(duì)外提供的服務(wù)就不可用了岗屏,單點(diǎn)問(wèn)題沒(méi)有得到解決辆琅。如果每次都是手動(dòng)把之前的從服務(wù)器切換成主服務(wù)器,這個(gè)比較費(fèi)時(shí)費(fèi)力这刷,還會(huì)造成一定時(shí)間的服務(wù)不可用婉烟。
2.可用性保證之 Sentinel(哨兵)
1)原理
1.思路: 通過(guò)運(yùn)行監(jiān)控服務(wù)器來(lái)保證服務(wù)的可用性。
2.Sentinel流程: 為了保證監(jiān)控服務(wù)器的可用性暇屋,我們會(huì)對(duì) Sentinel 做集群的部署似袁。Sentinel 既監(jiān)控所有的 Redis 服務(wù),Sentinel 之間也相互監(jiān)控咐刨。
3.注意點(diǎn): Sentinel 本身沒(méi)有主從之分昙衅,只有 Redis 服務(wù)節(jié)點(diǎn)有主從之分。
2)如何判斷服務(wù)下線定鸟?
1.主觀下線:Sentinel 默認(rèn)以每秒鐘 1 次的頻率向 Redis 服務(wù)節(jié)點(diǎn)發(fā)送 PING 命令而涉。如果在down-after-milliseconds 內(nèi)都沒(méi)有收到有效回復(fù),Sentinel 會(huì)將該服務(wù)器標(biāo)記為下線联予!
2.客觀下線: Sentinel 節(jié)點(diǎn)會(huì)繼續(xù)詢問(wèn)其他的 Sentinel 節(jié)點(diǎn)啼县,確認(rèn)這個(gè)節(jié)點(diǎn)是否下線材原,如果多數(shù) Sentinel 節(jié)點(diǎn)都認(rèn)為 master 下線,master 才真正確認(rèn)被下線(客觀下線)季眷,這個(gè)時(shí)候就需要重新選舉 master余蟹!
3)故障轉(zhuǎn)移
1.原理:如果 master 被標(biāo)記為客觀下線,就會(huì)開(kāi)始故障轉(zhuǎn)移流程子刮。故障轉(zhuǎn)移流程的第一步就是在 Sentinel 集群選擇一個(gè) Leader威酒,由 Leader 完成故障轉(zhuǎn)移流程。Sentinle 通過(guò) Raft 算法挺峡,實(shí)現(xiàn) Sentinel 選舉葵孤。
2.問(wèn)題1:怎么讓一個(gè)原來(lái)的 slave 節(jié)點(diǎn)成為主節(jié)點(diǎn)? 1)選出 Sentinel Leader 之后,由 Sentinel Leader 向某個(gè)節(jié)點(diǎn)發(fā)送 slaveof no one命令沙郭,讓它成為獨(dú)立節(jié)點(diǎn)佛呻。2)然后向其他節(jié)點(diǎn)發(fā)送 slaveof x.x.x.x xxxx(本機(jī)服務(wù)),讓它們成為這個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn)病线,故障轉(zhuǎn)移完成。
3.問(wèn)題2:這么多從節(jié)點(diǎn)鲤嫡,選誰(shuí)成為主節(jié)點(diǎn)? 關(guān)于從節(jié)點(diǎn)選舉送挑,一共有四個(gè)因素影響選舉的結(jié)果,分別是斷開(kāi)連接時(shí)長(zhǎng)暖眼、優(yōu)先級(jí)排序惕耕、復(fù)制數(shù)量、進(jìn)程 id诫肠。如果與哨兵連接斷開(kāi)的比較久司澎,超過(guò)了某個(gè)閾值,就直接失去了選舉權(quán)栋豫。如果擁有選舉權(quán)挤安,那就看誰(shuí)的優(yōu)先級(jí)高,這個(gè)在配置文件里可以設(shè)置(replica-priority 100)丧鸯,數(shù)值越小優(yōu)先級(jí)越高蛤铜。如果優(yōu)先級(jí)相同,就看誰(shuí)從 master 中復(fù)制的數(shù)據(jù)最多(復(fù)制偏移量最大)丛肢,選最多的那個(gè)围肥,如果復(fù)制數(shù)量也相同,就選擇進(jìn)程 id 最小的那個(gè)蜂怎。
4)Raft算法
1.目的:通過(guò)復(fù)制的方式穆刻,使所有節(jié)點(diǎn)達(dá)成一致!
2.步驟:領(lǐng)導(dǎo)選舉杠步,數(shù)據(jù)復(fù)制
3.核心思想:先到先得氢伟,少數(shù)服從多數(shù)榜轿。
4.演示:http://thesecretlivesofdata.com/raft/
5.總結(jié):1)master 客觀下線觸發(fā)選舉,而不是過(guò)了 election timeout 時(shí)間開(kāi)始選舉腐芍。2)Leader 并不會(huì)把自己成為 Leader 的消息發(fā)給其他 Sentinel差导。其他 Sentinel 等待 Leader 從 slave 選出 master 后,檢測(cè)到新的 master 正常工作后猪勇,就會(huì)去掉客觀下線的標(biāo)識(shí)设褐,從而不需要進(jìn)入故障轉(zhuǎn)移流程
5)哨兵機(jī)制的不足
1.主從切換的過(guò)程中會(huì)丟失數(shù)據(jù),因?yàn)橹挥幸粋€(gè) master泣刹。只能單點(diǎn)寫助析,沒(méi)有解決水平擴(kuò)容的問(wèn)題。如果數(shù)據(jù)量非常大椅您,這個(gè)時(shí)候我們需要多個(gè) master-slave 的 group外冀,把數(shù)據(jù)分布到不同的 group 中。數(shù)據(jù)怎么分片?分片之后掀泳,怎么實(shí)現(xiàn)路由?且看下一節(jié)Redis 分布式方案雪隧!
3.Redis 分布式方案
1)Redis 數(shù)據(jù)的分片的方案:
1.客戶端實(shí)現(xiàn)相關(guān)的邏輯,例如用取脑倍妫或者一致性哈希對(duì) key 進(jìn)行分片脑沿,查詢和修改都先判斷 key 的路由。
2.做分片處理的邏輯抽取出來(lái)马僻,運(yùn)行一個(gè)獨(dú)立的代理服務(wù)庄拇,客戶端連接到這個(gè)代理服務(wù),代理服務(wù)做請(qǐng)求的轉(zhuǎn)發(fā)韭邓。
3.基于服務(wù)端實(shí)現(xiàn)措近。
2)客戶端-Sharding
1.代碼展示:
2.shardjedis分片的原理:
1)采用一致性hash算法----》數(shù)據(jù)分布均勻的解決方案
2)關(guān)鍵點(diǎn):實(shí)現(xiàn)hash環(huán),創(chuàng)建虛擬結(jié)點(diǎn)女淑,順時(shí)針定位第一個(gè)結(jié)點(diǎn)
3)關(guān)鍵代碼:
在getResource()中瞭郑,獲取了一個(gè)Jedis實(shí)例。它最終調(diào)用了redis.clients.util.Sharded類的initialize()方法诗力。
在jedis.getShard(“k”+i).getClient()獲取到真正的客戶端凰浮。
Sharded用紅黑樹(shù)實(shí)現(xiàn)了哈希環(huán)∥荆客戶端從哈希環(huán)中獲取Redis節(jié)點(diǎn)的信息袜茧,虛擬節(jié)點(diǎn)也是映射到對(duì)應(yīng)的Redis實(shí)例。
3)代理服務(wù)-codis
4)服務(wù)端-Redis Cluster
5)如何保證數(shù)據(jù)分布均勻
1.哈希后取模:例如瓣窄,hash(key)%N笛厦,根據(jù)余數(shù),決定映射到那一個(gè)節(jié)點(diǎn)俺夕。這種方式比較簡(jiǎn)單裳凸,屬于靜態(tài)的分片規(guī)則贱鄙。但是一旦節(jié)點(diǎn)數(shù)量變化,新增或者減少姨谷,由于取模的 N 發(fā)生變化逗宁,數(shù)據(jù)需要重新分布。
2.一致性哈希:把所有的哈希值空間組織成一個(gè)虛擬的圓環(huán)(哈希環(huán))梦湘,整個(gè)空間按順時(shí)針?lè)较蚪M織瞎颗。因?yàn)槭黔h(huán)形空間,0 和 2^32-1 是重疊的捌议。
問(wèn)題:一致性哈希算法有一個(gè)缺點(diǎn),因?yàn)楣?jié)點(diǎn)不一定是均勻地分布的宫补,特別是在節(jié)點(diǎn)數(shù)比較少的情況下檬姥,所以數(shù)據(jù)不能得到均勻分布。解決這個(gè)問(wèn)題的辦法是引入虛擬節(jié)點(diǎn)(Virtual Node)粉怕。
3.Redis 虛擬槽分區(qū)
原理:Redis 創(chuàng)建了 16384 個(gè)槽(slot)穿铆,每個(gè)節(jié)點(diǎn)負(fù)責(zé)一定區(qū)間的 slot。比如 Node1 負(fù)責(zé) 0-5460斋荞,Node2 負(fù)責(zé) 5461-10922,Node3 負(fù)責(zé) 10923-16383虐秦。
怎么標(biāo)記:Redis 的每個(gè) master 節(jié)點(diǎn)維護(hù)一個(gè) 16384 位(2048bytes=2KB)的位序列平酿,比如:序列的第 0 位是 1,就代表第一個(gè) slot 是它負(fù)責(zé);序列的第 1 位是 0悦陋,代表第二個(gè) slot不歸它負(fù)責(zé)蜈彼。對(duì)象分布到 Redis 節(jié)點(diǎn)上時(shí),對(duì) key 用 CRC16 算法計(jì)算再%16384俺驶,得到一個(gè) slot的值幸逆,數(shù)據(jù)落到負(fù)責(zé)這個(gè) slot 的 Redis 節(jié)點(diǎn)上。
問(wèn)題:怎么讓相關(guān)的數(shù)據(jù)落到同一個(gè)節(jié)點(diǎn)上?---》比如有些 multi key 操作是不能跨節(jié)點(diǎn)的暮现,如果要讓某些數(shù)據(jù)分布到一個(gè)節(jié)點(diǎn)上还绘,例如用戶 2673 的基本信息和金融信息,怎么辦?
在 key 里面加入{hash tag}即可栖袋。Redis 在計(jì)算槽編號(hào)的時(shí)候只會(huì)獲取{}之間的字符串進(jìn)行槽編號(hào)計(jì)算拍顷,這樣由于上面兩個(gè)不同的鍵,{}里面的字符串是相同的塘幅,因此他們可以被計(jì)算出相同的槽昔案。
例:user{2673}base=...? ?user{2673}fin=...