redis 刪除大key集合的方法


redis大key,這里指的是大的集合數據類型,如(set/hash/list/sorted set),一個key包含很多元素。由於redis是單線程,在刪除大key(千萬級別的set集合)的時候,或者清理過期大key數據時,主線程忙於刪除這個大key,會導致redis阻塞、崩潰,應用程序異常的情況。

一個例子

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

 1 10.1.254.18:6380> info memory
 2 # Memory
 3 used_memory:15175740016
 4 used_memory_human:14.13G
 5 used_memory_rss:22302339072
 6 used_memory_peak:22351749192
 7 used_memory_peak_human:20.82G
 8 used_memory_lua:36864
 9 mem_fragmentation_ratio:1.47
10 mem_allocator:jemalloc-3.6.0
11 10.1.254.18:6380> scard helper_2019-03-12
12 (integer) 64530980
13 10.1.254.18:6380> del helper_2019-03-12
14 (integer) 1
15 (81.23s)
16 10.1.254.18:6380> info memory
17 # Memory
18 used_memory:8466985704
19 used_memory_human:7.89G
20 used_memory_rss:10669453312
21 used_memory_peak:22351749192
22 used_memory_peak_human:20.82G
23 used_memory_lua:36864
24 mem_fragmentation_ratio:1.26
25 mem_allocator:jemalloc-3.6.0

可以看到,helper_2019-03-12這個key,是一個包含64530980個元素的集合,直接使用del刪除命令,花的時間為:81.23s,在超時時間短的苛刻情況下,顯然會發送超時,程序異常!好在,我們用的是連接池,沒有出現問題。

Java 分批刪除

這種情況,應該使用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個元素。

Python腳本批量刪除

對於redis的監控和清理,通常會用一些Python腳本去做,簡單、輕便。用java的話,再小的一個任務也要打包、發布,如果沒有一套完善的開發、發布的流程,還是比較麻煩的。這時候,很多人傾向於寫Python腳本,會Python的大部分人都是會Java的。

這里,還是以刪除一個set集合為例:

 1 # -*- coding:utf-8 -*-
 2 
 3 import redis
 4 
 5 def test():
 6     # StrictRedis創建連接時,這個連接由連接池管理,所以我們無需關注連接是否需要主動釋放
 7     re = redis.StrictRedis(host = "0.0.0.0",port = 6379,password = "123")
 8     key = "test"
 9     for i in range(100000):
10         re.sadd(key, i)
11 
12         cursor = '0'
13         cou = 200
14         while cursor != 0:
15             cursor,data = re.sscan(name = key, cursor = cursor, count = cou)
16             for item in data:
17                 re.srem(key, item)
18                 print cursor
19 
20                 if __name__ == '__main__':
21                     test()

后台刪除之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種場景,每種場景對應一個配置參數; 默認都是關閉。

 

1 lazyfree-lazy-eviction no
2 lazyfree-lazy-expire no
3 lazyfree-lazy-server-del no
4 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來執行。

 

參考鏈接:

http://mysql.taobao.org/monthly/2018/10/05/

https://blog.csdn.net/liu1390910/article/details/79728569

https://blog.csdn.net/wsliangjian/article/details/52329320


免責聲明!

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



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