【Redis】【Redis分布式锁】Spring Boot项目中使用RedisTemplate.delete() 删除指定key失败

问题现象

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

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

排查过程

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

csharp 复制代码
try {
    //要执行的方法逻辑
}finally {
    redisLock.unlock();
}

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

unlock方法的逻辑如下:

arduino 复制代码
public synchronized void unlock() {
   if (locked) {
      redisTemplate.delete(lockKey);
      locked = false;
   }
}

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

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

vbnet 复制代码
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);
}

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

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

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

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

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

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

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

问题原因

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

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

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

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

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

解决方案

typescript 复制代码
/**
 * 锁释放
 */
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加上自动过期时间。

这个我们在另一篇关于redis锁超时时间的文章单独展开来说一下。

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

1.在锁定的方法块的finally进行手动释放清除key,偶尔因为锁异常或者远程连接失败,导致redis的key值未删除成功的也在可接受范围。 2.那有可能因为锁异常导致死锁的情况,目前处理的机制是, 把过期时间作为key的value:

相关推荐
小码哥_常17 小时前
别再被误导!try...catch性能大揭秘
后端
无巧不成书021818 小时前
30分钟入门Java:从历史到Hello World的小白指南
java·开发语言
苍何19 小时前
30分钟用 Agent 搓出一家跨境网店,疯了
后端
ssshooter19 小时前
Tauri 2 iOS 开发避坑指南:文件保存、Dialog 和 Documents 目录的那些坑
前端·后端·ios
追逐时光者19 小时前
一个基于 .NET Core + Vue3 构建的开源全栈平台 Admin 系统
后端·.net
程序员飞哥19 小时前
90后大龄程序员失业4个月终于上岸了
后端·面试·程序员
zs宝来了20 小时前
Playwright 自动发布 CSDN 的完整实践
java
彭于晏Yan21 小时前
Redisson分布式锁
spring boot·redis·分布式
吴声子夜歌21 小时前
TypeScript——基础类型(三)
java·linux·typescript
GetcharZp21 小时前
Git 命令行太痛苦?这款 75k Star 的神级工具,让你告别“合并冲突”恐惧症!
后端