Redis++:Redis 大 key的發現與刪除方法全解析


關於Redis大鍵(Key),我們從 [空間復雜性] 和訪問它的 [時間復雜度] 兩個方面來定義大鍵。

前者主要表示Redis鍵的占用內存大小;后者表示Redis集合數據類型(set/hash/list/sorted set)鍵,所含有的元素個數。

以下兩個示例:

1個大小200MB的String鍵(String Object最大512MB),內存空間占用較大;
1個包含100000000(1kw)個字段的Hash鍵,對應訪問模式(如hgetall)時間復雜度高

因為內存空間復雜性處理耗時都非常小,測試 del 200MB String鍵耗時約1毫秒,而刪除一個含有1kw個字段的Hash鍵,卻會阻塞Redis進程數十秒。

所以本文只從時間復雜度分析大的集合類鍵。刪除這種大鍵的風險,以及怎么優雅地刪除。

在Redis集群中,應用程序盡量避免使用大鍵;直接影響容易導致集群的容量和請求出現”傾斜問題“,具體分析見文章:redis-cluster-imbalance

但在實際生產過程中,總會有業務使用不合理,出現這類大鍵;當DBA發現后推進業務優化改造,然后刪除這個大鍵;

如果直接刪除它,DEL命令可能阻塞Redis進程數十秒,對應用程序和Redis集群可用性造成嚴重的影響。

直接刪除大Key的風險

DEL命令 在刪除單個集合類型的Key時,命令的時間復雜度是O(M),其中M是集合類型Key包含的元素個數。

DEL keyTime complexity: O(N) where N is the number of keys that will be removed. When a key to remove holds a value other than a string, the individual complexity for this key is O(M) where M is the number of elements in the list, set, sorted set or hash. Removing a single key that holds a string value is O(1).

生產環境中遇到過多次因業務刪除大Key,導致Redis阻塞,出現故障切換和應用程序雪崩的故障。

測試刪除集合類型大Key耗時,一般每秒可清理100w~數百w個元素; 如果數千w個元素的大Key時,會導致Redis阻塞上10秒可能導致集群判斷Redis已經故障,出現故障切換;或應用程序出現雪崩的情況。

說明:Redis是單線程處理。單個耗時過大命令,導致阻塞其他命令,容易引起應用程序雪崩或Redis集群發生故障切換。所以避免在生產環境中使用耗時過大命令。

Redis刪除大的集合鍵的耗時, 測試估算,可參考;和硬件環境、Redis版本和負載等因素有關

當我們發現集群中有大key時,要刪除時,如何優雅地刪除大Key?

從Redis2.8版本開始支持SCAN命令,通過m次時間復雜度為O(1)的方式,遍歷包含n個元素的大key.這樣避免單個O(n)的大命令,導致Redis阻塞。

這里刪除大key操作的思想也是如此。

說明:

  redis大key,這里指的是大的集合數據類型,如(set/hash/list/sorted set),一個key包含很多元素。

  由於redis是單線程,在刪除大key(千萬級別的set集合)的時候,或者清理過期大key數據時,主線程忙於刪除這個大key,會導致redis阻塞、崩潰,應用程序異常的情況。

  redis-cli --bigkeys 命令。可以找到某個實例5種數據類型(String、hash、list、set、zset)的最大key 、等其他方式

一個栗子:↓

線上redis作為實時去重的一個工具,里面有6千萬的用戶guid,這么一個set集合,如果直接使用del刪除,會導致redis嚴重阻塞

10.1.254.18:6380> info memory # Memory used_memory:15175740016 used_memory_human:14.13G used_memory_rss:22302339072 used_memory_peak:22351749192 used_memory_peak_human:20.82G used_memory_lua:36864 mem_fragmentation_ratio:1.47 mem_allocator:jemalloc-3.6.0
10.1.254.18:6380> scard helper_2019-03-12 (integer) 64530980
10.1.254.18:6380> del helper_2019-03-12 (integer) 1 (81.23s) 10.1.254.18:6380> info memory # Memory used_memory:8466985704 used_memory_human:7.89G used_memory_rss:10669453312 used_memory_peak:22351749192 used_memory_peak_human:20.82G used_memory_lua:36864 mem_fragmentation_ratio:1.26 mem_allocator:jemalloc-3.6.0

可以看到,helper_2019-03-12這個key,是一個包含64530980個元素的集合,直接使用del刪除命令,花的時間為:81.23s,顯然會發送超時、阻塞,程序異常!

這種情況,應該使用sscan命令,批量刪除set集合元素的方法。下面是一個Java代碼分批刪除redis中set集合的例子:

private static void test2(){ // 連接redis 服務器
    Jedis jedis = new Jedis("0.0.0.0",6379); jedis.auth("123456"); // 分批刪除
    try { ScanParams scanParams = new ScanParams(); // 每次刪除 500 條
        scanParams.count(500); String cursor = ""; while (!cursor.equals("0")){ ScanResult<String> scanResult=jedis.sscan("testset", cursor, scanParams); // 返回0 說明遍歷完成
            cursor = scanResult.getStringCursor(); List<String> result = scanResult.getResult(); long t1 = System.currentTimeMillis(); for(int m = 0;m < result.size();m++){ String element = result.get(m); jedis.srem("testset", element); } long t2 = System.currentTimeMillis(); System.out.println("刪除"+result.size()+"條數據,耗時: "+(t2-t1)+"毫秒,cursor:"+cursor); } }catch (JedisException e){ e.printStackTrace(); }finally { if(jedis != null){ jedis.close(); } } }

對於其它集合,也有對應的方法。

  • hash key:通過hscan命令,每次獲取500個字段,再用hdel命令;
  • set key:使用sscan命令,每次掃描集合中500個元素,再用srem命令每次刪除一個元素;
  • list key:刪除大的List鍵,未使用scan命令; 通過ltrim命令每次刪除少量元素。
  • sorted set key:刪除大的有序集合鍵,和List類似,使用sortedset自帶的zremrangebyrank命令,每次刪除top 100個元素。

后台刪除之lazyfree機制:

為了解決redis使用del命令刪除大體積的key,或者使用flushdb、flushall刪除數據庫時,造成redis阻塞的情況,在redis 4.0引入了lazyfree機制,可將刪除操作放在后台,讓后台子線程(bio)執行,避免主線程阻塞。

lazy free的使用分為2類:第一類是與DEL命令對應的主動刪除,第二類是過期key刪除、maxmemory key驅逐淘汰刪除。

主動刪除:

UNLINK命令是與DEL一樣刪除key功能的lazy free實現。唯一不同時,UNLINK在刪除集合類鍵時,如果集合鍵的元素個數大於64個(詳細后文),會把真正的內存釋放操作,給單獨的bio來操作。

127.0.0.1:7000> UNLINK mylist
(integer) 1
FLUSHALL/FLUSHDB ASYNC
127.0.0.1:7000> flushall async //異步清理實例數據

被動刪除:

lazy free應用於被動刪除中,目前有4種場景,每種場景對應一個配置參數; 默認都是關閉。

lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
slave-lazy-flush no

lazyfree-lazy-eviction

  針對redis內存使用達到maxmeory,並設置有淘汰策略時;在被動淘汰鍵時,是否采用lazy free機制;

因為此場景開啟lazy free, 可能使用淘汰鍵的內存釋放不及時,導致redis內存超用,超過maxmemory的限制。此場景使用時,請結合業務測試。

lazyfree-lazy-expire

  針對設置有TTL的鍵,達到過期后,被redis清理刪除時是否采用lazy free機制;

此場景建議開啟,因TTL本身是自適應調整的速度。

lazyfree-lazy-server-del

  針對有些指令在處理已存在的鍵時,會帶有一個隱式的DEL鍵的操作。如rename命令,當目標鍵已存在,redis會先刪除目標鍵,如果這些目標鍵是一個big key,那就會引入阻塞刪除的性能問題。

此參數設置就是解決這類問題,建議可開啟。

slave-lazy-flush

  針對slave進行全量數據同步,slave在加載master的RDB文件前,會運行flushall來清理自己的數據場景,

參數設置決定是否采用異常flush機制。如果內存變動不大,建議可開啟。可減少全量同步耗時,從而減少主庫因輸出緩沖區爆漲引起的內存使用增長。

expire及evict優化

  redis在空閑時會進入activeExpireCycle循環刪除過期key,每次循環都會率先計算一個執行時間,在循環中並不會遍歷整個數據庫,

而是隨機挑選一部分key查看是否到期,所以有時時間不會被耗盡(采取異步刪除時更會加快清理過期key),剩余的時間就可以交給freeMemoryIfNeeded來執行。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM