一、rename原理
當使用 rename oldKey newKey 指令時,主要會執行如下兩個操作
1、隐式删除newKey
由于rename操作不是renameNX,而是強制性的把舊Key名修改為新Key名。是以如果新Key名指向了資料,Redis就必先把這個資料删掉!
注 :key對應的Value抽象為memory記憶體
(1) 源碼
在Redis中,無論執行rename還是renameNX指令,都會執行一個通用的renameGenericCommand函數,隻是傳遞的第二個NX參數不一樣而已,而client參數就是如其名,表示用戶端,用于擷取指令攜帶的參數
- c->argv[1]表示oldKey
- c->argv[2]表示newKey
是以核心看renameGenericCommand函數即可
如下代碼中 if(lookupKeyWrite(c->db,c->argv[2]) != NULL) ,其中lookupKeyWrite 函數會傳回Key所指向的記憶體指針,如果不為空,則說明已經有資料存儲,是以緊接着就會執行删除newKey的邏輯
(2) 時間複雜度
時間複雜度為O(M) ,M為成員數量
(3) 測試
先寫一個有500W成員的Hash類型的bigkey,如下圖發現寫入後,記憶體增加約400MB,删除它需要3秒左右
然後我們執行rename操作,結果如下
是以rename操作會隐式的删除newKey,且删除耗時為O(M)
2、修改指針指向
Redis有如下兩種方案可以實作rename效果,第一種是資料拷貝,第二種是修改指針指向。如果采用值拷貝的方式,會增加Redis的記憶體峰值,且拷貝記憶體的時間也會增加耗時,最重要的值拷貝在Redis場景中不需要,是以Redis使用的是第二種修改指針的方式
注 :key對應的Value抽象為memory記憶體
(1) 源碼
如下源碼中,在拿到oldKey指向的記憶體對象(值對象)指針後,記為o,然後依次做如下操作
- 為o引用計數加1,此時o的引用計數為2
- 把新的鍵值關系(newKey => o)增加到目前DB中,相當于讓newKey重新指向o
- 删除舊的鍵值(oldKey => o)關系,相當于删除oldKey的指向
由于o的引用計數為2,在删除了oldKey的指向關系後,o的引用計數還是1,并不會觸發GC,是以對象o所占用的記憶體空間仍然是有效的,不過變成了由newKey指向
(2) 時間複雜度
O(1)
3、總結
rename操作耗時為O(1)是不準确的,應該為O(M)+O(1)
- O(M)為删除newKey的耗時,成員與删除耗時成線性關系
- O(1)為newKey指向新記憶體的耗時,是常數級别,可忽略
二、rename完整過程
- find newKey :找到newKey所指向的值對象
- delete memory A :删除值對象所指向的記憶體
- find oldKey :找到oldKey所指向的值對象
- incrRefCount :為oldKey所指向的值對象的引用計數+1
- add relation :把(newKey => o)新的鍵值對資訊加到資料庫中,讓newKey指向一個新的值對象
- delete relation :删除(oldKey => o)舊的鍵值對資訊,讓oldKey不再指向之前的值對象
注 :key對應的Value抽象為memory記憶體
三、關于rename的一些疑問
1、rename具有原子性嗎
從源碼中看,rename過程需要經過删除newKey和修改指針指向這兩步,而如果第二步失敗,第一步操作并不會復原,是以不具有原子性
2、rename中的删除操作是同步的嗎
從代碼中可以看到是同步還是異步,完全取決于配置的DEL機制,即由lazyfree-lazy-server-del配置決定。
3、如何解決rename耗時長的問題
之前測試中發現rename操作卡了3秒,執行config get *指令,發現确實配置的删除方式為同步删除
是以解決方法有兩個,要麼減少Key的member成員數量,要麼配置lazyfree-lazy-server-del為yes
四、結束語
本文已結束,能力有限,文章錯誤地方煩請指出,感謝大家的閱讀