《Redis Cluster探索與思考》要點(diǎn):
本文介紹了Redis Cluster探索與思考,希望對您有用。如果有疑問,可以聯(lián)系我們。
責(zé)編 | 仲培藝
Redis Cluster的基來源根基理和架構(gòu)
Redis Cluster是分布式Redis的實(shí)現(xiàn).隨著Redis版本的更替,以及各種已知bug的fixed,在穩(wěn)定性和高可用性上有了很大的提升和進(jìn)步,越來越多的企業(yè)將Redis Cluster實(shí)際應(yīng)用到線上業(yè)務(wù)中,通過從社區(qū)獲取到反饋社區(qū)的迭代,為Redis Cluster成為一個可靠的企業(yè)級開源產(chǎn)品,在簡化業(yè)務(wù)架構(gòu)和業(yè)務(wù)邏輯方面都起著積極重要的作用.下面從Redis Cluster的基來源根基理為起點(diǎn)開啟Redis Cluster在業(yè)界的分析與思考之旅.
Redis Cluster的基來源根基理可以從數(shù)據(jù)分片、數(shù)據(jù)遷移、集群通訊、故障檢測以及故障轉(zhuǎn)移等方面進(jìn)行了解,Cluster相關(guān)的代碼也不是很多,注釋也很詳細(xì),可自行查看,地址是:https://github.com/antirez/redis/blob/unstable/src/cluster.c.這里由于篇幅的原因,主要從數(shù)據(jù)分片和數(shù)據(jù)遷移兩方面進(jìn)行詳細(xì)介紹:
數(shù)據(jù)分片
Redis Cluster在設(shè)計中沒有使用一致性哈希(Consistency Hashing),而是使用數(shù)據(jù)分片(Sharding)引入哈希槽(hash slot)來實(shí)現(xiàn);一個 Redis Cluster包括16384(0~16383)個哈希槽,存儲在Redis Cluster中的所有鍵都會被映射到這些slot中,集群中的每個鍵都屬于這16384個哈希槽中的一個,集群使用公式slot=CRC16(key)/16384來計算key屬于哪個槽,其中CRC16(key)語句用于計算key的CRC16 校驗(yàn)和.
集群中的每個主節(jié)點(diǎn)(Master)都負(fù)責(zé)處理16384個哈希槽中的一部分,當(dāng)集群處于穩(wěn)定狀態(tài)時,每個哈希槽都只由一個主節(jié)點(diǎn)進(jìn)行處理,每個主節(jié)點(diǎn)可以有一個到N個從節(jié)點(diǎn)(Slave),當(dāng)主節(jié)點(diǎn)出現(xiàn)宕機(jī)或網(wǎng)絡(luò)斷線等弗成用時,從節(jié)點(diǎn)能自動提升為主節(jié)點(diǎn)進(jìn)行處理.
如圖1,ClusterNode數(shù)據(jù)結(jié)構(gòu)中的slots和numslots屬性記錄了節(jié)點(diǎn)負(fù)責(zé)處理哪些槽.其中,slot屬性是一個二進(jìn)制位數(shù)組(bitarray),其長度為16384/8=2048 Byte,共包括16384個二進(jìn)制位.集群中的Master節(jié)點(diǎn)用bit(0和1)來標(biāo)識對于某個槽是否擁有.比如,對于編號為1的槽,Master只要判斷序列第二位(索引從0開始)的值是不是1即可,時間復(fù)雜度為O(1).
圖1 ClusterNode數(shù)據(jù)布局
集群中所有槽的分配信息都保存在ClusterState數(shù)據(jù)結(jié)構(gòu)的slots數(shù)組中,程序要檢查槽i是否已經(jīng)被分配或者找出處理槽i的節(jié)點(diǎn),只需要拜訪clusterState.slots[i]的值即可,復(fù)雜度也為O(1).ClusterState數(shù)據(jù)結(jié)構(gòu)如圖2所示.
圖2 ClusterState數(shù)據(jù)布局
查找關(guān)系如圖3所示.
圖3 查找關(guān)系圖
數(shù)據(jù)遷移
數(shù)據(jù)遷移可以理解為slot和key的遷移,這個功能很重要,極大地便利了集群做線性擴(kuò)展,以及實(shí)現(xiàn)平滑的擴(kuò)容或縮容.那么它是一個怎樣的實(shí)現(xiàn)過程?下面舉個例子:現(xiàn)在要將Master A節(jié)點(diǎn)中編號為1、2、3的slot遷移到Master B節(jié)點(diǎn)中,在slot遷移的中間狀態(tài)下,slot 1、2、3在Master A節(jié)點(diǎn)的狀態(tài)表現(xiàn)為MIGRATING,在Master B節(jié)點(diǎn)的狀態(tài)表現(xiàn)為IMPORTING.
MIGRATING狀態(tài)
這個狀態(tài)如圖4所示是被遷移slot在當(dāng)前所在Master A節(jié)點(diǎn)中出現(xiàn)的一種狀態(tài),預(yù)備遷移slot從Mater A到Master B的時候,被遷移slot的狀態(tài)首先變?yōu)镸IGRATING狀態(tài),當(dāng)客戶端哀求的某個key所屬的slot的狀態(tài)處于MIGRATING狀態(tài)時,會出現(xiàn)以下幾種情況:
圖4 slot遷移的中間狀態(tài)
如果key存在則勝利處理.
如果key不存在,則返回客戶端ASK,客戶端根據(jù)ASK首先發(fā)送ASKING命令到目標(biāo)節(jié)點(diǎn),然后發(fā)送哀求的命令到目標(biāo)節(jié)點(diǎn).
當(dāng)key包括多個命令時:
如果都存在則勝利處理
如果都不存在,則返回客戶端ASK
如果一部分存在,則返回客戶端TRYAGAIN,通知客戶端稍后重試,這樣當(dāng)所有的key都遷移完畢,客戶端重試哀求時會得到ASK,然后經(jīng)過一次重定向就可以獲取這批鍵
此時并不刷新客戶端中node的映射關(guān)系
IMPORTING狀態(tài)
這個狀態(tài)如圖2所示是被遷移slot在目標(biāo)Master B節(jié)點(diǎn)中出現(xiàn)的一種狀態(tài),預(yù)備遷移slot從Mater A到Master B的時候,被遷移slot的狀態(tài)首先變?yōu)镮MPORTING狀態(tài).在這種狀態(tài)下的slot對客戶端的哀求可能會有下面幾種影響:
如果key不存在則新建.
如果key不在該節(jié)點(diǎn)上,命令會被MOVED重定向,刷新客戶端中node的映射關(guān)系.
如果是ASKING命令則命令會被執(zhí)行,從而key沒在被遷移的節(jié)點(diǎn),已經(jīng)被遷移到目標(biāo)節(jié)點(diǎn)的情況命令可以被順利執(zhí)行.
鍵空間遷移
這是完成數(shù)據(jù)遷移的重要一步,鍵空間遷移是指當(dāng)滿足了slot遷移前提的情況下,通過相關(guān)命令將slot 1、2、3中的鍵空間從Master A節(jié)點(diǎn)轉(zhuǎn)移到Master B節(jié)點(diǎn),這個過程由MIGRATE命令經(jīng)過3步真正完成數(shù)據(jù)轉(zhuǎn)移.步調(diào)示意如圖5.
圖5 表空間遷移步調(diào)
顛末上面三步可以完成鍵空間數(shù)據(jù)遷移,然后再將處于MIGRATING和IMPORTING狀態(tài)的槽變?yōu)槌B(tài)即可,從而完成整個重新分片的過程.
實(shí)現(xiàn)細(xì)節(jié):
Redis Cluster中節(jié)點(diǎn)負(fù)責(zé)存儲數(shù)據(jù),記錄集群狀態(tài),集群節(jié)點(diǎn)能自動發(fā)現(xiàn)其他節(jié)點(diǎn),檢測出節(jié)點(diǎn)的狀態(tài),并在必要時剔除故障節(jié)點(diǎn),提升新的主節(jié)點(diǎn).
Redis Cluster中所有節(jié)點(diǎn)通過PING-PONG機(jī)制彼此互聯(lián),使用一個二級制協(xié)議(Cluster Bus) 進(jìn)行通信,優(yōu)化傳輸速度和帶寬.發(fā)現(xiàn)新的節(jié)點(diǎn)、發(fā)送PING包、特定情況下發(fā)送集群消息,集群連接能夠宣布與訂閱消息.
客戶端和集群中的節(jié)點(diǎn)直連,不需要中間的Proxy層.理論上而言,客戶端可以自由地向集群中的所有節(jié)點(diǎn)發(fā)送哀求,但是每次不需要連接集群中的所有節(jié)點(diǎn),只需要連接集群中任何一個可用節(jié)點(diǎn)即可.當(dāng)客戶端發(fā)起哀求后,接收到重定向(MOVED\ASK)錯誤,會自動重定向到其他節(jié)點(diǎn),所以客戶端無需保存集群狀態(tài).不過客戶端可以緩存鍵值和節(jié)點(diǎn)之間的映射關(guān)系,這樣能明顯提高命令執(zhí)行的效率.
Redis Cluster中節(jié)點(diǎn)之間使用異步復(fù)制,在分區(qū)過程中存在窗口,容易導(dǎo)致丟失寫入的數(shù)據(jù),集群即使努力測驗(yàn)考試所有寫入,但是以下兩種情況可能丟失數(shù)據(jù):
命令操作已經(jīng)到達(dá)主節(jié)點(diǎn),但在主節(jié)點(diǎn)回復(fù)的時候,寫入可能還沒有通過主節(jié)點(diǎn)復(fù)制到從節(jié)點(diǎn)那里.如果這時主節(jié)點(diǎn)宕機(jī)了,這條命令將永久丟失.以防主節(jié)點(diǎn)長時間弗成達(dá)而它的一個從節(jié)點(diǎn)已經(jīng)被提升為主節(jié)點(diǎn).
分區(qū)導(dǎo)致一個主節(jié)點(diǎn)不可達(dá),然而集群發(fā)送故障轉(zhuǎn)移(failover),提升從節(jié)點(diǎn)為主節(jié)點(diǎn),本來的主節(jié)點(diǎn)再次恢復(fù).一個沒有更新路由表(routing table)的客戶端或許會在集群把這個主節(jié)點(diǎn)變成一個從節(jié)點(diǎn)(新主節(jié)點(diǎn)的從節(jié)點(diǎn))之前對它進(jìn)行寫入操作,導(dǎo)致數(shù)據(jù)徹底丟失.
Redis集群的節(jié)點(diǎn)不可用后,在經(jīng)過集群折半以上Master節(jié)點(diǎn)與故障節(jié)點(diǎn)通信超過cluster-node-timeout時間后,認(rèn)為該節(jié)點(diǎn)故障,從而集群根據(jù)自動故障機(jī)制,將從節(jié)點(diǎn)提升為主節(jié)點(diǎn).這時集群恢復(fù)可用.
Redis Cluster的優(yōu)勢和不敷
1. 無中心架構(gòu).
2. 數(shù)據(jù)依照slot存儲分布在多個節(jié)點(diǎn),節(jié)點(diǎn)間數(shù)據(jù)共享,可動態(tài)調(diào)整數(shù)據(jù)分布.
3. 可擴(kuò)展性,可線性擴(kuò)展到1000個節(jié)點(diǎn),節(jié)點(diǎn)可動態(tài)添加或刪除.
4. 高可用性,部分節(jié)點(diǎn)弗成用時,集群仍可用.通過增加Slave做standby數(shù)據(jù)副本,能夠?qū)崿F(xiàn)故障自動failover,節(jié)點(diǎn)之間通過gossip協(xié)議交換狀態(tài)信息,用投票機(jī)制完成Slave到Master的角色提升.
5. 降低運(yùn)維本錢,提高系統(tǒng)的擴(kuò)展性和可用性.
1. Client實(shí)現(xiàn)復(fù)雜,驅(qū)動要求實(shí)現(xiàn)Smart Client,緩存slots mapping信息并及時更新,提高了開發(fā)難度,客戶端的不成熟影響業(yè)務(wù)的穩(wěn)定性.目前僅JedisCluster相對成熟,異常處理部分還不完善,好比常見的“max redirect exception”.
2. 節(jié)點(diǎn)會因?yàn)槟承┰虬l(fā)生阻塞(阻塞時間大于clutser-node-timeout),被判斷下線,這種failover是沒有需要的.
3. 數(shù)據(jù)通過異步復(fù)制,不保證數(shù)據(jù)的強(qiáng)一致性.
4. 多個業(yè)務(wù)使用同一套集群時,無法根據(jù)統(tǒng)計區(qū)分冷熱數(shù)據(jù),資源隔離性較差,容易呈現(xiàn)相互影響的情況.
5. Slave在集群中充當(dāng)“冷備”,不克不及緩解讀壓力,當(dāng)然可以通過SDK的合理設(shè)計來提高Slave資源的利用率.
Redis Cluster在業(yè)界有哪些探索
通過調(diào)研了解,目前業(yè)界使用Redis Cluster年夜致可以總結(jié)為4類:
直連型,又可以稱之為經(jīng)典型或者傳統(tǒng)型,是官方的默認(rèn)使用方式,架構(gòu)圖見圖6.這種使用方式的優(yōu)缺點(diǎn)在上面的介紹中已經(jīng)有所說明,這里不再過多重復(fù)贅述.但值得一提的是,這種方式使用Redis Cluster需要依賴Smart Client,諸如連接維護(hù)、緩存路由表、MultiOp和Pipeline的支持都需要在Client上實(shí)現(xiàn),而且很多語言的Client目前都還是沒有的(關(guān)于Clients的更多介紹請參考https://redis.io/clients).雖然Client能夠進(jìn)行定制化,但有必定的開發(fā)難度,客戶端的不成熟將直接影響到線上業(yè)務(wù)的穩(wěn)定性.
圖6 Redis Cluster架構(gòu)
在Redis Cluster還沒有那么穩(wěn)定的時候,很多公司都已經(jīng)開始探索分布式Redis的實(shí)現(xiàn)了,好比有基于Twemproxy或者Codis的實(shí)現(xiàn),下面舉一個唯品會基于Twemproxy架構(gòu)的例子(不少公司分布式Redis的集群架構(gòu)都經(jīng)歷過這個階段),如圖7所示.
圖7 Redis基于Twemproxy的架構(gòu)實(shí)現(xiàn)
這種架構(gòu)的優(yōu)點(diǎn)和缺點(diǎn)也比擬明顯.
長處:
1. 后端Sharding邏輯對業(yè)務(wù)透明,業(yè)務(wù)方的讀寫方式和操作單個Redis一致;
2. 可以作為Cache和Storage的Proxy,Proxy的邏輯和Redis資源層的邏輯是隔離的;
3. Proxy層可以用來兼容那些目前還不支持的Clients.
缺點(diǎn):
1. 結(jié)構(gòu)復(fù)雜,運(yùn)維本錢高;
2. 可擴(kuò)展性差,進(jìn)行擴(kuò)縮容都必要手動干預(yù);
3. failover邏輯需要自己實(shí)現(xiàn),其自己不能支持故障的自動轉(zhuǎn)移;
4. Proxy層多了一次轉(zhuǎn)發(fā),性能有所損耗.
正是因此,我們知道Redis Cluster和基于Twemproxy布局使用中各自的優(yōu)缺點(diǎn),于是就出現(xiàn)了下面的這種架構(gòu),糅合了二者的優(yōu)點(diǎn),盡量規(guī)避二者的缺點(diǎn),架構(gòu)如圖8.
圖8 Smart Proxy計劃架構(gòu)
目前業(yè)界Smart Proxy的方案了解到的有基于Nginx Proxy和自研的,自研的如餓了么開源部分功能的Corvus,優(yōu)酷土豆是則通過Nginx來實(shí)現(xiàn),滴滴也在展開基于這種方式的探索.選用Nginx Proxy主要是考慮到Nginx的高性能,包含異步非阻塞處理方式、高效的內(nèi)存管理、和Redis一樣都是基于epoll事件驅(qū)動模式等優(yōu)點(diǎn).優(yōu)酷土豆的Redis服務(wù)化就是采用這種結(jié)構(gòu).
長處:
1. 提供一套HTTP Restful接口,隔離底層資源,對客戶端完全透明,跨語言挪用變得簡單;
2. 升級維護(hù)較為容易,維護(hù)Redis Cluster,只需平滑升級Proxy;
3. 條理化存儲,底層存儲做冷熱異構(gòu)存儲;
4. 權(quán)限控制,Proxy可以通過密鑰管理白名單,把一些不合法的哀求都過濾掉,并且也可以對用戶哀求的超大value進(jìn)行控制和過濾;
5. 平安性,可以屏蔽掉一些危險命令,比如keys *、save、flushall等,當(dāng)然這些也可以在Redis上進(jìn)行設(shè)置;
6. 資源邏輯隔離,根據(jù)分歧用戶的key加上前綴,來實(shí)現(xiàn)動態(tài)路由和資源隔離;
7. 監(jiān)控埋點(diǎn),對于分歧的接口進(jìn)行埋點(diǎn)監(jiān)控.
缺點(diǎn):
1. Proxy層做了一次轉(zhuǎn)發(fā),性能有所損耗;
2. 增加了運(yùn)維成本和管理成本,需要對架構(gòu)和Nginx Proxy的實(shí)現(xiàn)細(xì)節(jié)足夠了解,因?yàn)镹ginx Proxy在批量接口調(diào)用高并發(fā)下可能會瞬間向Redis Cluster發(fā)起幾百甚至上千的協(xié)程去拜訪,導(dǎo)致Redis的連接數(shù)或系統(tǒng)負(fù)載的不穩(wěn)定,進(jìn)而影響集群整體的穩(wěn)定性.
這種類型典型的案例就是企業(yè)級的PaaS產(chǎn)品,如亞馬遜和阿里云提供的Redis Cluster服務(wù),用戶無需知道內(nèi)部的實(shí)現(xiàn)細(xì)節(jié),只管使用即可,降低了運(yùn)維和開發(fā)成本.當(dāng)然也有開源的產(chǎn)品,國內(nèi)如搜狐的CacheCloud,它提供一個Redis云管理平臺,實(shí)現(xiàn)多種類型(Redis Standalone、Redis Sentinel、Redis Cluster)自動部署,辦理Redis實(shí)例碎片化現(xiàn)象,提供完善統(tǒng)計、監(jiān)控、運(yùn)維功能,減少開發(fā)人員的運(yùn)維成本和誤操作,提高機(jī)器的利用率,提供靈活的伸縮性,提供方便的接入客戶端,更多細(xì)節(jié)請參考:https://cachecloud.github.io.盡管這還不錯,如果是一個新業(yè)務(wù),到可以嘗試一下,但若對于一個穩(wěn)定的業(yè)務(wù)而言,要遷移到CacheCloud上則需要謹(jǐn)慎.如果對分布式框架感興趣的可以看下Twitter開源的一個實(shí)現(xiàn)Memcached和Redis的分布式緩存框架Pelikan,目前國內(nèi)并沒有看到這樣的應(yīng)用案例,它的官網(wǎng)是http://twitter.github.io/pelikan/.
圖9 CacheCloud平臺架構(gòu)
這種類型在眾多類型中更顯得孤獨(dú),因?yàn)檫@種類型的方案更多是現(xiàn)象級,僅僅存在于為數(shù)不多的具有自研能力的公司中,或者說這種方案都是各公司根據(jù)自己的業(yè)務(wù)模型來進(jìn)行定制化的.這類產(chǎn)品的一個共同特點(diǎn)是沒有使用Redis Cluster的全部功能,只是借鑒了Redis Cluster的某些核心功能,比如說failover和slot的遷移.作為國內(nèi)使用Redis較早的公司之一,新浪微博就基于內(nèi)部定制化的Redis版本研發(fā)出了微博Redis服務(wù)化系統(tǒng)Tribe.它支持動態(tài)路由、讀寫分離(從節(jié)點(diǎn)能夠處理讀哀求)、負(fù)載均衡、配置更新、數(shù)據(jù)聚集(相同前綴的數(shù)據(jù)落到同一個slot中)、動態(tài)擴(kuò)縮容,以及數(shù)據(jù)落地存儲.同類型的還有百度的BDRP系統(tǒng).
圖10 Tribe系統(tǒng)架構(gòu)圖
Redis Cluster運(yùn)維開發(fā)最佳實(shí)踐經(jīng)驗(yàn)
根據(jù)公司的業(yè)務(wù)模型選擇合適的架構(gòu),適合本身的才是最好的;
做好容錯機(jī)制,當(dāng)連接或者哀求異常時進(jìn)行連接retry或reconnect;
重試時間可設(shè)置大于cluster-node-time (默認(rèn)15s),增強(qiáng)容錯性,減少不需要的failover;
避免發(fā)生hot-key,導(dǎo)致節(jié)點(diǎn)成為系統(tǒng)的短板;
避免發(fā)生big-key,導(dǎo)致網(wǎng)卡打爆和慢查詢;
設(shè)置合理的TTL,釋放內(nèi)存.避免大量key在同一時間段過期,雖然Redis已經(jīng)做了很多優(yōu)化,仍然會導(dǎo)致哀求變慢;
避免使用阻塞操作(如save、flushall、flushdb、keys *等),不建議使用事務(wù);
Redis Cluster不建議使用pipeline和multi-keys操作(如mset/mget. multi-key操作),減少max redirect的發(fā)生;
當(dāng)數(shù)據(jù)量很大時,由于復(fù)制積壓緩沖區(qū)大小的限制,主從節(jié)點(diǎn)做一次全量復(fù)制導(dǎo)致網(wǎng)絡(luò)流量暴增,建議單實(shí)例容量不要分配過大或者借鑒微博的優(yōu)化采納增量復(fù)制的方式來規(guī)避;
數(shù)據(jù)持久化建議在業(yè)務(wù)低峰期操作,關(guān)閉aofrewrite機(jī)制,aof的寫入操作放到bio線程中完成,辦理磁盤壓力較大時Redis阻塞的問題.設(shè)置系統(tǒng)參數(shù)vm.overcommit_memory=1,也可以避免bgsave/aofrewrite的失敗;
client buffer參數(shù)調(diào)整
client-output-buffer-limit normal 256mb 128mb 60
client-output-buffer-limit slave 512mb 256mb 180
對于版本升級的問題,修改源碼,將Redis的核心處理邏輯封裝到動態(tài)庫,內(nèi)存中的數(shù)據(jù)保留在全局變量里,通過外部程序來調(diào)用動態(tài)庫里的相應(yīng)函數(shù)來讀寫數(shù)據(jù).版本升級時只需要替換成新的動態(tài)庫文件即可,無須重新載入數(shù)據(jù),可毫秒級完成;
對付實(shí)現(xiàn)異地多活或?qū)崿F(xiàn)數(shù)據(jù)中心級災(zāi)備的要求(即實(shí)現(xiàn)集群間數(shù)據(jù)的實(shí)時同步),可以參考搜狐的實(shí)現(xiàn):Redis Cluster => Redis-Port => Smart proxy => Redis Cluster;
從Redis 4.2的Roadmap來看,更值得等待(詳情:https://gist.github.com/antirez/a3787d538eec3db381a41654e214b31d):
加速key->hashslot的分派
更好更多的數(shù)據(jù)中心存儲
redis-trib的C代碼將移植到redis-cli,瘦身包體積
集群的備份/恢復(fù)
非阻塞的Migrate
更快的resharding
暗藏一個只Cache模式,當(dāng)沒有Slave時,Masters當(dāng)在有一個失敗后能夠自動重新分配slot
Cluster API和Redis Modules的改進(jìn),而且Disque分布式消息隊列將作為Redis Module加入Redis.
致謝:感謝好友陳群、李航和劉東輝的幫忙審稿和寶貴建議.
作者簡介
張冬洪,微博研發(fā)中心高檔DBA,Redis中國用戶組主席,多年Linux和數(shù)據(jù)庫運(yùn)維經(jīng)驗(yàn),專注于MySQL和NoSQL架構(gòu)設(shè)計與運(yùn)維以及自動化平臺的開發(fā);目前在微博主要負(fù)責(zé)Feed核心系統(tǒng)相關(guān)業(yè)務(wù)的數(shù)據(jù)庫運(yùn)維和業(yè)務(wù)保障工作.
歡迎參與《Redis Cluster探索與思考》討論,分享您的想法,維易PHP學(xué)院為您提供專業(yè)教程。
轉(zhuǎn)載請注明本頁網(wǎng)址:
http://www.snjht.com/jiaocheng/9600.html