使用spring-data-redis对redis集群进行KEY模糊删除的几种方式
环境:虚拟机3主3从
准备工作:
向redis cluster写入100W条数据
[code] public void multiSet() { long start = System.currentTimeMillis(); //定义key对应的value Consumer consumer = new Consumer(); consumer.setId(11111L); consumer.setName("multiSet-consumer-"); consumer.setPhone("13011112222"); consumer.setBirthday(new Date()); byte[] trueValue = RedisSerializer.json().serialize(consumer); //start for (long k = 1; k <= 100; k++) { long kk = k * 10000; System.out.println(String.format("第【%s】次批量添加", k)); redisTemplate.executePipelined((RedisCallback<Object>) connection -> { for (long x = kk - 9999; x <= kk; x++) { String key = "multiSet-Key-" + x; byte[] trueKey = RedisSerializer.string().serialize(key); connection.set(trueKey, trueValue); System.out.println("*****" + x + "*****"); } return null; }); } long end = System.currentTimeMillis(); System.out.println(String.format("总计耗时:【%s】秒", (end - start) / 1000)); }
运行结果:
集群状态:
一、使用del xxx*
[code] public void deleteOne() { Boolean result = redisTemplate.delete("multiSet-Key-*"); System.out.println("***********" + result); }
运行结果:
此方法无效,说明redis不支持del xxx*
二、使用keys方法拿出所有key,然后delete所有key
[code] public void deleteTwo() { Long result = redisTemplate.delete(redisTemplate.keys("multiSet-Key-*")); System.out.println("***********" + result); }
运行结果:命令执行超时异常,redis中所有key依然健在
集群状态:
注意:
1、如果key很少,是不会报异常的,而且key会被删除
2、但是无论删除的KEY少还是多,方法都会一直阻塞住无法结束,就是keys()方法导致的
3、同时redis集群性能严重下降,get set耗时较长
此做法不OK
三、使用lua脚本,用scan分批次扫描出key,然后进行删除
[code] public void deleteThree() { long start = System.currentTimeMillis(); String luaScript = "while(true) " + "do local x = redis.call('scan','0','match',KEYS[1]) " + "for i,k in ipairs(x[2]) " + "do redis.call('del',k) " + "end " + "if(x[1] == '0') then break end " + "end"; DefaultRedisScript defaultRedisScript = new DefaultRedisScript(luaScript); ScriptExecutor scriptExecutor = new DefaultScriptExecutor(redisTemplate); scriptExecutor.execute(defaultRedisScript, Arrays.asList("multiSet-Key-*")); long end = System.currentTimeMillis(); System.out.println(String.format("总计耗时:【%s】秒", (end - start) / 1000)); }
运行结果:命令执行超时异常,redis集群状态异常
Exception in thread "main" org.springframework.dao.QueryTimeoutException: Redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException: Command timed out after 1 minute(s)
有个redis master已经不可用了
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
192.168.8.132已挂
集群状态:出问题了
注意:如果key较少
1、是不会报异常的,而且key会被删除
2、但是只能删除一个master上的数据,其他master上的匹配KEY没有被删
3、被删的那个master执行脚本期间,不可读写
此做法不OK
集群恢复解决方法:
1、在出问题的master执行redis命令script kill
2、在出问题的master执行redis命令shutdown nosave
3、集群所有机器执行killall redis-server
4、集群所有机器重新启动redis
四、使用 scan + pipeline 批量删除
[code] public void deleteFour() { long start = System.currentTimeMillis(); //获取所有KEY怼到set中 RedisCallback<Set<String>> redisCallback = connection -> { Set<String> keySet = new HashSet<>(); ScanOptions.ScanOptionsBuilder scanOptionsBuilder = ScanOptions.scanOptions(); scanOptionsBuilder.match("multiSet-Key-*"); ScanOptions scanOptions = scanOptionsBuilder.build(); Cursor<byte[]> cursor = connection.scan(scanOptions); int count = 0; while (cursor.hasNext()) { keySet.add(RedisSerializer.string().deserialize(cursor.next())); count++; } System.out.println("获取到的KEY总量为:" + count); return keySet; }; Set<String> keySet = (Set<String>) redisTemplate.execute(redisCallback); //遍历set中的KEY,进行分批次删除,一次10000条 List<String> list = new ArrayList<>(); for(String s : keySet){ if(list.size() == 10000){ redisTemplate.executePipelined((RedisCallback<Object>) connection -> { for(String key : list){ connection.del(RedisSerializer.string().serialize(key)); } return null; }); list.clear(); } list.add(s); } if(list.size() > 0){ redisTemplate.executePipelined((RedisCallback<Object>) connection -> { for(String key : list){ connection.del(RedisSerializer.string().serialize(key)); } return null; }); } long end = System.currentTimeMillis(); System.out.println(String.format("总计耗时:【%s】秒", (end - start) / 1000)); System.out.println("清除所有指定前缀KEY执行完毕--------------------"); }
运行结果:
TIPS1、如果所有redis服务当前不是很忙,同时管道批量操作的数量不是很大,则一切OK;其他线程 set get del等都正常;
TIPS2、删除期间,当有其他线程在get值的时候,有一定几率产生异常;同时其他线程操作也会发生异常
此方法虽然效率高,但是还是存在一定风险.
五、使用 scan + redisTemplate 一个个删
[code] public void deleteFive() { long start = System.currentTimeMillis(); RedisCallback<Set<String>> redisCallback = connection -> { Set<String> keySet = new HashSet<>(); ScanOptions.ScanOptionsBuilder scanOptionsBuilder = ScanOptions.scanOptions(); scanOptionsBuilder.match("multiSet-Key-*"); ScanOptions scanOptions = scanOptionsBuilder.build(); Cursor<byte[]> cursor = connection.scan(scanOptions); int count = 0; while (cursor.hasNext()) { keySet.add(RedisSerializer.string().deserialize(cursor.next())); count++; } System.out.println("获取到的KEY总量为:" + count); return keySet; }; Set<String> keySet = (Set<String>) redisTemplate.execute(redisCallback); Iterator<String> it = keySet.iterator(); while (it.hasNext()) { redisTemplate.delete(it.next()); } long end = System.currentTimeMillis(); System.out.println(String.format("总计耗时:【%s】秒", (end - start) / 1000)); System.out.println("清除所有指定前缀KEY执行完毕--------------------"); }
运行结果:
集群状态:
确实清除了所有KEY
redis集群性能未受到任何影响,set get del 等操作都正常
但是KEY怼在一个set里面还是不太合适
六、使用 scan + connection 一边获取一边删除(最优方案)
[code] public void deleteSix() { long start = System.currentTimeMillis(); RedisCallback<Long> redisCallback = connection -> { ScanOptions.ScanOptionsBuilder scanOptionsBuilder = ScanOptions.scanOptions(); scanOptionsBuilder.match("multiSet-Key-*"); ScanOptions scanOptions = scanOptionsBuilder.build(); Cursor<byte[]> cursor = connection.scan(scanOptions); long count = 0; while (cursor.hasNext()) { count += connection.del(cursor.next()); //count为所有删除成功次数的总和 System.out.println("******" + count + "******"); } return count; }; Long count = (Long) redisTemplate.execute(redisCallback); long end = System.currentTimeMillis(); System.out.println("被删除KEY的数量为:" + count); System.out.println(String.format("总计耗时:【%s】秒", (end - start) / 1000)); }
运行结果:
集群状态:
发现KEY全部清除了
优势:
- redis集群性能未受到任何影响,set get del 等操作都正常
- 不需要将获取到的key都保存在set中再取出来删,节约服务器内存
- 代码简单
虽然执行时间很长,但是这种缓和处理占用资源很少,对redis集群不会造成压力。
方法重新整理一下:
[code] public boolean deleteSix(String pattern) { RedisCallback<Boolean> redisCallback = connection -> { try{ ScanOptions.ScanOptionsBuilder scanOptionsBuilder = ScanOptions.scanOptions(); scanOptionsBuilder.match(pattern); ScanOptions scanOptions = scanOptionsBuilder.build(); Cursor<byte[]> cursor = connection.scan(scanOptions); while (cursor.hasNext()) { connection.del(cursor.next()); } return true; }catch (Exception e){ return false; } }; return (boolean) redisTemplate.execute(redisCallback); }
总结:
1、如果想迅速进行批量删除,可采用方案4(redisTemplate scan + pipeline),云服务器的性能强悍,
应该不会那么容易卡顿吧...SO 管道流操作可以发挥它的优势。
2、对于缓存,可能有批量清除的需求,此时可以使用方案6,让他删个几十分钟也不过分。
And,缓存我推荐使用自定义缓存,非永久化的数据每个KEY都设置过期时间。这样就用不着批量删除了。
- 点赞
- 收藏
- 分享
- 文章举报
- 使用spring-data-redis配置集群错误
- spring data jpa 想使用EntityManager 对sql 进行处理四种方式(第四种本人改写的)
- 使用spring-data-redis兼容redis单机和集群操作
- 【Spring】(二)使用Spring进行事务管理的几种方式
- 使用spring-data-redis进行对redis的操作,封装的一些操作方法
- 使用spring-data-redis进行对redis的操作,封装的一些操作方法
- 使用spring-session-data-redis来进行session共享
- spring-data-redis 使用 protobuf进行序列化和反序列
- 使用Spring-data进行Redis操作
- 在Linux上(我的服务器是Ubuntu) 用redis-trib.rb搭建redis集群,并在客户端使用spring-data-redis连接(亲测)
- spring data redis 集群(sentinel实现)和simple spring memcached分布式初使用
- Spring 使用注解方式进行事务管理
- 使用spring-data-redis开发redis应用
- 使用Spring BlazeDS Integration进行数据推送服务(push data)
- Spring 使用注解方式进行事务管理
- spring-data-redis 使用
- jedis,spring-redis-data 整合使用,版本问题异常以及解决。
- Chapter 1. 使用Spring进行数据访问(Data Access With Spring)
- Spring 使用注解方式进行事务管理
- 【Redis基础】SSH 中Spring-data-redis使用体验