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/