如何優雅地刪除Redis set集合中前綴相同的key?
Redis中有刪除單條數據的命令DEL,卻沒有批量刪除特定前綴key的指令,但我們經常遇到需要根據前綴來刪除的業務場景,那么究竟該怎么做呢?可能你一通搜索后會得到下邊的答案:
redis-cli --raw keys "prefix-*" | xargs redis-cli del
直接在linux下通過redis的keys命令匹配到所有的key,然后調用系統命令xargs來刪除,看似十全十美,實則風險巨大。這就是一顆隨時爆炸的炸彈!我們都知道Redis是單線程服務模式,使用命令 keys * 查詢key的時候會阻塞正常的業務請求,甚至造成redis宕機,所以肯定不行。因此,我們在生產環境中應當避免使用上邊的方法,那使用什么優雅的方法來解決呢?SCAN!
SCAN介紹
Redis從2.8版本開始支持SCAN命令,它是一個基於游標的迭代器,每次被調用之后都會返回一個新的游標,用戶在下次迭代時需要使用這個新游標作為SCAN命令的游標參數,以此來延續之前的迭代過程,直到服務器返回值為0的游標時,一次完整的遍歷過程就結束了。
SCAN命令的基本語法如下:
scan cursor [MATCH pattern] [COUNT count]
MATCH: 匹配規則,例如遍歷以ops-coffee-開頭的所有key可以寫成ops-coffee-*,中間包含-coffee-的可以寫成*-coffee-*cursor: 游標,
COUNT: COUNT選項的作用就是讓用戶告知迭代命令,在每次迭代中應該從數據集里返回多少元素,COUNT只是對增量式迭代命令的一種提示,並不代表真正返回的數量,例如你COUNT設置為2有可能會返回3個元素,但返回的元素數據會與COUNT設置的正相關,COUNT的默認值是10。
下面在jedis中操作scan和keys,以刪除相同前綴的key。
public void testSetDel(Jedis jedis) { try { log.info("---------------- Tests begin -----------------"); initRedisData(jedis); String givenKey = "prefix_*"; delValuesByKeys(givenKey, jedis); log.info("開始使用 scan 刪除數據 ------------ "); initRedisData(jedis); this.delSetValues(givenKey, jedis); Set<String> keys = jedis.keys(givenKey); log.info("---------------- Tests end ----------------- 是否存在相同前綴的key result = " + !CollectionUtils.isEmpty(keys)); } catch (Exception e) { log.error(" 刪除指定前綴的key對應的鍵值對 " + e); } finally { if (jedis != null) { jedis.close(); log.info("關閉jedis連接"); } } } /** * java redis 刪除指定前綴的key對應的鍵值對 * @param givenKey * @return
*/
public Boolean delSetValues(String givenKey, Jedis jedis) throws Exception { log.info("開始模糊刪除set中的數據,givenKey = " + givenKey); List<String> keys = getByScan(givenKey, jedis); log.info("即將刪除的key是 " + keys); String[] array = keys.toArray(new String[0]); jedis.del(array); return true; } /** * Jedis 刪除指定前綴的key對應的key,使用keys * @param givenKey * @return
*/
public Boolean delValuesByKeys(String givenKey, Jedis jedis) throws Exception { log.info("開始模糊刪除set中的數據,givenKey = " + givenKey); Set<String> keys = jedis.keys(givenKey); for (String key : keys) { log.info("當前 key 是 :" + key); jedis.del(key); } return true; } private void initRedisData(Jedis jedis) { jedis.set("prefix_1333", "1"); jedis.set("prefix_2KKKKK", "2"); jedis.set("prefix_3哈哈哈哈哈哈", "777"); }
// 使用 scan public List<String> getByScan(String key, Jedis jedis) { List<String> list = new ArrayList<>(); ScanParams params = new ScanParams(); params.match(key); params.count(100); String cursor = "0"; while (true) { ScanResult scanResult = jedis.scan(cursor, params); List<String> eles = scanResult.getResult(); if (!CollectionUtils.isEmpty(eles)) { list.addAll(eles); } cursor = scanResult.getStringCursor(); if ("0".equals(cursor)) { break; } } log.info(" getByScan 查到的數據集是 ============ " + list); return list; }
測試結果:
---------------- Tests begin ----------------- 開始模糊刪除set中的數據,givenKey = prefix_* 當前 key 是 :prefix_1333 當前 key 是 :prefix_3哈哈哈哈哈哈 當前 key 是 :prefix_2KKKKK 開始使用 scan 刪除數據 ------------ 開始模糊刪除set中的數據,givenKey = prefix_* getByScan 查到的數據集是 ============ [prefix_1333, prefix_2KKKKK, prefix_3哈哈哈哈哈哈] 即將刪除的key是 [prefix_1333, prefix_2KKKKK, prefix_3哈哈哈哈哈哈] ---------------- Tests end ----------------- 是否存在相同前綴的 key result = false
總結:雖然不提倡使用keys命令刪除key,但是,本文示例依然給出了示例,目的在於了解使用原理。當然,比使用scan命令刪除key效果更好的方案是直接調用Lua腳本,童鞋們自己琢磨吧!