前言
文章來自部落格,檢視最新文章,請點選檢視部落格,歡迎大家關注與討論
當使用 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指向新記憶體的耗時,是常數級別,可忽略
- 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記憶體
1、rename具有原子性嗎
從原始碼中看,rename過程需要經過刪除newKey和修改指標指向這兩步,而如果第二步失敗,第一步操作並不會回滾,所以不具有原子性
2、rename中的刪除操作是同步的嗎
從程式碼中可以看到是同步還是非同步,完全取決於配置的DEL機制,即由lazyfree-lazy-server-del
配置決定。
3、如何解決rename耗時長的問題
之前測試中發現rename操作卡了3秒,執行config get *
命令,發現確實配置的刪除方式為同步刪除
所以解決方法有兩個,要麼減少Key的member成員數量,要麼配置lazyfree-lazy-server-del
為yes
本文已結束,能力有限,文章錯誤地方煩請指出,感謝大家的閱讀
本作品採用《CC 協議》,轉載必須註明作者和本文連結