您好,登錄后才能下訂單哦!
這篇文章主要講解了“如何實現Redis集群擴縮容”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“如何實現Redis集群擴縮容”吧!
一、背景
攜程Redis集群規模和數據規模在過去幾年里快速增長,我們通過容器化解決了Redis集群快速部署的問題,并根據實際業務進行的一系列嘗試,比如二次調度、自動化漂移等,在內存超分的情況下保證了宿主機的可靠性。
擴縮容方面,我們主要通過垂直擴縮容的方式解決Redis集群容量的問題,但隨著集群規模擴大,這種方式逐漸遇到了瓶頸。一方面,單個Redis實例過大,會帶來較大的運維風險和困難;另一方面,宿主機容量有上限,不能無止境的擴容。考慮到運維便利性和資源利用率的平衡,我們希望單個Redis實例的上限為15GB。
但實際操作中卻很難做到:某些業務發展很快,經常性需要給Redis進行擴容,導致單個實例大小遠超15GB;一些業務萎縮,實際使用量遠低于初始申請的量,造成資源的浪費。
如何有效控制Redis實例大小呢?接下來本文將帶著這個問題,逐步講解攜程Redis治理和擴縮容方面的演進歷程。
二、Redis水平擴分拆
在攜程開始使用Redis很長一段時間里,一直只有垂直擴縮容,原因有兩點:
第一,一開始業務規模比較小,垂直擴縮容可以滿足需求。垂直擴縮容對于Redis來說只是Maxmemory的配置更改,對業務透明。
第二,水平拆分/擴縮容的實現難度和成本較高。
之前文章《攜程Redis治理演進之路》中已經提到,攜程訪問所有的Redis集群使用的是自主研發的CRedis,而部署在應用端的CRedis通過一致性hash來訪問實際承載數據的Redis實例。但一致性hash是無法支持直接水平擴縮容的。因為無論增加一個節點或者刪除一個節點,都會導致整個hash環的調整。
圖1
如圖所示,假設原始有4個分片(圖1)。當添加一個節點后,它會導致某一部分的key本來是寫到nodeC上而現在會被寫到nodeE上,也就是無法命中之前的節點。從客戶端的角度來看,key就像是丟失了。而變動的節點越多,key丟失的也越多,假設某個集群從10分片直接添加到20分片,它直接會導致50%的key丟失。刪除一個節點同理,就不再贅述。
因此盡管一致性hash是個比較簡單優秀的集群方案,但無法直接水平擴容一直困擾著運維和架構團隊。為此,CRedis團隊在2019年提出了水平拆分的方案。
CRedis水平分拆的思路比較樸素,因為在一致性hash同一個水平位置增加節點會導致數據丟失,那么不改變原來層次節點的hash規則,以某個節點為hash的起點,再來進行一次一致性hash,演變成樹的結構(圖2)。
圖2
如上圖所示,將樹形結構從一層拓展成二層,如果繼續拆分新的葉子Group,則可以將樹形結構拓展到三層,拆分方案可以支持到十層。葉子 Group是物理分片,直接對應的 Redis 實例,分支 Group 是虛擬分片,當Hash 命中到分支 Group 后,并沒有找不到對應的Redis實例,需要再繼續向下尋找,直到找到葉子 Group 為止。
圖3
CRedis水平分拆上線后,DBA將現存的絕大部分超過15G的實例都拆分成更小的實例,在一段時間內緩解了大內存實例的運維治理壓力。但隨著Redis規模的快速增長,不斷有大的實例集群出現,此外CRedis水平分拆的缺點也逐漸暴露出來:
持續的周期很長,對多個 Group 進行拆分的話,每個Group的數據需要同時復制幾份同樣的實例。比如60G的某個實例(圖3),如果想拆到5G一個,那么下級的Group必須有12個,而拆分要先將該實例的數據先同步為12個60G的實例,再根據key的命中規則清理該12個60G的實例中不會命中的key,最終演變成12個5G的實例。一般60G的group實例拆分需要3個小時-6個小時,如果一個集群的分片非常多,加上觀察對業務影響的時間,可能要持續上幾天或一兩周,并且只能是有人值守的串行操作。
拆分過程中需要2次遷移,如上面所說的,拆分中中間態實例對于內存的要求是非常大的,拆分完成后對內存的需求會急劇下降,因此每次拆分都涉及到2次遷移,盡管遷移不會影響業務,但對于執行操作拆分的運維人員來說,心智負擔比較大,而且一不小心也會導致線上事故。
拆分后無法還原回去,也就是說假設業務分拆后收縮,對Redis的需求變小了,但它實際拆分后的分片還在那邊,所申請的空間還并沒有釋放掉,客觀上浪費了資源,降低了Redis總體的利用率。
只支持擴容,不支持縮容,這點上面也提到了,除了一些集群過大需要分拆外,還有一些申請遠超需求的實例需要縮容,而水平分拆對于這點無能為力。
拆分一次,就多一次的性能損耗,因為需要多計算一次hash,雖然耗時不大,但是對于性能敏感的業務還是有影響。
由此可見,水平分拆的方案雖然解決了實例過大的問題,但不能縮容的弊端也逐漸凸現了出來。尤其是在今年因疫情影響需要降本增效的背景下,一方面資源比充足,一方面宿主機上跑的都是無法縮容的實例。那么是否有更好的解決方案呢?答案是有的。
三、Redis水平擴縮容
1、設計思路
圖4
既然縮分片比較困難,我們首先想到的是業務雙寫集群的方法,也就是業務同時雙寫2個新老集群,新老集群的分片數是不一樣的,并且大小配置也不一樣。比如之前申請4個分片現在發現資源過剩,讓業務創新申請一個新的2個分片的集群,由業務來控制灰度寫哪個集群(圖4)。最終會遷移到新集群上,而新集群大小是滿足當前業務需求的,從而達到了縮容的目的。
雙寫集群的方案雖然解決我們部分的問題,但對于業務的侵入比較深,此外由于雙寫集群引入了業務配合觀察的時間,整體流程也比較長。所以,我們需要尋找更好的解決方案。
既然業務雙寫集群可以達到要求,基礎設施如果代替業務做完這部分豈不是更好?借鑒業務雙寫集群的思路和云原生的不可變基礎設施的理念,我們首先想到的是通過新集群替換老集群而不是原地修改集群;另外,為了在公有云上節省Redis成本,我們積累了kvrocks的實踐經驗,兩者相結合,設計了一種高效的水平擴縮容的方案。
本方案的核心是引入了一個基于kvrocks改造的中間態binlogserver,它既是一個老集群的Slave節點,又充當了新集群的客戶端。一方面,它會從Redis Master復制全量和增量數據;另一方面,它又充當客戶端的角色,將復制來的數據按照新集群的一致性HASH規則寫往新的集群。大致的步驟如下,具體的步驟流程可以參考下面的圖所示(圖5)。
根據當前V1集群的分片啟動對應個數binlogserver,并獲取V2集群的一致性HASH規則和group。
每個binlogserver成為V1集群單個分片中Master的Slave,執行salveof后保存V1中Master傳過來的RDB文件并解析,對于每個RDB文件,解析還原成Redis命令,并按CRedis的一致性hash規則寫入到V2中,對于后續V1集群傳播過來的命令,同樣同步到V2中。
當這個過程都完成并且binlog追的差不多的時候,為了數據一致性,可以停止V1的寫(客戶端報錯)后由CRedis推送V2的配置或直接推送V2的配置(客戶端不報錯但數據可能會丟或不一致),APP端將會順序切換到 V2上來;此過程對用戶完全透明,應用端無需做任何操作。
圖5
通過Redis的水平擴縮容方案,我們解決了之前的幾個痛點問題:
持續時間大大縮短,基本上跟V1集群最大實例的大小正相關,因為是并發執行,跟集群分片數無關。根據實際的運維數據來看,集群單個實例為20G,集群擴縮容在10分鐘之內完成,而低于10G的,5分鐘即可完成,大大縮短了擴縮容的周期,并且業務在毫無感知的情況下即可完成擴縮容。由于可以做到秒級切換集群,即使擴縮容后對業務有影響也可以快速回退,因為回退也只是更改了集群的路由指向。
擴縮容過程只需要1次切換集群指向,0次遷移,沒有中間態,也無需通過大內存宿主機來實現分拆。
對于擴容的集群,很方便再來一次縮容還原回去,縮容同理。對于那些已經水平拆分過的集群,也可以通過這種方式還原回去。
既可以擴容也可以縮容,甚至還可以不擴容也不縮容按集群來遷移,比如《攜程Cilium+BGP云原生網絡實踐》一文中提到的云原生網絡安全控制試點項目。由于原來Redis集群下面的實例可能同時部署在openstack網絡和cilium網絡,但云原生安全只能控制cilium網絡下的實例,這種情況下就需要遷移Redis實例。如果按之前的運維方式,要按分片來一組組遷移,整個工程可能持續較長時間,并且耗費較多人力,而水平擴縮容可以將一個集群一次性快速遷移到cilium網絡,省時省力。
擴縮容后無性能損耗。
2、運維數據
水平擴縮容方案上線4個月來,已經成功完成了200多次的擴容和縮容。今年某個業務突然請求量暴增十幾倍,相關集群經歷了多次擴容,每次擴容大多在10分鐘內完成,有效地支撐了業務發展。
另一方面,針對申請分片非常多而大但實際使用量非常小的集群,我們也借助水平擴縮容的能力快速地縮小了分片數和申請量。通過這些縮容,有效地提升了整體的資源利用率。
3、一些坑
(1)單個key過大導致key被驅逐
在實際水平擴縮容過程中,我們發現有些集群,單個實例中可能會有巨大的key(大于3G),由于V2集群的大小是根據V1大小實時算出來的平均值,一旦V1中某個實例過大,可能會導致寫到V2中的某個實例大小大于預期的平均值,從而引起某些key被驅逐。因此,針對這種情況:
加強大key的檢測邏輯,對于超過512M的key會有告警郵件告知所有者。
V2中所有實例的maxmemory在分拆之前不設置限制,統一都調到60G,防止V2中key分配不均導致key驅逐。
水平擴縮容后,在V1和V2切換過程中,檢測V2中的實例是否發生過驅逐,如果有則默認分拆失敗,不進行切換。
(2)mget擴容后會導致性能下降
對于極個別的場景,我們還發現,mget請求耗時會有明顯上升,主要原因還是在于,擴容之前mget需要訪問的實例數少,而分拆后訪問的實例數變多。一般這種情況,我們建議業務控制單次mget的key的數量,或者將string類型改造為hash類型,通過hmget來訪問數據,保證每次只會訪問到一個實例,這樣擴容后其吞吐量是隨著分片數量線性增加,而延遲不會有增加。
四、總結和未來規劃
1、Xpipe支持
目前水平擴縮容和漂移以及二次調度等一系列治理工具和策略組成了一個比較完善的閉環,有效地支撐了生產幾千臺宿主機,幾萬帶超分能力Redis實例的運維治理。
但目前受制于xpipe的架構,對于接入了xpipe的集群,必須先擴縮容后再將DR端的xpipe人工補齊,自動化程度還不足,而補齊xpipe的時間比較長,比如之前是就近讀本機房的Redis集群的APP,在擴縮容后可能一段時間里只能跨機房讀取,必然導致延遲上升。而這種延遲上升又會影響我們對于水平擴縮容邏輯是否正確,是否需要回退的判斷。因此后續我們會針對xpipe集群,也做到和普通集群一樣,也就是V2集群在擴縮容寫流量之前就是帶DR架構的集群。
2、持久化KV存儲的支持
除了Redis本身受業務歡迎使用廣泛外,我們還發現有些業務需要相比Redis 更可靠的KV存儲方式,比如數據保存在磁盤上而不是保存在內存里,再比如業務需要支持一些增減庫存邏輯,對某個key的獨占訪問,實現語義近似INCRBY操作,但實際上是對于一些字符串進行merge操作。此外數據可靠性要求更高,master宕機不能丟失數據等。。
感謝各位的閱讀,以上就是“如何實現Redis集群擴縮容”的內容了,經過本文的學習后,相信大家對如何實現Redis集群擴縮容這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。