谈谈分布式锁

本文探讨了分布式锁的使用目的,包括两类操作共享资源的情况。详细介绍了Redis和ZooKeeper实现分布式锁的不同方案,如Redis的setnx和Redisson框架,以及ZooKeeper的临时顺序节点加watch机制。同时,文章比较了两者在性能和高并发场景下的优缺点,并提出了分布式锁的高并发优化策略,如分段加锁和避免使用分布式锁的方案。
摘要由CSDN通过智能技术生成

1、为什么要使用分布式锁

使用分布式锁的目的,无外乎就是保证同一时间只有一个客户端可以对共享资源进行操作。
根据锁的用途还可以细分为以下两类:

  1. 允许多个客户端操作共享资源
    这种情况下,对共享资源的操作一定是幂等性操作,无论你操作多少次都不会出现不同结果。在这里使用锁,无外乎就是为了避免重复操作共享资源从而提高效率。
  2. 只允许一个客户端操作共享资源
    这种情况下,对共享资源的操作一般是非幂等性操作。在这种情况下,如果出现多个客户端操作共享资源,就可能意味着数据不一致,数据丢失。比如在购物系统中,对于用户下单来说,对一件商品的购买同一时间只能一个人操作,比如说秒杀商品 下单减库存,否则多个用户同时下单同时减库存容易出现问题,比如说超卖。

2、分布式锁的技术有哪些

常见的有redis来实现分布式锁和zookpeer来实现分布式锁两种方式。

3、Redis分布式锁的实现方案

3.1、setnx实现方式

这是最普通的实现方式,就是在 redis 里使用 setnx 命令创建一个 key,执行下面这个命令就算加锁。

SET key random_value NX PX 30000

  • random_value :value设置为随机值。
  • NX:表示只有 key 不存在的时候才会设置成功。(如果此时 redis 中存在这个 key,那么设置失败,返回 nil)
  • PX 30000:意思是 30s 后锁自动释放。别人创建的时候如果发现已经有了就不能加锁了。
    释放锁就是删除key,为了保证删除key的原子性,一般使用lua脚本进行删除。删除锁的时候,找到 key 对应的 value,跟自己传过去的 value 做比较,如果是一样的才删除。

if redis.call(“get”,KEYS[1]) == ARGV[1] then
return redis.call(“del”,KEYS[1])
else
return 0
end

将value设置随机值的原因就是假如A获取到了锁,由于执行逻辑时间长,超过锁过期时间,此时B获取到了锁,A执行完后会释放锁,如果不设置随机值,就会释放B获取到的锁,这是不对的。

缺点:单机下使用,容易会有单点故障,没有可用性,假如有主从复制,那么主挂掉,还没将锁复制到从节点,此时从节点切换为主节点,别人就可以 set key,从而拿到锁。就会有两个线程持有锁。

3.2、Redisson框架实现方式

Redis推荐使用redlock算法实现分布式锁,实现框架有Redisson,在集群下可以使用。人家还支持redis单实例、redis哨兵、redis cluster、redis master-slave等各种部署架构。

实现原理
在这里插入图片描述(1)加锁机制
咱们来看上面那张图,现在某个客户端要加锁。如果该客户端面对的是一个redis cluster集群,他首先会根据hash节点选择一台机器。紧接着,就会发送一段lua脚本到redis上,为啥要用lua脚本呢?
因为一大坨复杂的业务逻辑,可以通过封装在lua脚本中发送给redis,保证这段复杂业务逻辑执行的原子性。
hset myLock 8743c9c0-0795-4907-87fd-6c719a6b4586:1 1
通过这个命令设置一个hash数据结构,这行命令执行后,会出现一个类似下面的数据结构:

myLock :{
”8743c9c0-0795-4907-87fd-6c719a6b4586:1 ”:1
}

上述就代表“8743c9c0-0795-4907-87fd-6c719a6b4586:1”这个客户端对“myLock”这个锁key完成了加锁。

接着会执行“pexpire myLock 30000”命令,设置myLock这个锁key的生存时间是30秒。

好了,到此为止,ok,加锁完成了。

(2)锁互斥机制

那么在这个时候,如果客户端2来尝试加锁,执行了同样的一段lua脚本,会咋样呢?

首先会判断发现myLock这个锁key已经存在了。

接着再判断,myLock锁key的hash数据结构中,是否包含客户端2的ID,但是明显不是的,因为那里包含的是客户端1的ID。

所以,客户端2会进入一个while循环,不停的尝试加锁。

(3)watch dog自动延期机制

客户端1加锁的锁key默认生存时间才30秒,如果超过了30秒,客户端1还想一直持有这把锁,怎么办呢?

这就用到了watch dog机制,只要客户端1一旦加锁成功,就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端1还持有锁key,那么就会不断的延长锁key的生存时间。

(4)可重入加锁机制

那如果客户端1都已经持有了这把锁了,客户端1再次加锁,就会执行可重入加锁的逻辑,他会用:
incrby myLock 8743c9c0-0795-4907-87fd-6c71a6b4586:1 1
通过这个命令,对客户端1的加锁次数,累加1。

(5)释放锁机制

如果执行lock.unlock(),就可以释放分布式锁,每次释放锁都对myLock数据结构中的那个加锁次数减1。如果发现加锁次数是0了,说明这个客户端已经不再持有锁了,此时就会用:“del myLock”命令,从redis里删除这个key。

然后客户端2就可以尝试完成加锁了。

这就是分布式锁Redisson框架的实现机制。

一般我们在生产系统中,可以用Redisson框架提供的这个类库来基于redis进行分布式锁的加锁与释放锁。

缺点:

其实上面那种方案最大的问题,就是如果你对某个redis master实例,写入了myLock这种锁key的value,此时会异步复制给对应的redis slave实例。

但是这个过程中一旦发生redis master宕机,还没来得进行异步复制,就进行了主备切换,redis slave变为了redis master。

接着就会导致,客户端2来尝试加锁的时候,在新的redis master上完成了加锁,而客户端1也以为自己成功加了锁。

此时就会导致多个客户端对一个分布式锁完成了加锁。

这时系统在业务语义上一定会出现问题&#x

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值