您的位置:首页 > 数据库 > Redis

使用spring-data-redis对redis集群进行KEY模糊删除的几种方式

2020-02-05 23:03 597 查看

 环境:虚拟机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全部清除了

优势:

  1. redis集群性能未受到任何影响,set get del 等操作都正常
  2. 不需要将获取到的key都保存在set中再取出来删,节约服务器内存
  3. 代码简单

虽然执行时间很长,但是这种缓和处理占用资源很少,对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都设置过期时间。这样就用不着批量删除了。

  • 点赞
  • 收藏
  • 分享
  • 文章举报
Elliotzzz 发布了4 篇原创文章 · 获赞 0 · 访问量 145 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: