Redis 高效刪除大key
- 2019 年 10 月 30 日
- 筆記
一、大key的刪除問題
大key(bigkey)是指 key 的 value 是個龐然大物,例如 Hashes, Sorted Sets, Lists, Sets
,日積月累之後,會變得非常大,可能幾十上百MB,甚至到GB。
如果對這類大key直接使用 del
命令進行刪除,會導致長時間阻塞,甚至崩潰。
因為 del
命令在刪除集合類型數據時,時間複雜度為 O(M)
,M 是集合中元素的個數。
Redis 是單線程的,單個命令執行時間過長就會阻塞其他命令,容易引起雪崩。
二、解決方案
可靠方案:
- 漸進式刪除
- UNLINK (4.0版本以後)
1. 漸進式刪除
思路:
分批刪除,通過 scan 命令遍歷大key,每次取得少部分元素,對其刪除,然後再獲取和刪除下一批元素。
示例:
- 刪除大 Hashes
步驟:
(1)key改名,相當於邏輯上把這個key刪除了,任何redis命令都訪問不到這個key了
(2)小步多批次的刪除
偽代碼:
# key改名 newkey = "gc:hashes:" + redis.INCR( "gc:index" ) redis.RENAME("my.hash.key", newkey) # 每次取出100個元素刪除 cursor = 0 loop cursor, hash_keys = redis.HSCAN(newkey, cursor, "COUNT", 100) if hash_keys count > 0 redis.HDEL(newkey, hash_keys) end if cursor == 0 break end end
- 刪除大 Lists
偽代碼:
# key改名 newkey = "gc:hashes:" + redis.INCR("gc:index") redis.RENAME("my.list.key", newkey) # 刪除 while redis.LLEN(newkey) > 0 redis.LTRIM(newkey, 0, -99) end
- 刪除大 Sets
偽代碼:
# key改名 newkey = "gc:hashes:" + redis.INCR("gc:index") redis.RENAME("my.set.key", newkey) # 每次刪除100個成員 cursor = 0 loop cursor, members = redis.SSCAN(newkey, cursor, "COUNT", 100) if size of members > 0 redis.SREM(newkey, members) end if cursor == 0 break end end
- 刪除大 Sorted Sets
偽代碼:
# key改名 newkey = "gc:hashes:" + redis.INCR("gc:index") redis.RENAME("my.zset.key", newkey) # 刪除 while redis.ZCARD(newkey) > 0 redis.ZREMRANGEBYRANK(newkey, 0, 99) end
2. UNLINK
Redis 4.0 推出了一個重要命令 UNLINK
,用來拯救 del
刪大key的困境。
UNLINK 工作思路:
(1)在所有命名空間中把 key 刪掉,立即返回,不阻塞。
(2)後台線程執行真正的釋放空間的操作。
UNLINK
基本可以替代 del
,但個別場景還是需要 del
的,例如在空間佔用積累速度特別快的時候就不適合使用UNLINK
,因為 UNLINK
不是立即釋放空間。
三、總結
- 使用
del
刪除大key可能會造成長時間阻塞,甚至崩潰。 - 可以使用漸進式刪除,對
Hashes, Sorted Sets, Lists, Sets
分別處理,思路相同,先邏輯刪除,對key改名,使客戶端無法使用原key,然後使用批量小步刪除。 - 4.0版本以後可以使用
UNLINK
命令,後台線程釋放空間。