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

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 命令,後台線程釋放空間。