騰訊會議用戶暴漲,Redis集羣如何實現無縫擴容?

遠程辦公期間,在線會議用戶需求激增,騰訊會議8天完成100萬核雲服務器擴展,Redis集羣僅在半小時之內就高效完成了數十倍規模的擴容,單集羣的擴容流程後臺處理時間不超過30分鐘。在這背後,騰訊雲Redis是如何作到的呢?本文是伍旭飛老師在「雲加社區沙龍online」的分享整理,詳細闡述了騰訊雲Redis無損擴容的實踐和挑戰。
點擊此連接,查看完整直播視頻回放​

1、疫情帶來的挑戰

今年疫情帶來的挑戰很明顯,遠程辦公和在線教育用戶暴漲,從1月29到2月6日,日均擴容1.5w臺主機。業務7×24小時不間斷服務,遠程辦公和在線教育要求不能停服,停服一分鐘都會影響成百上千萬人的學習和工做,因此這一塊業務對於咱們的要求很是高。html

在線會議和遠程辦公都大量使用了redis,用戶暴增的騰訊會議背後也有騰訊雲Redis提供支持,同時海量請求對redis的快速擴容能力提出了要求。咱們有的業務實例,從最開始的3片一天以內擴容到5片,緊接着發現仍是不夠,又擴到12片,次日繼續擴。redis

2、開源Redis擴容方案

1. 騰訊雲Redis的集羣版架構

騰訊雲Redis跟廣泛Redis有差異,咱們加入了Proxy,提升了系統的易用性,這個是由於不是全部的語言都支持集羣版客戶端。爲了兼容這部分客戶,咱們作了不少的兼容性處理,可以兼容更多普通客戶端使用,像作自動的路由管理,切換的時候能夠自由處理MOVE和ASK,增長端到端的慢查詢統計等功能,Redis默認的slowlog只包含命令的運算時間,不包括網絡來回的時間,不包括本地物理機卡頓致使的延時,Proxy能夠作端到端的慢日誌統計,更準確反應業務的真實延遲。算法

對於多賬戶,Redis不支持,如今把這部分功能也挪到Proxy,還有讀寫分離,這個對於客戶很是有幫助,客戶無須敲寫代碼,只須要在平臺點一下,咱們在Proxy自動實現把讀寫派發上去。這一塊功能放到Redis也是能夠,那爲何作到Proxy呢?數據庫

主要是考慮安全性!由於Redis承載用戶數據,若是在Redis作數據會頻繁升級功能迭代,這樣對於用戶數據安全會產生比較大的威脅。安全

2. 騰訊雲Redis如何擴容

騰訊雲Redis怎麼擴容呢?咱們的擴容從三個維度出發,單個節點容量擴容, 好比說三分片,每一個片4G,咱們能夠每節點擴到8G。單節點容量擴容,通常來講只要機器容量足夠,就能夠擴容上去。還有副本擴容,如今客戶使用的是一主一從,有的同窗開讀寫分離,把讀所有打到從機,這種狀況下,增長讀qps比較簡單的方法就是增長副本的數量,還增長了數據安全性。最近的實際業務,咱們遇到的主要是擴分片,對於集羣分片數,最主要就是CPU的處理能力,擴容分片就是至關於擴展CPU,擴容處理能力也間接擴容內存。服務器

最先騰訊雲作過一個版本,利用開源的原生版的擴容方式擴容。簡單描述一下操做步驟:網絡

首先Proxy是要作slot容量計算,不然一旦搬遷過去,容易把新分片的內存打爆。計算完每一個slot內存後,按照算法分配,決定好目標分片有哪些slot。先設置目標節點slot 爲importing狀態 ,再設置源節點的slot爲 migrating狀態。架構

這裏存在一個小坑,在正常開發中這兩個設置你們感受順序可有可無,可是實際上有影響。若是先設置源節點的slot爲migrating,再設置目標節點的slot爲importing,那麼在這兩條命令的執行間隙,若是有對應slot的命令打到源節點,而key又剛好不存在,則會重定向到目標節點,因爲目標節點此時slot並將來得及設置爲importing, 又會把這條命令重定向給源節點,這樣就無限重定向了。併發

好在常見的客戶端(好比jedis)對重定向次數是有限制的, 一旦打到上限,就會拋出錯誤。less

(1)準備


(2)搬遷

設置完了這一步,下一步就是搬遷。從源節點來獲取slot的搬遷,從源進程慢慢逐個搬遷到目標節點。此操做是同步的,什麼意思呢?

在migrate命令結束以前進程不能直接處理客戶請求,其實是源端臨時建立一個socket,鏈接目標節點,同步執行命令,確認執行成功了後,把本地的Key刪掉,這個時候源端才能夠繼續處理客戶新的請求。在搬遷過程當中,整個集羣仍然是能夠處理請求的。

這一塊開源Redis有考慮,若是這個時候有Key讀請求,恰好這個slot發到源進程,進程能夠判斷,若是這個Key在本進程有數據,就會當正常的請求返回給它。

那若是不存在怎麼辦?就會發一個ASK給客戶,用戶收到ASK知道這個數據不在這個進程上,立刻從新發一個ASKING到目標節點,緊接着把命令發到那邊去。這樣會有一個什麼好處呢?

源端的Key的slot只會慢慢減小,不會增長,由於新增長的都發到目標節點去了。隨着搬遷的持續,源端的Key會愈來愈少,目標端的key逐步增長,用戶感知不到錯誤,只是多了一次轉發延遲,只有零點零幾毫秒,沒有特別明顯的感知。

(3)切換

方案到何時切換呢?就是slot源進程發現這個slot已經不存在數據了,說明全部數據所有搬到目標進程去了。這個時候怎麼辦呢?先發送set slot給目標,而後給源節點發送set slot命令,最後給集羣全部其餘節點發送set slot。

這是爲了更快速把路由更新過去,避免經過自身集羣版協議推廣,下降速度。這裏跟設置遷移前的準備步驟是同樣,也有一個小坑須要注意。

若是先給源節點設置slot,源節點認爲這個slot歸屬目標節點,會給客戶返回move,這個是由於源節點認爲Key永久歸屬目標進程,客戶端搜到move後,就不會發ASKing給目標,目標若是沒有收到ASK,就會把這個消息從新返回源進程,這樣就和打乒乓球同樣,來來回回無限重複,這樣客戶就會感受到這裏有錯誤。

3、 無損擴容挑戰

1. 大Key問題

像這樣遷移其實也沒有問題,客戶也不會感受到正常的訪問請求的問題。可是依然會面臨一些挑戰,第一就是大Key的問題。

前文提到的搬遷內部,因爲這個搬遷是同步的搬遷,同步搬遷會卡住,這個卡住時間由什麼決定的?主要不是網速,而是搬遷Key的大小來決定的,因爲搬遷是按照key來進行的,一個list也是一個Key,一個哈希表也是Key,一個list會有上千萬的數據,一個哈希表也會有不少的數據。同步搬遷容易卡很是久,同步搬遷100兆,打包有一兩秒的狀況,客戶會以爲卡頓一兩秒,全部訪問都會超時,通常Redis業務設置超時大部分是200毫秒,有的是100毫秒。若是同步搬移Key超過一秒,就會有大量的超時出現,客戶業務就會慢。

若是這個卡時超過15秒,這個時間包括搬遷打包時間、網絡傳輸時間、還有loading時間。超過15秒,甚至自動觸發切換,把Master判死,Redis會從新選擇新的Master,因爲migrating狀態是不會同步給slave的,因此slave切換成master後,它身上是沒有migrating狀態的。而後,正在搬遷的目標節點會收到新的master節點對這個slot的全部權聲明, 因爲這個slot是importing的,因此它會拒絕認可新master擁有這個slot。從而在這個節點看來,slot的覆蓋是不全面的, 有的slot無節點提供服務,集羣狀態爲fail。

一旦出現這種狀況,假如客戶繼續在寫,因爲沒有migrating標記了,新Key會寫到源節點上,這個key可能在目標節點已經有了,就算人工處理,也會出現哪一邊的數據比較新, 應該用哪一邊的數據, 這樣的一些問題,會影響到用戶的可用性和可靠性。

這是整個開源Redis的核心問題,就是容易卡住,不提供服務,甚至影響數據安全。開源版如何解決這個問題呢?老規矩:惹不起就躲,若是這個slot有最大Key超過100M或者200M的閾值不搬這個slot。這個閾值很難設置,因爲migrate命令一次遷移不少個key,太小的閾值會致使大部分slot遷移不了,過大的閾值仍是會致使卡死,因此不管如何對客戶影響都很是大,並且這個影響是不能被預知的,由於這個Key大小能夠從幾k到幾十兆,不知道何時搬遷到大key就會有影響,只要搬遷未結束,客戶在至關長時間都心驚膽戰。

2. Lua問題

除了Key的總體搬遷有這樣問題之外,咱們還會有一個問題就是Lua。

假如業務啓動的時候經過script load加載代碼,執行的時候使用evalsha來,擴容是新加了一個進程,對於業務是透明,因此按照Redis開源版的辦法搬遷Key,key搬遷到目標節點了,可是lua代碼沒有,只要在新節點上執行evalsha,就會出錯。

這個緣由挺簡單,就是Key搬遷不會遷移代碼,並且Redis沒有命令能夠把lua代碼搬遷到另一個進程(除了主從同步)。

這個問題在開源版是無解,最後業務怎麼作纔可以解決這個問題呢?須要業務那邊改一下代碼,若是發現evalsha執行出現代碼不存在的錯誤,業務要主動執行一個script load,這樣能夠規避這個問題。可是對不少業務是不能接受的。由於要面臨一個從新加代碼而後再發布這樣一個流程,業務受損時間是很是長的。

3. 多Key命令/Slave讀取

還有一個挑戰,就是多Key命令,這個是比較嚴重的問題,Mget和mset其中一個Key在源進程,另一個Key根本不存在或者在目標進程,這個時候會直接報錯,不少業務嚴重依賴於mget的準確性,這個時候業務是不能正常工做的。

這也是原生版redis沒有辦法解決的問題,由於只要是Key搬遷,很容易出現mget的一部分key在源端,一部分在目標端。

除了這個問題還有另外一個問題,這個問題跟自己分片擴容無關,可是開源版本存在一個bug,就是咱們這邊Redis是提供了一個讀寫分離的功能,在Proxy提供這個功能,把全部的命令打到slave,這樣能夠下降master的性能壓力。這個業務用得很方便,業務想起來就能夠開啓,發現不行就能夠立刻關閉。

這裏比較明顯的問題是:當每一個分片數據比較大的時候,舉一個例子20G、30G的數據量的時候,咱們剛開始掛slave,slave身份推廣跟主從數據是兩個機制,可能slave已經被集羣承認了,可是還在等master的數據,由於20G數據的打包須要幾分鐘(和具體數據格式有關係)。這個時候若是客戶的讀命令來到這個slave, 會出現讀不到數據返回錯誤, 若是客戶端請求來到的時候rdb已經傳到slave了,slave正在loading, 這個時候會給客戶端回loading錯誤。

這兩個錯誤都是不能接受,客戶會有明顯的感知,擴容副本原本爲了提高性能,可是結果一擴反而持續幾分種到十幾分鍾內出現不少業務的錯誤。這個問題實際上是跟Redis的基本機制:身份推廣機制、主從數據同步機制有關,由於這兩個機制是徹底獨立的,沒有多少關係,問題的解決也須要咱們修改這個狀態來解決,下文會詳細展開。

最後一點就是擴容速度。前文說過,Redis經過搬Key的方式對業務是有影響的,因爲同步操做,速度會比較慢,業務會感覺到明顯的延時,這樣的延時業務確定但願越快結束越好。可是咱們是搬遷Key,嚴重依賴Key的速度。由於搬Key不能全速搬,Redis是單線程,基本上線是8萬到10萬之間,若是搬太快,就佔據用戶CPU。用戶原本由於同步搬遷卡頓致使變慢,搬遷又要佔他CPU,致使雪上加霜,因此通常這種方案是不可能作特別快的搬遷。好比說每次搬一萬Key,至關於佔到12.5%,甚至更糟,這對於用戶來講是很是難以接受的。

4、行業其餘方案

既然開源版有這麼多問題,爲何不改呢?不改的緣由這個問題比較多。可能改起來不容易,也確實不太容易。

關於搬遷分片擴容是Redis的難點,不少人反饋過,可是目前而言沒有獲得做者的反饋,也沒有一個明顯的解決的趨勢,行業內最多見就是DTS方案。

DTS方案能夠經過下圖來了解,首先經過DTS創建同步,DTS同步跟Redis-port是相似,會假裝一個slave,經過sync或者是psync命令從源端slave發起一次全量同步,全量以後再增量,DTS接到這個數據把rdb翻譯成命令再寫入目標端的實例上,這樣就不要求目標和源實例的分片數目一致,dts在中間把這個活給幹了。

等到DTS徹底遷移穩定以後,就能夠一直同步增量數據,不停從源端push目標端,這時候能夠考慮切換。

切換首先觀察是否是全部DTS延遲都在閾值內,這個延遲指的是從這邊Master到那邊Master的中間延遲。若是小於必定的數據量,就能夠斷連客戶端,等待必定時間,等目標實例徹底追上來了,再把LB指向新實例,再把源實例刪除了。一次擴容就徹底實現了,這是行業比較常見的一種方案。

DTS方案解決什麼問題呢?大Key問題獲得瞭解決。由於DTS是經過源進程slave的一個進程同步的。Lua問題有沒有解決?這個問題也解決了,DTS收到RDB的時候就有lua信息了,能夠翻譯成script load命令。多Key命令也獲得瞭解決,正經常使用戶訪問不受影響,在切換以前對用戶來講無感知。遷移速度也可以獲得比較好的改善。遷移速度自己是由於原實例經過rdb翻譯,翻譯以後併發寫入目標實例,這樣速度能夠很快,能夠全速寫。這個速度必定比開源版key搬遷更快,由於目標實例在切換前不對外工做,能夠全速寫入,遷移速度也是獲得保證。遷移中的HA和可用性和可靠性也都還能夠。固然中間可用性要斷連30秒到1分鐘,這個時間用戶不可用,很是小的時間影響用戶的可用性。

DTS有沒有缺點?有!首先是其複雜度,這個遷移方案依賴於DTS組件,須要外部組件才能實現,這個組件比較複雜,容易出錯。其次是可用性,前文提到步驟裏面有一個踢掉客戶端的狀況,30秒到1分鐘這是通常的經驗可用性影響,徹底不可訪問。還有成本問題,遷移過程當中須要保證全量的2份資源,這個資源量保證在遷移量比較大的狀況下,是很是大的。若是全部的客戶同時擴容1分片,須要整個倉庫2倍的資源, 不然不少客戶會失敗,這個問題很致命,意味着我要理論上要空置一半的資源來保證擴容的成功, 對雲服務商來講是不可接受的,基於以上緣由咱們最後沒有采用DTS方案。

5、騰訊雲Redis擴容方案

咱們採用方案是這樣的,咱們的目標是首先不依賴第三方組件,經過命令行也能夠遷。第二是咱們資源不要像DTS那樣遷移前和遷移後兩份資源都要保留,這個對於咱們有至關大的壓力。最後用的是經過slot搬遷的方案。具體步驟以下:

首先仍是計算各slot內存大小,須要計算具體搬遷多少slot。分配完slot以後,還要計算可分配到目標節點的slot。跟開源版不同,不須要設置源進程的migrating狀態,源進程設置migrating是但願新Key自動寫入到目標進程,可是咱們這個方案是不須要這樣作。

再就是在目標進程發起slot命令,這個命令執行後,目標節點根據slot區間自動找到進程,而後對它發起sync命令(帶slot的sync),源進程收到這個sync命令,執行一個fork,將全部同步的slot區間全部的數據生成rdb,同步給目標進程。

每個slot有哪一些Key在源進程是有記錄的,這裏遍歷將每個slot的key生成rdb傳輸給目標進程,目標進程接受rdb開始loading,而後接受aof,這個aof也是接受跟slot相關的區間數據,源進程也不會把不屬於這個slot的數據給目標進程。

一個目標進程能夠從一兩個源點創建這樣的鏈接,一旦所有創建鏈接,而且同步狀態正常後,當offset足夠小的時候,就能夠發起failover操做。和Redis官方主動failover機制同樣。在failover以前,目標節點是不提供服務的,這個和開源版有巨大的差異。

經過這個方案,大Key問題獲得瞭解決。由於咱們是經過fork進程解決的,而不是源節點搬遷key。切換前不對外提供服務,因此loading一兩分鐘沒有關係,客戶感知不到這個節點在loading。

還有就是Lua問題也解決了,新節點接受的是rdb數據,rdb包含了Lua信息在裏面。還有多Key命令也是同樣,由於咱們徹底不影響客戶正常訪問,多Key的命令之前怎麼訪問如今仍是怎麼訪問。遷移速度由於是批量slot打包成rdb方式,必定比單個Key傳輸速度快不少。

關於HA的影響,遷移中有一個節點掛了會不會有影響?開源版會有影響,若是migrating節點掛了集羣會有一個節點是不可以對外提供服務。但咱們的方案不存在這個問題,切換完了依然能夠提供服務。由於咱們原本目標節點在切換以前就是不提供服務的。

還有可用性問題,咱們方案不用斷客戶端鏈接,客戶端從頭至尾沒有受到任何影響,只是切換瞬間有小影響,毫秒級的影響。成本問題有沒有解決?這個也獲得解決,由於擴容過程當中,只建立最終須要的節點,不會建立中間節點,零損耗。

6、Q&A

Q:Cluster數量會改變槽位的數量嗎?

A:不會改變槽位數量,一直是16814,這個跟開源版是一致的。

Q:遷移以前怎麼評估新slot接觸數據不會溢出?

A:根據前文所述,咱們有一個準備階段,計算全部slot各自內存大小,怎麼計算咱們會在slave直接執行一次掃描計算,基本上可以計算比較準確。這裏沒有用前一天的備份數據,而是採用slave實時計算,CPU裏有相應控制,這樣能夠計算出slot總量大小。咱們會預約提早量(1.3倍),用戶還在寫,保證目標不會遷移中被寫爆,萬一寫爆了也只是流程失敗,用戶不會受到影響。

Q:對於 redis 擴容操做會發生的問題,大家有采用什麼備用方案,和緊急措施嗎?

A:新的擴容方案,若是出現問題,不會影響客戶的使用,只會存在多餘的資源,目前這塊依賴工具來處理。數據的安全性自己除了主從,還依賴每日備份來保證。

Q:請問老師,高峯期須要擴容,但總有迴歸正常請求量的時候,此時的擴容顯得有些冗餘,怎麼樣讓Redis集羣可以既可以快速回收多餘容量,同時又能方便下一次高峯請求的再次擴容呢?

A:可能你想了解的是serverless,按需付費自動擴展的模式,目前的數據庫大可能是提供的PAAS服務,PASS層的數據庫將過去的資源手動管理變成了半自動化,擴縮容仍是須要運維參與。Serverless形式的服務會是下一步的形態,可是就算是serverless可能也會面臨着須要資源手動擴展的問題,特別是對超大規模的運算服務。

Q:slot來源於多個分片,同時和多個節點同步進行嗎?

A:是,確實會這樣作的。

Q:slot產生過程當中產生新數據怎麼同步?

A:相似aof概念的機制同步到目標進程。這個aof跟普通aof傳輸到slave有區別,只會將跟目標slot相關的數據同步過去,而不會同步別的。

Q:還在招人嗎?

A:騰訊雲Redis還在招人,歡迎你們投簡歷,簡歷請投至郵箱:feiwu@tencent.com。你們還能夠選擇在官網上投遞簡歷,或者搜索關注騰訊雲數據庫的公衆號,查看21日的推送文章,咱們貼上了招聘職位的連接,也能夠@社羣小助手,小助手會收集上來發到我這邊。

講師簡介

伍旭飛,騰訊雲高級工程師,騰訊雲Redis技術負責人,有多年和遊戲和數據庫開發應用實踐經驗, 聚焦於遊戲開發和NOSQL數據庫在各個領域的應用實踐。

關注雲加社區公衆號,回覆「線上沙龍」,便可獲取老師演講PPT~

相關文章
相關標籤/搜索