likes
comments
collection
share

Spring Boot项目中使用RedisTemplate.delete() 删除指定key失败

作者站长头像
站长
· 阅读数 37

问题现象

最近收到运维报过来的一个问题:

redis中有大量的redis分布式锁的key存在,不过期,一直在持续堆积。

排查过程

看了代码发现,目前实现的方案是,在方法块执行完成后手动释放锁,将锁的key执行删除。

try {
    //要执行的方法逻辑
}finally {
    redisLock.unlock();
}

很明显,这里的删除应该是没有执行成功。

unlock方法的逻辑如下:

public synchronized void unlock() {
   if (locked) {
      redisTemplate.delete(lockKey);
      locked = false;
   }
}

本地调试发现执行redisTemplate.delete返回值为false,同时这个时候执行get确实还能获取到对应的Key。

很莫名奇妙,通过排查,包括阅读redisTemplate.delete的源码,发现是序列化不一致的问题:

public Boolean delete(K key) {
    byte[] rawKey = this.rawKey(key);
    Long result = (Long)this.execute((connection) -> {
        return connection.del(new byte[][]{rawKey});
    }, true);
    return result != null && result.intValue() == 1;
}

private byte[] rawKey(Object key) {
    Assert.notNull(key, "non null key required");
    return this.keySerializer == null && key instanceof byte[] ? (byte[])((byte[])key) : this.keySerializer.serialize(key);
}

Spring Boot项目中使用RedisTemplate.delete() 删除指定key失败

Redis 的默认序列化机制 “ defaultSerializer ” ,如果没有自定义的序列化机制,则系统默认使用的是 “ org.springframework.data.redis.serializer.JdkSerializationRedisSerializer ”

这里一般大家检查两个地方:

1. RedisConfig,自定义了 Redis 的序列化机制

Spring Boot项目中使用RedisTemplate.delete() 删除指定key失败

这里要重点说一下,好多同学不管是从网上帖子拿来主义也好,还是自己积累了固定的RedisLock工具类,在不同的系统之间搬运,一般系统中前人都已自定义了 Redis 的序列化机制。

2. 在redis加锁时执行set逻辑的序列化机制

tips:目前我的系统里用的是这个逻辑,导致网上搜了一下解决方案还是没能解决。

是因为在执行lock,进行setNX操作时,重写setNX,自定义了序列化方法:

Spring Boot项目中使用RedisTemplate.delete() 删除指定key失败

问题原因

没有删除成功的根本原因:

此时不能使用原生的 “ RedisTemplate redisTemplate; ” 而需要定义为泛型的 “ RedisTemplate <Object,Object> redisTemplate; ” 。

因为当我们再次新增的 key 的时候,使用的是 “ StringRedisSerializer ”序列化机制,

但是在 delete 操作的时候是使用的是原生 API ,redis 中的 redisTemplate 默认序列化机制采用的是 “ JdkSerializationRedisSerializer ”,

这样一来,即使你使用 hasKey 方法也会发现 redis 中存在这个 key ,但是实际 hasKey 返回 false,所以就会出现删除成功,但是实际的数据依然存在 Redis 服务器上。

解决方案

/**
 * 锁释放
 */
public synchronized void unlock() {
   if (locked) {

      boolean result = this.delete(lockKey);
      if (result){
         logger.info("lockKey 删除成功,lockKey:{}",lockKey);
      }else {
         logger.info("lockKey 删除失败,lockKey:{}",lockKey);
      }
      locked = false;
   }
}

/**
 * 按key删除redis lock
 * @param key
 * @return
 */
private Boolean delete(String key) {
   byte[] rawKey = this.rawString(key);
   Long result = (Long)redisTemplate.execute((connection) -> {
      return connection.del(new byte[][]{rawKey});
   }, true);
   return result != null && result.intValue() == 1;
}

private byte[] rawString(String key) {
   StringRedisSerializer serializer = new StringRedisSerializer();
   return serializer.serialize(key);
}

我个人比较建议这种方案,setNx和delete都重写,并且单独定义序列化机制,来保持一致,

这样不再受限于系统原本的Redis自定义序列化机制。

方便迁移复用。

问题引申

问题解决了,大家还会有疑问,为什么不给redis锁的key加上自动过期时间。

目前我没有进行复杂的实现的想法是:

1.在锁定的方法块的finally进行手动释放清除key,偶尔因为锁异常或者远程连接失败,导致redis的key值未删除成功的也在可接受范围。

2.那有可能因为锁异常导致死锁的情况,目前处理的机制是, 把过期时间作为key的value:

Spring Boot项目中使用RedisTemplate.delete() 删除指定key失败

转载自:https://juejin.cn/post/7324624772978327564
评论
请登录