记一次redis生产环境中,使用通配符删除key的bug

1,因业务需要,需要通过通配符删除redis指定的key,逻辑代码很简单,当时以为这个会执行很快,不应该会有问题,因为redis链接的是腾讯云的redis,不支持通配符直接删除,所以自己写了一个客户段,删除每个节点的数据。

  @PostMapping("/clearRedis2")
    public JsonResVo clearRedis2(){
        try {
            log.info("redis:" + applicationVo.host +",applicationVo.passwd" + applicationVo.passwd);
            JedisUtils jedisUtils = new JedisUtils(applicationVo.host,applicationVo.port,applicationVo.passwd);
            jedisUtils.delKeys("wpp_ds_*");
            jedisUtils.delKeys("wpp_d_*");
            jedisUtils.delKeys("wpp_h_*");
            return JsonResVo.buildSuccess("缓存清理完成");
        } catch (Exception e) {
            return JsonResVo.buildSuccess("缓存清理异常:"+e.getMessage());
        }
    }

import java.net.SocketException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;

public class JedisUtils {

  private JedisCluster jedisCluster;


  /*private String host = "xx.xx.xx.xx";
  private int port = 6379;
  private String passwd = "xxx";*/

  private String host;
  private int port;
  private String passwd;

  public JedisUtils(String hostip,int port,String passwd) {
    this.host=hostip;
    this.port=port;
    this.passwd=passwd;
    String redisString = this.host + ":" + this.port;
    String[] hostArray = redisString.split(",");
    Set nodes = new HashSet();

    // 配置redis集群
    for (String host : hostArray) {
      String[] detail = host.split(":");
      nodes.add(new HostAndPort(detail[0], Integer.parseInt(detail[1])));
    }

    this.jedisCluster =
        new JedisCluster(nodes, 10000, 10000, 5, this.passwd, new GenericObjectPoolConfig());
  }

//获取redis中指定key的值,value类型为String的使用此方法

  public Set keys(String redisKeyStartWith) throws SocketException {

    CommandSenderJedis commandSenderJedis =
        new CommandSenderJedis(this.host, this.port, this.passwd);
    return commandSenderJedis.keys(redisKeyStartWith);
  }
// 获取redis中指定key的值,value类型为String的使用此方法

  public void delKeys(String redisKeyStartWith) throws SocketException {
    CommandSenderJedis commandSenderJedis =
        new CommandSenderJedis(this.host, this.port, this.passwd);
    commandSenderJedis.delKeys(redisKeyStartWith);
  }
// 获取redis中指定key的值,value类型为String的使用此方法

  public String get(String key) {
    return this.jedisCluster.get(key);
  }

//设置redis中指定key的值,value类型为String的使用此方法

  public void set(String key, String value) {
    this.jedisCluster.set(key, value);
  }

//获取redis中指定key的值,对应的value,value类型为MAP的使用此方法

  public Map getMap(String key) {
    return this.jedisCluster.hgetAll(key);
  }

//删除redis中指定key的值项

  public void del(String key) {
    this.jedisCluster.del(key);
  }

  public static void main(String args[]) {
    try {
      rmr("liqiang_ds_*");
      rmr("liqiang_d_*");
      rmr("liqiang_h_*");
      //        rmr("TRIP:GT_TRAIN_KEY_TRAJACTORY:*");
      //        rmr("TRIP:KEY_TRAJACTORY:*");
      //      rmr("res:1:*");
      //      rmr("res:0:*");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public static void rmr(String redisKeyStartWith) throws Exception {
    /*JedisUtils jedisUtils = new JedisUtils();
    jedisUtils.delKeys(redisKeyStartWith);*/
    //    Set keys = jedisUtils.keys(redisKeyStartWith);
    //    System.out.println("=====================");
    //    keys.forEach(System.out::println);
    //    keys.forEach(
    //        k -> {
    //          jedisUtils.del(k);
    //        });
  }
}

2,后来通过打点日志,发现耗时确实在redis通配符删除那块代码,日志如下:

记一次redis生产环境中,使用通配符删除key的bug_第1张图片

3,发现删除执行需要18s,而网关那边做了一次限制,如果请求超过15秒,则网关自动断开链接,报504错误

 

4,发现问题后,通过开个线程,异步执行删除操作,问题解决,修复后的代码如下:

@PostMapping("/clearRedis")
    public JsonResVo clearRedis(){
        try {
            log.info("redis:" + applicationVo.host +",applicationVo.passwd" + applicationVo.passwd);

            Thread t1 =  new Thread(new Runnable() {
                @Override
                public void run() {
                    log.info("start...." +Thread.currentThread().getName());
                    try {
                        JedisUtils jedisUtils = new JedisUtils(applicationVo.host,applicationVo.port,applicationVo.passwd);
                        jedisUtils.delKeys("liqiang_ds_*");
                        jedisUtils.delKeys("liqiang_d_*");
                        jedisUtils.delKeys("liqiang_h_*");
                    } catch (SocketException e) {
                        e.printStackTrace();
                    }
                    log.info("end....");
                }
            });
            t1.setName("clearRedis");
            t1.start();
            log.info("3333");
            return JsonResVo.buildSuccess("缓存清理完成");
        } catch (Exception e) {
            log.info("error"+ e.getMessage());
            return JsonResVo.buildSuccess("缓存清理异常:"+e.getMessage());
        }
    }

 

总结:

 1,一直认为redis不会有问题,前期排除是不是其他问题,在这个纠结了很久,后来通过日志,发现确实是redis操作超时,所以还是要以日志为准,日志是最准确的,不能凭经验去判断

 2,通过这个问题,也发现,刚开始redis数据量比较少是,通过通配符删除数据,del key* 会比较快,随着数据量大的时候,通配符删除命令会指数级耗时增加,所以还是要精确指定key删除

你可能感兴趣的:(java,spring,boot,Redis)