Redis分布式锁的超时问题及解决

 更新时间:2024年05月11日 09:43:22   作者:阿飞Sirx  
这篇文章主要介绍了Redis分布式锁的超时问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

Redis分布式锁的超时

Redis的分布式锁并不能解决超时问题,如果在加锁和释放锁之间的逻辑执行得太长,以至于超出了锁的超时限制,就会出现问题,因为这时候第一个线程持有的锁过期了,临界区的逻辑还没有执行完,而同时第二个线程就提前持有了这把锁,导致临界区代码不能得到严格串行执行

为了避免这个问题,分布式锁不要用于较长时间的任务,如果真的偶尔出现了问题,造成的数据小错乱,可能需要人工介入解决

有一个稍微安全一点的方案,是将set指令的value参数设置为一个随机数,释放锁时先匹配随机数是否一致,然后在删除key,这是为了确保当前线程占有的锁不会被其他线程释放,除非这个锁是自动超时,但是匹配value,和删除ke y不是一个原子操作,所以只是相对安全

分布式锁失效问题

分布式锁

1.1集群下的锁失效问题

Synchronized中的重量级锁,底层就是基于锁监视器(Monitor)来实现的。

简单来说就是锁对象头会指向一个锁监视器,而在监视器中则会记录一些信息

比如:

  • _owner:持有锁的线程
  • _recursions:锁重入次数

因此每锁一个对象。都会指向一个锁监视器,但是每个锁监视器同一时刻只能被一个线程持有,这样再单机模式下,不同服务的JVM当然不能通信,这样就会出现锁失效问题。所以在分布式环境下就不能使用Synchronized。所以分布式锁一定要满足多JVM都能访问并且互斥的条件。

能满足上述特征的组件有很多,因此实现分布式锁的方式也非常多,例如:

  • 基于MySQL
  • 基于Redis
  • 基于Zookeeper
  • 基于ETCD

常见的最广泛的应用解决方式就是基于Redis实现的分布式锁。

1.2.简单分布式锁

先来弄清原理,Redis的setnx命令是基于string操作的。

命令如下:

SETNX key value

当且仅当这个key不存在时setnx才能执行成功,并且返回1,其它情况都会执行失败,并且返回0.我们就可以认为返回值是1就是获取锁成功,返回值是0就是获取锁失败,实现互斥效果。

当业务执行完成时,我们只需要通过DEL key命令删除这个即可释放锁。这个时候其它线程又可以再次获取锁(执行setnx成功)了。

不过我们要考虑一种极端的场景。获取成功后,还没释放锁时突然宕机,那么释放锁的动作就不会被执行这就出现了死锁。

# 获取锁,并记录持有锁的线程
SETNX lock thread1
# 设置过期时间,避免死锁
EXPIRE lock 20我们可以利用Redis的KEY过期时间机制,在获取锁时给锁添加一个超时时间:    

但是这显然是两条独立的命令,如果我执行完setnx后宕机,过期时间还未设置,死锁问题又出现了!

为了保证两条命令的原子性使用SET lock thread1 NX EX 20 就能保证原子性。对应的api如下。

@RequiredArgsConstructor
public class RedisLock {
 
    private final String key;
    private final StringRedisTemplate redisTemplate;
 
    /**
     * 尝试获取锁
     * @param leaseTime 锁自动释放时间
     * @param unit 时间单位
     * @return 是否获取成功,true:获取锁成功;false:获取锁失败
     */
    public boolean tryLock(long leaseTime, TimeUnit unit){
        // 1.获取线程名称
        String value = Thread.currentThread().getName();
        // 2.获取锁
        Boolean success = redisTemplate.opsForValue().setIfAbsent(key, value, leaseTime, unit);
        // 3.返回结果
        return BooleanUtils.isTrue(success);
    }
 
    /**
     * 释放锁
     */
    public void unlock(){
        redisTemplate.delete(key);
    }
}

1.3.分布式锁的问题

1.3.1.锁误删问题

第一个问题就是锁误删问题,目前释放锁的操作是基于DEL,但是在极端情况下会出现问题。

假设场景,线程1获取锁成功完成执行,准备释放锁。

 但因为某些原因导致释放锁的操作被阻塞,直到超时放锁

 

这时因为线程1被超时释放,所以线程2拿到了锁。这时候线程1醒了,给线程2的锁删了。

但此时线程2还是在执行中,线程3在来的时候就会认为现在没人拿锁,于是多个线程再次并发执行,并发安全就可能再出现。

为了解决这种场景,我们可以在删除锁之前判断当前锁的中保存的是否是当前线程标示,如果不是则证明不是自己的锁,则不删除;如果锁标示是当前线程,则可以删除。

1.3.2.超时释放问题

  • 加上了锁标识判断。
  • 可以避免大多数场景下的锁误删问题,但是还是有极端情况。
  • 比如我线程1那所执行完并且判断完挂了,直到超时放锁。
  • 这样线程2来的时候是可以获取锁的,线程2去执行业务中,线程1醒了,因为已经通过了校验,我给你锁删了,又发生了锁误删问题。

总结起来,根源就在于判断锁标识和删除锁是两个动作,又不符合原子性了。

1.3.3分布式锁的其他问题

  • 锁的重入问题:同一个线程多次获取锁的场景,目前不支持,可能会导致死锁
  • 锁失败的重试问题:获取锁失败后要不要重试?目前是直接失败,不支持重试
  • Redis主从的一致性问题:由于主从同步存在延迟,当线程在主节点获取锁后,从节点可能未同步锁信息。如果此时主宕机,会出现锁失效情况。此时会有其它线程也获取锁成功。从而出现并发安全问题。

对应的解决方案也有,就是比较麻烦

  • 原子性问题:可以利用Redis的LUA脚本来编写锁操作,确保原子性
  • 超时问题:利用WatchDog(看门狗)机制,获取锁成功时开启一个定时任务,在锁到期前自动续期,避免超时释放。而当服务宕机后,WatchDog跟着停止运行,不会导致死锁。
  • 锁重入问题:可以模拟Synchronized原理,放弃setnx,而是利用Redis的Hash结构来记录锁的持有者以及重入次数,获取锁时重入次数+1,释放锁是重入次数-1,次数为0则锁删除
  • 主从一致性问题:可以利用Redis官网推荐的RedLock机制来解决

我们自己手写解决不仅繁琐,而且实现起来耗费时间,所以我们可以使用开源的框架来实现分布式锁。其中比较完善的一个第三方组件就是Redisson

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 使用Grafana监控Redis的操作方法

    使用Grafana监控Redis的操作方法

    这篇文章主要介绍了使用Grafana监控Redis,号称下一代可视化监控系统,结合SpringBoot使用,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-04-04
  • redis持久化AOF和RDB的区别及解决各个场景问题示例

    redis持久化AOF和RDB的区别及解决各个场景问题示例

    这篇文章主要为大家介绍了redis持久化AOF和RDB的区别及解决各个场景问题示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • Redis BloomFilter实例讲解

    Redis BloomFilter实例讲解

    这篇文章主要介绍了Redis BloomFilter实例。BloomFilter不需要存储key,节省空间,在某些对保密要求非常严格的场合有优势。想要进一步了解BloomFilter运用实例的小伙伴可以了解一下这篇文章
    2021-09-09
  • redis的hGetAll函数的性能问题(记Redis那坑人的HGETALL)

    redis的hGetAll函数的性能问题(记Redis那坑人的HGETALL)

    这篇文章主要介绍了redis的hGetAll函数的性能问题,需要的朋友可以参考下
    2016-02-02
  • 详解RedisTemplate下Redis分布式锁引发的系列问题

    详解RedisTemplate下Redis分布式锁引发的系列问题

    这篇文章主要介绍了详解RedisTemplate下Redis分布式锁引发的系列问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • redis lua脚本实战秒杀和减库存的实现

    redis lua脚本实战秒杀和减库存的实现

    本文主要是学习一下redis lua脚本的编写,以及在redisson这个redis客户端中是怎样使用的,实战一下秒杀场景redis减库存lua脚本的编写,并伪真实环境压测查看效果。感兴趣的可以了解一下
    2021-11-11
  • Redis可视化工具Redis Desktop Manager的具体使用

    Redis可视化工具Redis Desktop Manager的具体使用

    本文主要介绍了Redis可视化工具Redis Desktop Manager的具体使用,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12
  • redis使用zset实现延时队列的示例代码

    redis使用zset实现延时队列的示例代码

    本文主要介绍了redis使用zset实现延时队列的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • Redis教程(六):Sorted-Sets数据类型

    Redis教程(六):Sorted-Sets数据类型

    这篇文章主要介绍了Redis教程(六):Sorted-Sets数据类型,本文讲解了Sorted-Sets数据类型概述、相关命令列表、命令使用示例、应用范围等内容,需要的朋友可以参考下
    2015-04-04
  • Redis持久化AOF示例详解

    Redis持久化AOF示例详解

    AOF(Append-Only File)用于将Redis服务器收到的写操作追加到日志文件,通过该机制可以保证服务器重启后依然可以依靠日志文件恢复数据,这篇文章主要介绍了Redis持久化AOF详解,需要的朋友可以参考下
    2023-12-12

最新评论