【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:

相关推荐
原野心存5 分钟前
java基础进阶——继承、多态、异常捕获(2)
java·java基础知识·java代码审计
进阶的架构师10 分钟前
互联网Java工程师面试题及答案整理(2024年最新版)
java·开发语言
黄俊懿10 分钟前
【深入理解SpringCloud微服务】手写实现各种限流算法——固定时间窗、滑动时间窗、令牌桶算法、漏桶算法
java·后端·算法·spring cloud·微服务·架构
木子020418 分钟前
java高并发场景RabbitMQ的使用
java·开发语言
夜雨翦春韭30 分钟前
【代码随想录Day29】贪心算法Part03
java·数据结构·算法·leetcode·贪心算法
2401_8574396937 分钟前
“衣依”服装销售平台:Spring Boot技术应用与优化
spring boot·后端·mfc
大霞上仙1 小时前
jmeter学习(1)线程组与发送请求
java·学习·jmeter
Jerry.ZZZ1 小时前
系统设计,如何设计一个秒杀功能
后端
笃励1 小时前
Java面试题二
java·开发语言·python
易雪寒2 小时前
IDEA在git提交时添加忽略文件
java·git·intellij-idea