分布式锁

什么是分布式锁
相对于单机应用设定的单机锁,为分布式应用各节点对共享资源的排他式访问而设定的锁就是分布式锁。所以分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。
分布式锁的特点
互斥性:和我们本地锁一样互斥性是最基本,但是分布式锁需要保证在不同节点的不同线程的互斥。
可重入性:同一个节点上的同一个线程如果获取了锁之后那么也可以再次获取这个锁。
锁超时:和本地锁一样支持锁超时,防止死锁。
高效高可用: 加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效,可以增加降级。
非阻塞: 即没有获取到锁将直接返回获取锁失败
分布式锁的使用场景
1 如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
2 分布式锁还是可以将标记存在内存,只是该内存不是某个进程分配的内存而是公共内存如 Redis、Memcache。至于利用数据库、文件等做锁与单机的实现是一样的,只要保证标记能互斥就行。
分布式锁的实现方式
1 mysql实现
2 redis实现
3 zookeeper实现
A 基于mysql实现
可以建一张表,在设置表里的唯一索引字段unique,设置失效时间字段expire_at,设置当前锁的持有者的机器和线程信息。
设置唯一索引是为了有多个请求同时提交的话,数据库保证只有一个操作可以成功。
设置失效时间字段,为了在宕机的情况下有定时任务可以清除这些失效的数据;
设置当前获取到锁的机器和线程信息,先查询表中机器和线程信息是否和当前机器和线程信息相同,若相同则直接获取锁;不相同则判断是否过期,不过期直接返回失败。
执行完毕业务后 直接删除对应的行数据释放锁。
B 基于redis的实现
使用redis分布式锁的时候主要用到的命令,setnx,expire,delete,setex 等。
setnx (SET if Not eXists) 只有在key值不存在的时候才能set成功,key值已经存在返回0。
expire 设置过期时间,要注意的是 setnx 命令不能设置 key 的超时时间,只能通过 expire() 来对 key 设置。
setex setex key seconds value 同时设置key的值和过期时间。这是一个原子性的操作。
B1 > setnx 加锁,del解锁,expire 锁超时,
伪代码
if(setnx(k)) {
expire(k,ttl)
dojob()
dellock();
return true;
} else {
return false;
}
这里有多个问题 :
P1: 长生不老锁
setnx 获取之后,还没来得expire 啪,嗝屁了。会导致后面的人永远都拿不到锁。导致deadlock(陷入僵局)。这是由于setnx和expire之间不是原子性的操作导致的。
可以使用 set key value EX seconds NX 来替代 setex和expire,这样获取到锁的线程突然蒸发了,这锁也会在过期时间后自动消失的,不会长生不老的。
P2: del误删操作
.假设A线程成功得到了锁,并且设置超时时间是30s,一些原因导致线程A执行的很慢,过了30秒都没执行完,这时候锁过期了自动释放了,线程B过来得到了锁,随后A执行完了任务,接着是dellock() 来释放锁,但是这时候B还没有执行完,线程A 实际上删除的是线程B 加的锁。
可以在加锁的时候把当前的线程ID当做value并在删除之前验证key对应的valude是不是直接的线程ID ,使用LUA脚本
$luaScript = <<<SCRIPT if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end
SCRIPT;
$redis->eval($lua,getCreatorId())
P3 watch dog 自动延期机制
客户端 1 加锁的锁 Key 默认生存时间才 30 秒,如果超过了 30 秒,客户端 1 还想一直持有这把锁,怎么办呢?
只要客户端 1 加锁成功,就会启动一个 watch dog 看门狗,这个后台线程,会每隔 10 秒检查一下,如果客户端 1 还持有锁 Key,就会不断的延长锁 Key 的生存时间。
C 基于zookeeper的实现
ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。
zk节点类型有4大类,持久化节点,持久化顺序编号目录节点,临时目录节点(客户端断开后节点就删除了),临时目录编号目录节点。
(1)创建一个目录Jacklock; (2)线程A想获取锁就在Jacklock目录下创建临时顺序节点; (3)获取Jacklock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁; (4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己小的节点; (5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小节点,如果是则获得锁。
这里推荐一个Apache的开源库Curator,它是一个ZooKeeper客户端,
Curator提供的InterProcessMutex是分布式锁的实现,
acquire方法用于获取锁,release方法用于释放锁。
优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。
缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式。
https://juejin.im/post/5b16148a518825136137c8db
https://blog.csdn.net/xlgen157387/article/details/79036337

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注