redis無(wú)法像mysql、mongodb那樣基于同步的點(diǎn)位在主庫(kù)發(fā)生變化后從新的主庫(kù)繼續(xù)同步數(shù)據(jù)。 在redis集群中一旦從庫(kù)換主,redis的做法是將更換主庫(kù)的從庫(kù)清空然后從新主庫(kù)完整同步一份數(shù)據(jù)再進(jìn)行續(xù)傳。
整個(gè)從庫(kù)重做流程是這樣的:
1,主庫(kù)bgsave自身數(shù)據(jù)到磁盤(pán)
2,主庫(kù)發(fā)送rdb文件到從庫(kù)
3,從庫(kù)開(kāi)始加載
4,加載完畢開(kāi)始續(xù)傳,同時(shí)開(kāi)始提供服務(wù)
在這個(gè)過(guò)程中redis的內(nèi)存體積越大以上每一個(gè)步驟的時(shí)間都會(huì)被拉長(zhǎng)。為什么Redis內(nèi)存不宜過(guò)大
所以當(dāng)redis實(shí)例過(guò)大時(shí),我們可以嘗試采取分片集群,就是指啟動(dòng)多個(gè) Redis 實(shí)例組成一個(gè)集群,然后按照一定的規(guī)則,把收到的數(shù)據(jù)劃分成多份,每一份用一個(gè)實(shí)例來(lái)保存。
Redis 應(yīng)對(duì)數(shù)據(jù)量增多的兩種方案:縱向擴(kuò)展和橫向擴(kuò)展。
縱向擴(kuò)展:升級(jí)單個(gè) Redis 實(shí)例的資源配置,包括增加內(nèi)存容量、增加磁盤(pán)容量、使用更高配置的 CPU。就像下圖中,原來(lái)的實(shí)例內(nèi)存是 8GB,硬盤(pán)是 50GB,縱向擴(kuò)展后,內(nèi)存增加到 24GB,磁盤(pán)增加到 150GB。(不要求持久化保存 Redis 數(shù)據(jù),是個(gè)不錯(cuò)的選擇,不持久化的話,對(duì)于宕機(jī)后數(shù)據(jù)穿透服務(wù)器的壓力需重點(diǎn)關(guān)注下)
橫向擴(kuò)展:橫向增加當(dāng)前 Redis 實(shí)例的個(gè)數(shù) (切片)
Redis Cluster
在切片集群中,數(shù)據(jù)需要分布在不同實(shí)例上,這個(gè)數(shù)據(jù)和實(shí)例的對(duì)應(yīng)關(guān)系,官方采用Redis Cluster解決;
在 Redis Cluster 方案中,一個(gè)切片集群共有 16384 個(gè)哈希槽,這些哈希槽類似于數(shù)據(jù)分區(qū),每個(gè)鍵值對(duì)都會(huì)根據(jù)它的 key,被映射到一個(gè)哈希槽中。
Redis 會(huì)自動(dòng)把這些槽平均分布在集群實(shí)例上。例如,如果集群中有 N 個(gè)實(shí)例,那么,每個(gè)實(shí)例上的槽個(gè)數(shù)為 16384/N 個(gè)。
hash槽可自動(dòng)分配,也可以根據(jù)實(shí)例的配置手動(dòng)分配,配置較高的實(shí)例可分配更多的槽位以抵抗較大的讀寫(xiě)壓力。
redis的16個(gè)數(shù)據(jù)庫(kù)數(shù)量是默認(rèn)的,1個(gè)數(shù)據(jù)庫(kù)的地位相當(dāng)于mysql的一個(gè)表,可以理解為一個(gè)命名空間。當(dāng)集群模式時(shí),只有一個(gè)db0的概念,數(shù)據(jù)按照slot放置。
redis集群整體不可以支持事務(wù),每個(gè)節(jié)點(diǎn)可以。
客戶端如何定位數(shù)據(jù)?
在定位鍵值對(duì)數(shù)據(jù)時(shí),它所處的哈希槽是可以通過(guò)計(jì)算得到的,這個(gè)計(jì)算可以在客戶端發(fā)送請(qǐng)求時(shí)來(lái)執(zhí)行。但是,要進(jìn)一步定位到實(shí)例,還需要知道哈希槽分布在哪個(gè)實(shí)例上(即為查找的時(shí)候如果數(shù)據(jù)不在當(dāng)前節(jié)點(diǎn)下,會(huì)先給予數(shù)據(jù)所在的實(shí)例地址)。
實(shí)例如果知道數(shù)據(jù)分布在哪個(gè)實(shí)例地址
Redis 實(shí)例會(huì)把自己的哈希槽信息發(fā)給和它相連接的其它實(shí)例,來(lái)完成哈希槽分配信息的擴(kuò)散。當(dāng)實(shí)例之間相互連接后,每個(gè)實(shí)例就有所有哈希槽的映射關(guān)系了。
客戶端收到哈希槽信息后,會(huì)把哈希槽信息緩存在本地。當(dāng)客戶端請(qǐng)求鍵值對(duì)時(shí),會(huì)先計(jì)算鍵所對(duì)應(yīng)的哈希槽,然后就可以給相應(yīng)的實(shí)例發(fā)送請(qǐng)求了。
但是,在集群中,實(shí)例和哈希槽的對(duì)應(yīng)關(guān)系并不是一成不變的,最常見(jiàn)的變化有兩個(gè):
在集群中,實(shí)例有新增或刪除,Redis 需要重新分配哈希槽;
為了負(fù)載均衡,Redis 需要把哈希槽在所有實(shí)例上重新分布一遍。
實(shí)例之間還可以通過(guò)相互傳遞消息,獲得最新的哈希槽分配信息,但是,客戶端是無(wú)法主動(dòng)感知這些變化的怎么辦?
Redis Cluster 方案提供了一種重定向機(jī)制,所謂的“重定向”,就是指,客戶端給一個(gè)實(shí)例發(fā)送數(shù)據(jù)讀寫(xiě)操作時(shí),這個(gè)實(shí)例上并沒(méi)有相應(yīng)的數(shù)據(jù),客戶端要再給一個(gè)新實(shí)例發(fā)送操作命令。
GET hello:key
(error) MOVED 13320 172.16.19.5:6379
就是如果在客戶端此時(shí)請(qǐng)求哈希槽2的數(shù)據(jù),此時(shí)會(huì)到實(shí)例2,發(fā)現(xiàn)數(shù)據(jù)不在該實(shí)例下,會(huì)讓你重定向?qū)嵗?,并更新客戶端緩存。
哈希槽數(shù)據(jù)存在兩個(gè)實(shí)例怎么辦?
如果 Slot 2 中的數(shù)據(jù)比較多,就可能會(huì)出現(xiàn)一種情況:客戶端向?qū)嵗?2 發(fā)送請(qǐng)求,但此時(shí),Slot 2 中的數(shù)據(jù)只有一部分遷移到了實(shí)例 3,還有部分?jǐn)?shù)據(jù)沒(méi)有遷移。在這種遷移部分完成的情況下,客戶端就會(huì)收到一條 ASK 報(bào)錯(cuò)信息。
GET hello:key
(error) ASK 13320 172.16.19.5:6379
此時(shí),客戶端需要先給 172.16.19.5 這個(gè)實(shí)例發(fā)送一個(gè) ASKING 命令。這個(gè)命令的意思是,讓這個(gè)實(shí)例允許執(zhí)行客戶端接下來(lái)發(fā)送的命令。然后,客戶端再向這個(gè)實(shí)例發(fā)送 GET 命令,以讀取數(shù)據(jù)。
和 MOVED 命令不同,ASK 命令并不會(huì)更新客戶端緩存的哈希槽分配信息。所以,在上圖中,如果客戶端再次請(qǐng)求 Slot 2 中的數(shù)據(jù),它還是會(huì)給實(shí)例 2 發(fā)送請(qǐng)求。這也就是說(shuō),ASK 命令的作用只是讓客戶端能給新實(shí)例發(fā)送一次請(qǐng)求,而不像 MOVED 命令那樣,會(huì)更改本地緩存,讓后續(xù)所有命令都發(fā)往新實(shí)例。
Redis Cluster不采用把key直接映射到實(shí)例的方式,而采用哈希槽的方式原因:
1、整個(gè)集群存儲(chǔ)key的數(shù)量是無(wú)法預(yù)估的,key的數(shù)量非常多時(shí),直接記錄每個(gè)key對(duì)應(yīng)的實(shí)例映射關(guān)系,這個(gè)映射表會(huì)非常龐大,這個(gè)映射表無(wú)論是存儲(chǔ)在服務(wù)端還是客戶端都占用了非常大的內(nèi)存空間。
2、Redis Cluster采用無(wú)中心化的模式(無(wú)proxy,客戶端與服務(wù)端直連),客戶端在某個(gè)節(jié)點(diǎn)訪問(wèn)一個(gè)key,如果這個(gè)key不在這個(gè)節(jié)點(diǎn)上,這個(gè)節(jié)點(diǎn)需要有糾正客戶端路由到正確節(jié)點(diǎn)的能力(MOVED響應(yīng)),這就需要節(jié)點(diǎn)之間互相交換路由表,每個(gè)節(jié)點(diǎn)擁有整個(gè)集群完整的路由關(guān)系。如果存儲(chǔ)的都是key與實(shí)例的對(duì)應(yīng)關(guān)系,節(jié)點(diǎn)之間交換信息也會(huì)變得非常龐大,消耗過(guò)多的網(wǎng)絡(luò)資源,而且就算交換完成,相當(dāng)于每個(gè)節(jié)點(diǎn)都需要額外存儲(chǔ)其他節(jié)點(diǎn)的路由表,內(nèi)存占用過(guò)大造成資源浪費(fèi)。
3、當(dāng)集群在擴(kuò)容、縮容、數(shù)據(jù)均衡時(shí),節(jié)點(diǎn)之間會(huì)發(fā)生數(shù)據(jù)遷移,遷移時(shí)需要修改每個(gè)key的映射關(guān)系,維護(hù)成本高。
4、而在中間增加一層哈希槽,可以把數(shù)據(jù)和節(jié)點(diǎn)解耦,key通過(guò)Hash計(jì)算,只需要關(guān)心映射到了哪個(gè)哈希槽,然后再通過(guò)哈希槽和節(jié)點(diǎn)的映射表找到節(jié)點(diǎn),相當(dāng)于消耗了很少的CPU資源,不但讓數(shù)據(jù)分布更均勻,還可以讓這個(gè)映射表變得很小,利于客戶端和服務(wù)端保存,節(jié)點(diǎn)之間交換信息時(shí)也變得輕量。
5、當(dāng)集群在擴(kuò)容、縮容、數(shù)據(jù)均衡時(shí),節(jié)點(diǎn)之間的操作例如數(shù)據(jù)遷移,都以哈希槽為基本單位進(jìn)行操作,簡(jiǎn)化了節(jié)點(diǎn)擴(kuò)容、縮容的難度,便于集群的維護(hù)和管理。
Redis Cluster不采用一致性哈希算法的原因
一致性哈希算法:多增加一層虛擬映射層,數(shù)據(jù)與虛擬節(jié)點(diǎn)映射、虛擬節(jié)點(diǎn)與真實(shí)節(jié)點(diǎn)再映射。聽(tīng)起來(lái)和哈希槽很像。
但一致性哈希的ketama算法實(shí)現(xiàn)在擴(kuò)容或down的情況下,需要重新計(jì)算節(jié)點(diǎn),這對(duì)之前的分配可能會(huì)有一些影響。所以可以引入hash slot的方式,即某些hash slot區(qū)間對(duì)應(yīng)一臺(tái)機(jī)器,對(duì)于擴(kuò)容或down機(jī)情況下,就改變某個(gè)hash slot區(qū)間就可以了,改動(dòng)比較小,對(duì)之前分配的影響也較小。
虛擬桶是取模和一致性哈希二者的折中辦法。
采用固定節(jié)點(diǎn)數(shù)量,來(lái)避免取模的不靈活性。
采用可配置映射節(jié)點(diǎn),來(lái)避免一致性哈希的部分影響。
一致性哈希算法原理詳解
Hash Tag
Hash Tag 是指加在鍵值對(duì) key 中的一對(duì)花括號(hào){}
這對(duì)括號(hào)會(huì)把 key 的一部分括起來(lái),客戶端在計(jì)算 key 的 CRC16 值時(shí),只對(duì) Hash Tag 花括號(hào)中的 key 內(nèi)容進(jìn)行計(jì)算。如果沒(méi)用 Hash Tag 的話,客戶端計(jì)算整個(gè) key 的 CRC16 的值。
Hash Tag主要是用在 Redis Cluster中,集群本身并不支持跨實(shí)例的事務(wù)操作和范圍查詢。Hash Tag可以避開(kāi)這個(gè)缺點(diǎn)。不過(guò)要小心數(shù)據(jù)傾斜導(dǎo)致實(shí)例不穩(wěn)定。
摘抄:《Redis 核心技術(shù)與實(shí)戰(zhàn)》-第9節(jié)