跋山涉水 —— 深入 Redis 字典遍歷
Redis 字典的遍歷過程邏輯比較復雜,互聯網上對這一塊的分析講解非常少。我也花了不少時間對源碼的細節進行了整理,將我個人對字典遍歷邏輯的理解呈現給各位讀者。也許讀者們對字典的遍歷過程有比我更好的理解,還請不吝指教。
一邊遍歷一邊修改
我們知道 Redis 對象樹的主幹是一個字典,如果對象很多,這個主幹字典也會很大。當我們使用 keys 命令搜尋指定模式的 key 時,它會遍歷整個主幹字典。值得註意的是,在遍歷的過程中,如果滿足模式匹配條件的 key 被找到了,還需要判斷 key 指向的對象是否已經過期。如果過期了就需要從主幹字典中將該 key 刪除。
那麽,你是否想到了其中的困難之處,在遍歷字典的時候還需要修改字典,會不會出現指針安全問題?
重復遍歷
字典在擴容的時候要進行漸進式遷移,會存在新舊兩個 hashtable。遍歷需要對這兩個 hashtable 依次進行,先遍歷完舊的 hashtable,再繼續遍歷新的 hashtable。如果在遍歷的過程中進行了 rehashStep,將已經遍歷過的舊的 hashtable 的元素遷移到了新的 hashtable中,那麽遍歷會不會出現元素的重復?這也是遍歷需要考慮的疑難之處,下面我們來看看 Redis 是如何解決這個問題的。
叠代器的結構
Redis 為字典的遍歷提供了 2 種叠代器,一種是安全叠代器,另一種是不安全叠代器。
叠代器的「安全」指的是在遍歷過程中可以對字典進行查找和修改,不用感到擔心,因為查找和修改會觸發過期判斷,會刪除內部元素。「安全」的另一層意思是叠代過程中不會出現元素重復,為了保證不重復,就會禁止 rehashStep。
而「不安全」的叠代器是指遍歷過程中字典是只讀的,你不可以修改,你只能調用 dictNext 對字典進行持續遍歷,不得調用任何可能觸發過期判斷的函數。不過好處是不影響 rehash,代價就是遍歷的元素可能會出現重復。
安全叠代器在剛開始遍歷時,會給字典打上一個標記,有了這個標記,rehashStep 就不會執行,遍歷時元素就不會出現重復。
叠代過程
安全的叠代器在遍歷過程中允許刪除元素,意味著字典第一維數組下面掛接的鏈表中的元素可能會被摘走,元素的 next 指針就會發生變動,這是否會影響叠代過程呢?下面我們仔細研究一下叠代函數的代碼邏輯。
值得註意的是在字典擴容時進行rehash,將舊數組中的鏈表遷移到新的數組中。某個具體槽位下的鏈表只可能會遷移到新數組的兩個槽位中。
叠代器的選擇
除了keys指令使用了安全叠代器,因為結果不允許重復。那還有其它的地方使用了安全叠代器麽,什麽情況下遍歷適合使用非安全叠代器呢?
簡單一點說,那就是如果遍歷過程中不允許出現重復,那就使用SafeIterator,比如下面的兩種情況
bgaofrewrite需要遍歷所有對象轉換稱操作指令進行持久化,絕對不允許出現重復
bgsave也需要遍歷所有對象來持久化,同樣不允許出現重復
如果遍歷過程中需要處理元素過期,需要對字典進行修改,那也必須使用SafeIterator,因為非安全的叠代器是只讀的。
其它情況下,也就是允許遍歷過程中出現個別元素重復,不需要對字典進行結構性修改的情況下一律使用非安全叠代器。
思考
請繼續思考rehash對非安全遍歷過程的影響,會重復哪些元素,重復的元素會非常多麽還是只是少量重復?
跋山涉水 —— 深入 Redis 字典遍歷