Redis实现分布式锁的场景分析

引言

  • 通常情况下,我们使用redis用来缓存热点数据,生成唯一主键,redis限流,其实它还有一个重要的功能,就是实现分布式锁。关于redis实现分布式锁,有多种场景,在不同的架构模式下,实现分布式锁的方式也不一样。一般的架构模式有单节点架构,多节点架构(常见的一主多从,多主多从或去中心化的架构),今天我在这里好好讲述一下,redis在分布式系统中,如何实现分布式锁来防止并发带来的安全问题。

单节点部署场景

  • 举例说明,系统A和系统B是两个部署在不同节点的相同应用(集群部署),这时客户端请求传来,两个系统都受到了请求,并且该请求是对数据表进行插入操作,如果这个时候不加锁来控制,可能会导致数据库新增两条记录,这时系统也不能允许的,由于是在不同应用内,在单个应用内加JVM级别的锁,另一个应用是感知不到的,这时需要用到分布式锁。
  • 接下来我们看看这种场景如何实现安全的分布式锁,由于是单节点部署场景,我们可以用setnx命令,以请求的唯一主键作为key,由于该操作是原子操作,当系统A设值成功后,系统B是无法设置成功的, 这时A就可以进行查询并插入操作,操作数据库完成后,删除key,此时系统B才能设值成功,但是由于查询到数据库有记录,所以并不会插入数据,这样就解决了该问题。但是这里会有个问题,如果redis挂机了,这里的锁不是永远都不释放了吗, 所以为了解决这个问题,redis提供了set命令,可传入超时时间的,那么在指定的时间范围内,如果没有释放锁,则该锁自动过期。如果执行时间超过超时时间呢,比如系统A还未执行完任务,就释放了锁,系统B接着执行任务,这时,系统A执行完了,把锁删掉(此时删除的时系统B获取的锁)。
    • 方案一: 为了避免这种情况,在del锁之前可以做一个判断,验证key对应的value是不是自己线程的ID.如果要考虑原子性问题,可以使用Lua脚本来实现,保证验证和删除的原子性。
    • 方案二:我们可以让获得锁的线程开启一个守护线程,用来给快要过期的锁加长超时时间。当系统A中的线程执行完任务,再显式关掉守护线程。

多节点部署场景

  • 多节点部署,是指redis不止一个节点,在一主多从,或者多主多从,以及去中心化架构上,上面的分布式锁的解决方案,就可能会出现由主节点宕机导致锁失效问题。比如说,R1为主节点,R2, R3是从节点,如果系统A刚获取到锁,还未开始执行时,R1还没把key同步到其他机器时,就宕机了,,此时R2由从节点升级为主节点,但是系统A获取的锁就失效了,此时系统B如果重新获取锁,那么就会导致并发问题,为了解决这个问题,redis给我们提供了RedLock,用来解决多节点部署的分布式锁如何安全获取问题。
  • 在redis分布式环境中,我们假设有N个Redis master.这些节点相互之间时独立的,不存在主从复制或者其他集群协调机制。我们确保将在N个实例上使用在Redis单实例下相同的方式获取和释放锁。现在我们假设有5个Redis master节点。为了获取到锁,客户端应该执行以下操作:
    • 获取当前Unix时间,以毫秒为单位。
    • 依次尝试从5个实例,使用相同的key和具有唯一性的value获取锁。当向Redis请求获取锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间,是为了避免服务器端Redis已经挂掉,客户端还在一直等待响应结果。如果没有及时响应,客户端尽快去另一个Redis实例请求获取锁。
    • 客户端使用当前时间减去开始获取锁的时间,就得到获取锁使用的时间,当且仅当从大多数(N/2 + 1, 这里是3个节点)的Redis节点都取到锁,并且使用的时间小于锁失效的时间,锁才算获取成功。key的真实有效时间等于有效时间减去获取锁所使用的时间。
    • 如果因为某些原因,获取锁失败(没有在至少N/2 + 1个Redis实例上获取到锁),客户端应该在所有的Redis实例上进行解锁(即便是某些Redis实例根本没有加锁成功,防止某些节点获取到锁但是客户端没有得到响应而导致一段时间不能重新获取锁)
    • 注意: value一定要具有唯一性,防止误删锁,比如可以使用UUID+threadId
  • 上面这种分布式锁的实现方案,有个响亮的名号:RedLock,Reddison有对RedLock算法做了封装,我们可以直接使用其中的API。

RedLock缺陷

  • 其实这里不是我分析出来的,而是有位大牛对RedLock提出了质疑,我只不过是简述一下而已。
  • 首先来说,使用上很笨重,提升了系统的维护成本。
  • 对于正确性严格要求的场景(比如订单), redLock也不能保证锁的正确性。
    • 解决方法:为锁增加一个token-fencing,获取锁的时候,还需要获取一个递增的token,在提交数据的时候,需要判断token的大小,如果token小于上一次提交的token,则提交被拒绝。 可以理解为这是一个乐观锁。(附言:都加版本号来控制了,还要分布式锁干啥
  • RedLock是一个严重依赖系统时钟的分布式系统,如果某个Redis Master的系统时间发生了错误,造成了它持有的锁提前过期被释放。Client 1 在A,B,C, D, E五个节点中给,获取了A, B,C三个节点的锁,我们认为他持有了锁,这时由于系统B的时间比别的系统快,B就会优先释放锁,Client 2就可以从B,D,E三个节点中获取到锁,这样就造成了分布式系统中两个client同时持有锁。
  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值