分布式锁

线程锁

相信大家在写代码中遇到多线程有资源竞争关系时,第一反应是加锁,Synchronized,Lock,这种属于线程锁,只在单个JVM中有效。如果在分布式系统中又是个什么情况呢?

分布式锁

在这里插入图片描述
现在假设有这样一种场景,有3台服务器组成一个集群,有一个商品,库存数量为1。现在有6个客人都想购买这个商品,假设他们同时都发送预定请求。
假设现在6个请求同时到达各自的服务器,同时假设每台服务器的每个线程处理速度一致,则6个线程拿到的库存数量都是1,都是可以售卖的,然后6个人都能成功下单。问题来了,实际商品库存只有1份,但是售卖了6份。

那问题出现在哪里呢?单台服务器中,同时能下单的不应该有两个线程,商品只有一件,很自然的需要对库存进行加锁:
在这里插入图片描述
同样的是同时有6个客人一同发送请求到服务器,但是每台服务器只接收两个请求中的一个。是否彻底解决了问题呢?很明显并没有,3台服务器中的3个线程,拿到的库存量都还是1,也就是说还是有3个客人是可以下单成功的。

这个时候,我们就需要引入分布式锁了:
在这里插入图片描述
3台服务器获取库存时,只有其中一个可以成功读取库存,并加上这把锁,其他两台服务器加锁失败,不能下单。

Redis分布式锁

简单讲述下Redis为什么可以做分布式锁:

  • Redis业务处理是单线程,不存在资源竞争
  • setnx等命令特性,必须不存在key才能插入成功,如图:
    在这里插入图片描述
    我们以StringRedisTemplate来操作Redis为例:
boolean locked =StringRedisTemplate.opsForValue().setIfAbsent("orderId","driverId");

我们通过代码尝试向Redis写入key为“orderId”的数据,如果当前线程写入成功,则代表我们当前拿到了分布式锁,下面可以执行业务代码了。其他线程在此情况下也尝试写入key为“orderId”的数据时,因Redis里面已经存在了这条记录,不会写入成功,则可认为没有获取到分布式锁,不能执行后续的业务逻辑。因而可以避免出现超售的情况。

那现在是否就没有问题了呢?
我们下面看下系统的架构:
在这里插入图片描述
假设用户A(Thread1)获取库存,将key为“orderId”的数据根据hash分槽后落在了Master2上,并且也写入成功,此时Thread1拿到了库存的锁。
现有用户B(Thread2)尝试获取库存锁,尝试向Redis写入数据。
下面我们分析一下一些极端情况:

  1. Thread1在向Redis写入数据时,没有指定数据失效时间,而Thread1在获得了库存锁之后,线程意外退出。Thread2尝试获取库存锁,因数据写入时没有指定失效时间,一直存活于Redis中,形成死锁,Thread2一直获取不到,只有等Redis主动清理低热度数据时才有可能被清理掉,也许几年之后,Thread2才能获得库存锁。
  2. Master2在成功写入数据之后告诉Thread1写入成功,Thread1获得库存锁,执行后续逻辑。但是此时Master2宕机了,新写入的key为“orderId”的数据还没有来得及同步到salve,通过哨兵灾备,其中一台salve成为新的Master,请注意此时该新的Master是没有key为“orderId”的数据的,此时Thread2尝试获取库存锁,是可以写入成功的,这样Thread2也会获得库存锁,也可以执行后续逻辑。
  3. 假设Thread1在写入数据时,有设置失效时间,如1分钟,但是Thread1执行后续逻辑需要2分钟,也就是说Thread1在执行后续逻辑期间,其实锁已经失效了,而在这段时间内,Thread2尝试获取库存锁,一样是可以获得锁,一样是可以执行后续逻辑。
  4. 在情况2下,Thread1和Thread2都获取到了锁,假设Thread2先执行完毕,此时Thread1还没有执行完,Thread2需要释放锁资源,当delete了key为“orderId”的数据后,对于Thread1而言,相当于锁失效了,也就是说Thread2把别人的锁给释放了。

所以我们在自己用Redis写分布式锁的时候,需要setnx+lua结合的方式,以lua脚本方式实现写数据和设置超时时间。另外需要在key,value中添加当前加锁人的标识,在释放锁时需要判断当前锁是不是自己添加的,避免释放了别人的锁。
对于情形3,需要添加一个守护线程,如果当前线程还没有执行完毕,锁已经快失效,需要将锁的失效时间延长。
对于情形4,由于Redis的主从架构采用异步复制,虽然提高了单点可靠性,但是在作为分布式锁方面反倒成了软肋。

RedLock

为了解决上面的问题,Redis的作者antirez提出了一个更高级的基于Redis的分布式锁:RedLock
RedLock基于N个独立的Redis节点实现分布式锁:

  • 在任何时候,只有一个client
  • 利用key的存活时间避免死锁
  • 只要多数的Redis节点正常运行,就能对外服务

获取锁步骤如下:

  1. 获取当前时间(毫秒)
  2. 按顺序依次向N个Redis节点执行获取锁操作。也即尝试写入key&value,以原子方式设置有效期。同时,为了保证在某个Redis节点不可用的时候能够向下一个节点获取锁,这个获取锁的操作需要设置一个超时时间time out,它需要远小于锁的有效期,这样当前Redis节点获取锁失败后,可以向下一个节点继续请求尝试获取锁。
  3. 计算整个获取锁的过程中花了多少时间,也即用当前时间减去步骤一里面记录的时间。
    如果client从大多数Redis节点(>=N/2+1)成功获取到锁,并且整个获取锁过程消耗的时间没有超过锁的有效期,那么可以认为最终获取锁成功,否则认为获取锁失败
  4. 如果获取锁成功,需要在当前锁剩余有效期的基础上,将步骤3计算出来的消耗时间,减去。
  5. 如果获取锁失败,那么client应该立即向所有Redis节点发起释放锁操作

Redisson

Redisson封装了对RedLock算法的实现。

SQL锁

与Redis锁类似,向数据库中写入一条特定的数据,写入成功即可认为获得锁。

zookeeper锁

后面再写

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值