redis 刪除大key集合的方法
redis大key,這里指的是大的集合數(shù)據(jù)類型,如(set/hash/list/sorted set),一個key包含很多元素。由于redis是單線程,在刪除大key(千萬級別的set集合)的時候,或者清理過期大key數(shù)據(jù)時,主線程忙于刪除這個大key,會導(dǎo)致redis阻塞、崩潰,應(yīng)用程序異常的情況。
一個例子
線上redis作為實時去重的一個工具,里面有6千萬的用戶guid,這么一個set集合,如果直接使用del刪除,會導(dǎo)致redis嚴(yán)重阻塞。
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,在超時時間短的苛刻情況下,顯然會發(fā)送超時,程序異常!好在,我們用的是連接池,沒有出現(xiàn)問題。
分批刪除
這種情況,應(yīng)該使用sscan命令,批量刪除set集合元素的方法。下面是一個Java的例子:
private static void test2(){ // 連接redis 服務(wù)器 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 scanResult=jedis.sscan("testset", cursor, scanParams); // 返回0 說明遍歷完成 cursor = scanResult.getStringCursor(); List 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()+"條數(shù)據(jù),耗時: "+(t2-t1)+"毫秒,cursor:"+cursor); } }catch (JedisException e){ e.printStackTrace(); }finally { if(jedis != null){ jedis.close(); } } }
對于其它集合,也有對應(yīng)的方法。
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的監(jiān)控和清理,通常會用一些Python腳本去做,簡單、輕便。用java的話,再小的一個任務(wù)也要打包、發(fā)布,如果沒有一套完善的開發(fā)、發(fā)布的流程,還是比較麻煩的。這時候,很多人傾向于寫Python腳本,會Python的大部分人都是會Java的。
這里,還是以刪除一個set集合為例:
# -*- coding:utf-8 -*- import redis def test(): # StrictRedis創(chuàng)建連接時,這個連接由連接池管理,所以我們無需關(guān)注連接是否需要主動釋放 re = redis.StrictRedis(host = "0.0.0.0",port = 6379,password = "123") key = "test" for i in range(100000): re.sadd(key, i) cursor = '0' cou = 200 while cursor != 0: cursor,data = re.sscan(name = key, cursor = cursor, count = cou) for item in data: re.srem(key, item) print cursor if __name__ == '__main__': test()
后臺刪除之lazyfree機制
為了解決redis使用del命令刪除大體積的key,或者使用flushdb、flushall刪除數(shù)據(jù)庫時,造成redis阻塞的情況,在redis 4.0引入了lazyfree機制,可將刪除操作放在后臺,讓后臺子線程(bio)執(zhí)行,避免主線程阻塞。
lazy free的使用分為2類:第一類是與DEL命令對應(yīng)的主動刪除,第二類是過期key刪除、maxmemory key驅(qū)逐淘汰刪除。
主動刪除
UNLINK命令是與DEL一樣刪除key功能的lazy free實現(xiàn)。唯一不同時,UNLINK在刪除集合類鍵時,如果集合鍵的元素個數(shù)大于64個(詳細后文),會把真正的內(nèi)存釋放操作,給單獨的bio來操作。
127.0.0.1:7000> UNLINK mylist (integer) 1 FLUSHALL/FLUSHDB ASYNC 127.0.0.1:7000> flushall async //異步清理實例數(shù)據(jù)
被動刪除
lazy free應(yīng)用于被動刪除中,目前有4種場景,每種場景對應(yīng)一個配置參數(shù); 默認(rèn)都是關(guān)閉。
lazyfree-lazy-eviction no lazyfree-lazy-expire no lazyfree-lazy-server-del no slave-lazy-flush no
lazyfree-lazy-eviction
針對redis內(nèi)存使用達到maxmeory,并設(shè)置有淘汰策略時;在被動淘汰鍵時,是否采用lazy free機制;
因為此場景開啟lazy free, 可能使用淘汰鍵的內(nèi)存釋放不及時,導(dǎo)致redis內(nèi)存超用,超過maxmemory的限制。此場景使用時,請結(jié)合業(yè)務(wù)測試。
lazyfree-lazy-expire
針對設(shè)置有TTL的鍵,達到過期后,被redis清理刪除時是否采用lazy free機制;
此場景建議開啟,因TTL本身是自適應(yīng)調(diào)整的速度。
lazyfree-lazy-server-del
針對有些指令在處理已存在的鍵時,會帶有一個隱式的DEL鍵的操作。如rename命令,當(dāng)目標(biāo)鍵已存在,redis會先刪除目標(biāo)鍵,如果這些目標(biāo)鍵是一個big key,那就會引入阻塞刪除的性能問題。 此參數(shù)設(shè)置就是解決這類問題,建議可開啟。
slave-lazy-flush
針對slave進行全量數(shù)據(jù)同步,slave在加載master的RDB文件前,會運行flushall來清理自己的數(shù)據(jù)場景,
參數(shù)設(shè)置決定是否采用異常flush機制。如果內(nèi)存變動不大,建議可開啟??蓽p少全量同步耗時,從而減少主庫因輸出緩沖區(qū)爆漲引起的內(nèi)存使用增長。
expire及evict優(yōu)化
redis在空閑時會進入activeExpireCycle循環(huán)刪除過期key,每次循環(huán)都會率先計算一個執(zhí)行時間,在循環(huán)中并不會遍歷整個數(shù)據(jù)庫,而是隨機挑選一部分key查看是否到期,所以有時時間不會被耗盡(采取異步刪除時更會加快清理過期key),剩余的時間就可以交給freeMemoryIfNeeded來執(zhí)行。
作者:柯廣的網(wǎng)絡(luò)日志
微信公眾號:Java大數(shù)據(jù)與數(shù)據(jù)倉庫