聊聊 Undermoon - Redis Cluster Slots 遷移
項(xiàng)目地址:https://github.com/doyoubi/undermoon
目標(biāo):
- 簡(jiǎn)單
- 快速
遷移過程基于以下 Redis 命令:
- SCAN
- DUMP
- PTTL
- RESTORE
- DEL
SCAN 命令有一個(gè)很好的特性,它可以保證在第一個(gè) SCAN 命令之前設(shè)置的所有 key 最終都會(huì)返回,但有時(shí)會(huì)返回多次。我們可以執(zhí)行 3 階段遷移來模擬復(fù)制。
- 等待 Redis 完成所有命令。
- 將所有讀寫操作重定向到目標(biāo) Redis。如果 key 不存在,則目標(biāo) Redis 將需要在處理命令之前從源 Redis 轉(zhuǎn)儲(chǔ) key 的數(shù)據(jù)。
- 開始掃描并將數(shù)據(jù)轉(zhuǎn)發(fā)到 peer Redis。
詳細(xì)步驟
- migrating proxy(遷移代理) 通過 PreCheck 命令檢查 importing proxy(導(dǎo)入代理) 是否也收到遷移任務(wù)。
- migrating proxy 阻塞所有新添加的命令到 Queue,并等待現(xiàn)有命令完成。
- migrating proxy 向 importing proxy 發(fā)送 TmpSwitch 命令。收到此命令后,importing proxy 開始處理導(dǎo)入slot(槽)范圍內(nèi)的 key。當(dāng)命令返回時(shí),migrating proxy 釋放 Queue 內(nèi)的所有命令,并將它們重定向到 importing proxy。
- migrating proxy 使用 SCAN、PTTL、DUMP、RESTORE、DEL 將遷移 slot 范圍內(nèi)的所有數(shù)據(jù)轉(zhuǎn)發(fā)到 peer importing Redis。RESTORE 不設(shè)置 REPLACE flag。
- importing proxy 在處理命令時(shí),無論是讀操作還是寫操作,都會(huì)先
- 將 EXISTS 和處理后的命令發(fā)送到 local importing Redis,如果 EXISTS 返回 true,則將命令轉(zhuǎn)發(fā)到 local importing Redis。
- 如果 EXISTS 返回 false,則發(fā)送 DUMP 和 PTTL 到遷移的 Redis 獲取數(shù)據(jù),并 RESTORE 數(shù)據(jù)并將命令轉(zhuǎn)發(fā)到 local Redis。然后最后將命令轉(zhuǎn)發(fā)到 local importing Redis。
- 如果該命令不會(huì)刪除 key,則獲取 key lock,
- 如果該命令可能刪除 key,則獲取 key lock 并將 UMSYNC 發(fā)送到 migrating proxy,讓 migrating proxy 使用 DUMP、PTTL、RESTORE、DEL 將 key 傳輸?shù)?importing proxy。然后最后將命令轉(zhuǎn)發(fā)到 local importing Redis。
- 當(dāng) migrating proxy 完成掃描后,它會(huì)向 importing proxy 提出 CommitSwitch。然后importing proxy只需要在 local Redis 中處理命令。
- 通知 coordinator 并等待 UMCTL SETCLUSTER 的最終提交。
為什么會(huì)這樣設(shè)計(jì)
整個(gè)遷移過程基于以下命令 SCAN、PTTL、DUMP、RESTORE、DELETE。僅向?qū)敕?wù)器代理發(fā)送 RESTORE 命令,因此為了獲得更好的性能,應(yīng)在遷移服務(wù)器代理中執(zhí)行此掃描和傳輸。
由于掃描和傳輸在服務(wù)器代理和 Redis 上都占用了大量的 CPU 資源,因此最好在importing proxy上處理其他工作負(fù)載。因此,一開始我們將所有插槽(slots)直接轉(zhuǎn)移到importing proxy。
此時(shí),importing proxy 仍然只有一小部分?jǐn)?shù)據(jù)。當(dāng)它需要處理新添加的 slots 上的命令時(shí),需要在處理請(qǐng)求之前使用 PTTL、DUMP、RESTORE 從遷移的服務(wù)器代理中拉取數(shù)據(jù)。它還需要發(fā)送 DELETE 來刪除 key。
請(qǐng)注意,對(duì)于不會(huì)刪除 key 的任何命令,由于它是冪等的,因此對(duì)同一 key 多次 RESTORE 仍然是正確的。所以僅僅讓 importing proxy 來拉數(shù)據(jù)不會(huì)導(dǎo)致任何不一致。
但是對(duì)于那些可能刪除 DEL、EXPIRE、KPOP 等 key 的命令,只讓 importing proxy 拉取數(shù)據(jù)可能會(huì)導(dǎo)致以下情況:
- key 被刪除
- 還有另一個(gè) RESTORE 命令可以恢復(fù) key。
因此,在提取數(shù)據(jù)時(shí),需要將其與
- importing proxy中的其他 RESTORE 命令。
- migrating proxy中的 SCAN 和 RESTORE。
因此,我們需要在 importing proxy 中鎖定 key,并且需要 migrating proxy 幫助我們發(fā)送數(shù)據(jù)而不是從importing proxy中拉取數(shù)據(jù),以便對(duì)該 key 的操作只能按順序處理。
性能
因此,在遷移過程中,遷移和導(dǎo)入 proxy 的工作量非常平衡。migrating proxy 使用 130% 的 CPU,importing proxy使用 80% 的 CPU。
而且遷移 1G 數(shù)據(jù)只用了不到一分鐘。
在測(cè)試中,在遷移的同時(shí)進(jìn)行基準(zhǔn)測(cè)試,吞吐量從 50k 減少到 28k 并逐漸增加到 40k。這是因?yàn)樵谶w移和importing proxy中,SCAN、DUMP、RESTORE 會(huì)在 Redis 中消耗大量吞吐量。但是一旦 key 被遷移到導(dǎo)入服務(wù)器代理,它只需要在請(qǐng)求之前發(fā)送一個(gè)額外的 EXISTS 命令。
提交遷移后,吞吐量將翻倍。