基本目標(biāo)與設(shè)計基本思想
Redis cluster 目標(biāo)
- 高性能超燃,并且能線性擴(kuò)展到1000個節(jié)點(diǎn)倘待。不需要代理,使用異步復(fù)制,值沒有合并的操作
- 可接受的寫安全
- 可用性
實現(xiàn)的子集
Redis cluster 實現(xiàn)了所有的single key 操作乞而,對于multi key操作的話肠骆,這些key必須在一個節(jié)點(diǎn)上面尖坤,redis cluster 通過 hash tags決定key存貯在哪個slot上面溪窒。
client 與server 在集群中的角色
節(jié)點(diǎn)首要功能是存貯數(shù)據(jù),集群狀態(tài)徘熔,映射key到相應(yīng)的節(jié)點(diǎn)门躯。自動發(fā)現(xiàn)其他節(jié)點(diǎn),發(fā)現(xiàn)失敗節(jié)點(diǎn)酷师,讓從變?yōu)橹鳌?/p>
為了完成以上功能讶凉,cluster使用tcp和二進(jìn)制協(xié)議(Redis Cluster Bus),節(jié)點(diǎn)間互聯(lián).node 同時使用gossip協(xié)議傳播信息山孔,包括節(jié)點(diǎn)的發(fā)現(xiàn)懂讯,發(fā)送ping包,Pub/Sub信息台颠。
因為節(jié)點(diǎn)并不代理請求轉(zhuǎn)發(fā)褐望,會返回MOVED和ASk錯誤,clients就可以直連到其他節(jié)點(diǎn)串前。client理論上面可以給任意節(jié)點(diǎn)發(fā)送請求瘫里,如果需要就重定向。但實際應(yīng)用中client存貯一個從key到node的map來提高性能荡碾。
寫安全
Redis cluster 使用異步復(fù)制的模式谨读,故障轉(zhuǎn)移的時候,被選為主的節(jié)點(diǎn)坛吁,會用自己的數(shù)據(jù)去覆蓋其他副本節(jié)點(diǎn)的數(shù)據(jù)劳殖。所以總有一個時間口會丟失數(shù)據(jù)。
下面一個例子會丟失數(shù)據(jù):
一個寫請求到master節(jié)點(diǎn)拨脉,master返回成功給client,但還沒異步寫個副本的時候哆姻,這時候master死掉了,如果一定時間不恢復(fù)玫膀,從升為主節(jié)點(diǎn)矛缨,數(shù)據(jù)就永遠(yuǎn)丟了。
理論上面丟失數(shù)據(jù)還有下面一種情況
master partition 變得不可用
它的一個從變?yōu)橹?/p>
一定時間之后,這個主又可用了
客戶端這時候還使用舊的的路由劳景,在這個主變?yōu)閺闹坝颍瑢懻埱蟮竭_(dá)這個主。
3盟广、可用性
假設(shè)n個主節(jié)點(diǎn),每個主下面掛載一個從瓮钥,掛掉一個筋量,集群仍然可用。掛點(diǎn)兩個碉熄,可用性是1 -(1/(n2 -1))(第一個節(jié)點(diǎn)掛掉后桨武,還剩下n2-1個節(jié)點(diǎn)),只有一個節(jié)點(diǎn)的主掛掉的可能性是 1/n*2 -1)
replicas migration 使可用性更高
4锈津、性能
reids cluster 不代理請求到正確的節(jié)點(diǎn)呀酸,而是告訴客戶端正確的節(jié)點(diǎn)
client 會保存一份最新的key與node映射,一般情況琼梆,會直接訪問到正確的節(jié)點(diǎn)性誉。
異步寫副本
一般的操作和單臺redis有相同的性能,一個有n個主節(jié)點(diǎn)的集群性能接近n*單個redis
綜上 高性能 線性擴(kuò)展 合理的寫安全 高可用 是rediscluser 的主要目標(biāo)
為什么避免數(shù)據(jù)合并
因為首先redis 存貯的數(shù)據(jù)量會特別大茎杂,如果合并需要更大的空間
Redis Cluseter 主要組件
key 分布模式
key空間分布被劃分為16384個slot,所以一個集群错览,主節(jié)點(diǎn)的個數(shù)最大為16384(一般建議master最大節(jié)點(diǎn)數(shù)為1000)
HASH_SLOT = CRC16(key) mod 16384
Keys hash tags
hash tag 是為了保證不同的key,可以分布到同一個slot上面,來執(zhí)行multi-key的操作
hash tag的規(guī)則是以第一個{開始煌往,到第一個}結(jié)尾倾哺,中間的內(nèi)容,來做hash刽脖。
例子
{user1000}.following 與 {user1000}.followers user1000作為key
foo{}{bar} 整個key
{{bar}} {bar 為key
{bar}{zap} bar 為key
Ruby Example
def HASH_SLOT(key)
s = key.index "{"
if s
e = key.index "}",s+1
if e && e != s+1
key = key[s+1..e-1]
end
end
crc16(key) % 16384
end`
Cluster nodes 屬性
$ redis-cli cluster nodes
d1861060fe6a534d42d8a19aeb36600e18785e04 127.0.0.1:6379 myself - 0 1318428930 1 connected 0-1364
3886e65cc906bfd9b1f7e7bde468726a052d1dae 127.0.0.1:6380 master - 1318428930 1318428931 2 connected 1365-2729
d289c575dcbc4bdd2931585fd4339089e461a27d 127.0.0.1:6381 master - 1318428931 1318428931 3 connected 2730-4095
從左到右依次為:node id, address:port, flags, last ping sent, last pong received, configuration epoch, link state, slots
其中node id是第一次啟動獲得的一個160字節(jié)的隨機(jī)字符串羞海,并把id保存在配置文件中,一直不會再變
Cluster bus
每個節(jié)點(diǎn)有一個額外的TCP端口曲管,這個端口用來和其他節(jié)點(diǎn)交換信息却邓。這個端口一般是在與客戶端鏈接端口上面加10000,比如客戶端端口為6379翘地,那么cluster bus的端口為16379.
node-to-node 交流是通過cluster bus與 cluster bus protocol進(jìn)行申尤。其中cluster bus protocol 是一個二進(jìn)制協(xié)議,因為官方不建議其他應(yīng)用與redis 節(jié)點(diǎn)進(jìn)行通信衙耕,所以沒有公開的文檔昧穿,要查看的話只能去看源碼。
cluster 拓?fù)?/h3>
Redis cluster 是一個網(wǎng)狀的橙喘,每一個節(jié)點(diǎn)通過tcp與其他每個節(jié)點(diǎn)連接时鸵。假如n個節(jié)點(diǎn)的集群,每個節(jié)點(diǎn)有n-1個出的鏈接,n-1個進(jìn)的鏈接饰潜。這些鏈接會一直存活初坠。假如一個節(jié)點(diǎn)發(fā)送了一個ping,很就沒收到pong,但還沒到時間把這個節(jié)點(diǎn)設(shè)為 unreachable,就會通過重連刷新鏈接彭雾。
Nodes handshake
node 會在cluster bus端口一直接受連接碟刺,回復(fù)ping,即使這個ping 的node是不可信的。但是其他的包會被丟掉薯酝,如果發(fā)送者不是cluster 一員半沽。
一個node有兩種方式接受其他其他node作為集群一員
-
如果一個節(jié)點(diǎn)發(fā)送MEET信息(METT 類似ping,但是強(qiáng)迫接受者,把它作為集群一員)吴菠。一個節(jié)點(diǎn)發(fā)送MEET信息者填,只有管理員通過命令行,運(yùn)行如下命令
CLUSTER MEET ip port
如果這個節(jié)點(diǎn)已經(jīng)被一個節(jié)點(diǎn)信任做葵,那么也會被其他節(jié)點(diǎn)信任占哟。比如A 知道B,B知道C,B會發(fā)送gossip信息給A關(guān)于C的信息。A就會認(rèn)為C是集群一員酿矢,并與其建立連接榨乎。
這樣只要我們把節(jié)點(diǎn)加入到一個節(jié)點(diǎn),就會自動被其他節(jié)點(diǎn)自動發(fā)現(xiàn)棠涮。
Redirection and resharding
MOVED Redirection
客戶端可以自由的連接任何一個node,如果這個node 不能處理會返回一個MOVED的錯誤谬哀,類似下面這樣
GET x
-MOVED 3999 127.0.0.1:6381
描述了key 的hash slot,屬于哪個node
client 會維護(hù)一個hash slots到IP:port的映射
當(dāng)收到moved錯誤的時候,可以通過CLUSTER NODES或者CLUSTER SLOTS去刷新一遍整個client
Cluster live reconfiguration
cluster 支持運(yùn)行狀態(tài)下添加和刪除節(jié)點(diǎn)严肪。添加刪除節(jié)點(diǎn)抽象:把一部分hash slot從一個節(jié)點(diǎn)移動到另一個節(jié)點(diǎn)史煎。
- 添加一個新節(jié)點(diǎn),hash slot 從一些節(jié)點(diǎn)移動到這個新節(jié)點(diǎn)
- 移除一個節(jié)點(diǎn)驳糯,把這個節(jié)點(diǎn)的hash slot 移動到其他的節(jié)點(diǎn)
- rebalance ,部分hash slot在節(jié)點(diǎn)間移動
所以篇梭,動態(tài)擴(kuò)容的核心就是在節(jié)點(diǎn)之間移動hash slot,hash slot 又是key的集合。所以reshare 就是把key從一個節(jié)點(diǎn)移動到其他節(jié)點(diǎn)酝枢。
redis 提供如下命令:
- CLUSTER ADDSLOTS slot1 [slot2] ... [slotN]
- CLUSTER DELSLOTS slot1 [slot2] ... [slotN]
- CLUSTER SETSLOT slot NODE node
- CLUSTER SETSLOT slot MIGRATING node
- CLUSTER SETSLOT slot IMPORTING node
前兩個指令:ADDSLOTS和DELSLOTS恬偷,用于向當(dāng)前node分配或者移除slots,指令可以接受多個slot值帘睦。分配slots的意思是告知指定的master(即此指令需要在某個master節(jié)點(diǎn)執(zhí)行)此后由它接管相應(yīng)slots的服務(wù)袍患;slots分配后,這些信息將會通過gossip發(fā)給集群的其他nodes竣付。
ADDSLOTS指令通常在創(chuàng)建一個新的Cluster時使用诡延,一個新的Cluster有多個空的Masters構(gòu)成,此后管理員需要手動為每個master分配slots古胆,并將16384個slots分配完畢肆良,集群才能正常服務(wù)筛璧。簡而言之,ADDSLOTS只能操作那些尚未分配的(即不被任何nodes持有)slots惹恃,我們通常在創(chuàng)建新的集群或者修復(fù)一個broken的集群(集群中某些slots因為nodes的永久失效而丟失)時使用夭谤。為了避免出錯,Redis Cluster提供了一個redis-trib輔助工具巫糙,方便我們做這些事情朗儒。
DELSLOTS就是將指定的slots刪除,前提是這些slots必須在當(dāng)前node上参淹,被刪除的slots處于“未分配”狀態(tài)(當(dāng)然其對應(yīng)的keys數(shù)據(jù)也被clear)采蚀,即尚未被任何nodes覆蓋,這種情況可能導(dǎo)致集群處于不可用狀態(tài)承二,此指令通常用于debug,在實際環(huán)境中很少使用纲爸。那些被刪除的slots亥鸠,可以通過ADDSLOTS重新分配。
SETSLOT是個很重要的指令识啦,對集群slots進(jìn)行reshard的最重要手段负蚊;它用來將單個slot在兩個nodes間遷移。根據(jù)slot的操作方式颓哮,它有兩種狀態(tài)“MIGRATING”家妆、“IMPORTING”
1)MIGRATING:將slot的狀態(tài)設(shè)置為“MIGRATING”,并遷移到destination-node上冕茅,需要注意當(dāng)前node必須是slot的持有者伤极。在遷移期間,Client的查詢操作仍在當(dāng)前node上執(zhí)行姨伤,如果key不存在哨坪,則會向Client反饋“-ASK”重定向信息,此后Client將會把請求重新提交給遷移的目標(biāo)node乍楚。
2)IMPORTING:將slot的狀態(tài)設(shè)置為“IMPORTING”当编,并將其從source-node遷移到當(dāng)前node上,前提是source-node必須是slot的持有者徒溪。Client交互機(jī)制同上忿偷。
假如我們有兩個節(jié)點(diǎn)A、B臊泌,其中slot 8在A上鲤桥,我們希望將8從A遷移到B,可以使用如下方式:
1)在B上:CLUSTER SETSLOT 8 IMPORTING A
2)在A上:CLUSTER SETSLOT 8 MIGRATING B
在遷移期間缺虐,集群中其他的nodes的集群信息不會改變芜壁,即slot 8仍對應(yīng)A,即此期間,Client查詢?nèi)栽贏上:
1)如果key在A上存在慧妄,則有A執(zhí)行顷牌。
2)否則,將向客戶端返回ASK塞淹,客戶端將請求重定向到B窟蓝。
這種方式下,新key的創(chuàng)建就不會在A上執(zhí)行饱普,而是在B上執(zhí)行运挫,這也就是ASK重定向的原因(遷移之前的keys在A,遷移期間created的keys在B上)套耕;當(dāng)上述SET SLOT執(zhí)行完畢后谁帕,slot的狀態(tài)也會被自動清除,同時將slot遷移信息傳播給其他nodes冯袍,至此集群中slot的映射關(guān)系將會變更匈挖,此后slot 8的數(shù)據(jù)請求將會直接提交到B上。
動態(tài)分片的步驟:
- 在目標(biāo)節(jié)點(diǎn)設(shè)置為 SETSLOT <slot> IMPORTING <source-node-id>.
- 在原節(jié)點(diǎn) SETSLOT <slot> MIGRATING <destination-node-id>.
- CLUSTER GETKEYSINSLOT 獲得所有的key ,使用MIGRATE 從原節(jié)點(diǎn)遷移到目標(biāo)節(jié)點(diǎn)
- 在原節(jié)點(diǎn)或者目標(biāo)節(jié)點(diǎn) CLUSTER SETSLOT <slot> NODE <destination-node-id>
ASK重定向
在上文中康愤,我們已經(jīng)介紹了MOVED重定向儡循,ASK與其非常相似。在resharding期間征冷,為什么不能用MOVED择膝?MOVED意思為hash slots已經(jīng)永久被另一個node接管、接下來的相應(yīng)的查詢應(yīng)該與它交互检激,ASK的意思是當(dāng)前query暫時與指定的node交互肴捉;在遷移期間,slot 8的keys有可能仍在A上呵扛,所以Client的請求仍然需要首先經(jīng)由A每庆,對于A上不存在的,我們才需要到B上進(jìn)行嘗試今穿。遷移期間缤灵,Redis Cluster并沒有粗暴的將slot 8的請求全部阻塞、直到遷移結(jié)束蓝晒,這種方式盡管不再需要ASK腮出,但是會影響集群的可用性。
1)當(dāng)Client接收到ASK重定向芝薇,它僅僅將當(dāng)前query重定向到指定的node胚嘲;此后的請求仍然交付給舊的節(jié)點(diǎn)。
2)客戶端并不會更新本地的slots映射洛二,仍然保持slot 8與A的映射馋劈;直到集群遷移完畢攻锰,且遇到MOVED重定向。
一旦slot 8遷移完畢之后(集群的映射信息也已更新)妓雾,如果Client再次在A上訪問slot 8時娶吞,將會得到MOVED重定向信息,此后客戶端也更新本地的集群映射信息械姻。
客戶端首次鏈接以及重定向處理
可能有些Cluster客戶端的實現(xiàn)妒蛇,不會在內(nèi)存中保存slots映射關(guān)系(即nodes與slots的關(guān)系),每次請求都從聲明的楷拳、已知的nodes中绣夺,隨機(jī)訪問一個node,并根據(jù)重定向(MOVED)信息來尋找合適的node欢揖,這種訪問模式陶耍,通常是非常低效的。
當(dāng)然她混,Client應(yīng)該盡可能的將slots配置信息緩存在本地物臂,不過配置信息也不需要絕對的實時更新,因為在請求時偶爾出現(xiàn)“重定向”产上,Client也能兼容此次請求的正確轉(zhuǎn)發(fā),此時再更新slots配置蛾狗。(所以Client通常不需要間歇性的檢測Cluster中配置信息是否已經(jīng)更新)客戶端通常是全量更新slots配置:
- 首次鏈接到集群的某個節(jié)點(diǎn)
- 當(dāng)遇到MOVED重定向消息時
遇到MOVED時晋涣,客戶端僅僅更新特定的slot是不夠的,因為集群中的reshard通常會影響到多個slots沉桌⌒蝗担客戶端通過向任意一個nodes發(fā)送“CLUSTER NODES”或者“CLUSTER SLOTS”指令均可以獲得當(dāng)前集群最新的slots映射信息;“CLUSTER SLOTS”指令返回的信息更易于Client解析留凭。
slaves擴(kuò)展reads請求
通常情況下佃扼,read、write請求都將有持有slots的master節(jié)點(diǎn)處理蔼夜;因為redis的slaves可以支持read操作(前提是application能夠容忍stale數(shù)據(jù))兼耀,所以客戶端可以使用“READONLY”指令來擴(kuò)展read請求。
“READONLY”表明其可以訪問集群的slaves節(jié)點(diǎn)求冷,能夠容忍stale數(shù)據(jù)瘤运,而且此次鏈接不會執(zhí)行writes操作。當(dāng)鏈接設(shè)定為readonly模式后匠题,Cluster只有當(dāng)keys不被slave的master節(jié)點(diǎn)持有時才會發(fā)送重定向消息(即Client的read請求總是發(fā)給slave拯坟,只有當(dāng)此slave的master不持有slots時才會重定向,很好理解):
1)此slave的master節(jié)點(diǎn)不持有相應(yīng)的slots
2)集群重新配置韭山,比如reshard或者slave遷移到了其他master上郁季,此slave本身也不再支持此slot冷溃。
容錯
心跳與gossip消息
集群中的nodes持續(xù)的交換ping、pong數(shù)據(jù)梦裂,這兩種數(shù)據(jù)包的結(jié)構(gòu)一樣似枕,同樣都攜帶集群的配置信息,唯一不同的就是message中的type字段塞琼。
通常菠净,一個node發(fā)送ping消息,那么接收者將會反饋pong消息彪杉;不過有時候并非如此毅往,比如當(dāng)集群中添加新的node時,接收者會將pong信息發(fā)給其他的nodes派近,而不是直接反饋給發(fā)送者攀唯。這樣的好處是會將配置盡快的在cluster傳播。
通常一個node每秒都會隨機(jī)向幾個nodes發(fā)送ping渴丸,所以無論集群規(guī)模多大侯嘀,每個nodes發(fā)送的ping數(shù)據(jù)包的總量是恒定的。每個node都確保盡可能半個NODE_TIMEOUT時間內(nèi)谱轨,向那些尚未發(fā)送過ping或者未接收到它們的pong消息的nodes發(fā)送ping戒幔。在NODE_TIMEOUT逾期之前,nodes也會嘗試與那些通訊異常的nodes重新建立TCP鏈接土童,確保不能僅僅因為當(dāng)前鏈接異常而認(rèn)為它們就是不可達(dá)的诗茎。
當(dāng)NODE_TIMEOUT值較小、集群中nodes規(guī)模較大時献汗,那么全局交換的信息量也會非常龐大敢订,因為每個node都盡力在半個NODE_TIMEOUT時間內(nèi),向其他nodes發(fā)送ping罢吃。比如有100個nodes楚午,NODE_TIMEOUT為60秒,那么每個node在30秒內(nèi)向其他99各nodes發(fā)送ping尿招,平均每秒3.3個消息矾柜,那么整個集群全局就是每秒330個消息。這些消息量就谜,并不會對集群的帶寬帶來不良問題把沼。
心跳包的內(nèi)容
心跳數(shù)據(jù)包的內(nèi)容
- node ID
- currentEpoch和configEpoch
- node flags:比如表示此node是maste、slave等
- hash slots:發(fā)送者持有的slots
- TCP port
- state (down or ok)
- master node ID (如果是從節(jié)點(diǎn))
ping和pong數(shù)據(jù)包中也包含gossip部分吁伺,這部分信息告訴接受者饮睬,當(dāng)前節(jié)點(diǎn)持有其他節(jié)點(diǎn)的狀態(tài),不過它只包含sender已知的隨機(jī)幾個nodes篮奄,nodes的數(shù)量根據(jù)集群規(guī)模的大小按比例計算捆愁。
gossip部分包含了
- Node ID
- IP and port of the node
- Node flags
失敗檢測
集群失效檢測就是割去,當(dāng)某個master或者slave不能被大多數(shù)nodes可達(dá)時,用于故障遷移并將合適的slave提升為master昼丑。當(dāng)slave提升未能有效實施時呻逆,集群將處于error狀態(tài)且停止接收Client端查詢。
每個node持有其已知nodes的列表包括flags菩帝,有2個flag狀態(tài):PFAIL和FAIL咖城;PFAIL表示“可能失效”,是一種尚未完全確認(rèn)的失效狀態(tài)(即某個節(jié)點(diǎn)或者少數(shù)masters認(rèn)為其不可達(dá))呼奢。FAIL表示此node已經(jīng)被集群大多數(shù)masters判定為失效(大多數(shù)master已認(rèn)定為不可達(dá)宜雀,且不可達(dá)時間已達(dá)到設(shè)定值,需要failover)握础。
nodes的ID辐董、ip+port、flags禀综,那么接收者將根據(jù)sender的視圖简烘,來判定節(jié)點(diǎn)的狀態(tài),這對故障檢測定枷、節(jié)點(diǎn)自動發(fā)現(xiàn)非常有用孤澎。
PFAIL flag:
當(dāng)node不可達(dá)的時間超過NODE_TIMEOUT,這個節(jié)點(diǎn)就被標(biāo)記為PFAIL(Possible failure),master和slave都可以標(biāo)記其他節(jié)點(diǎn)為PFAIL欠窒。所謂不可達(dá)亥至,就是當(dāng)“active ping”(發(fā)送ping且能受到pong)尚未成功的時間超過NODE_TIMEOUT,因此我們設(shè)定的NODE_TIMEOUT的值應(yīng)該比網(wǎng)絡(luò)交互往返的時間延遲要大一些(通常要大的多贱迟,以至于交互往返時間可以忽略)。為了避免誤判絮供,當(dāng)一個node在半個NODE_TIMEOUT時間內(nèi)仍未能pong衣吠,那么當(dāng)前node將會盡力嘗試重新建立連接進(jìn)行重試,以排除pong未能接收