后台常见的锁

四、分布式锁

传统的锁可解决在一台服务器上多个线程之间的并发冲突,但有些场景下多台服务器需要进行同一操作,这时为了协调多台服务器就需要分布式锁(由第三方提供锁)。
分布式锁常用的是两种实现方式:使用Redis或Zookeeper。

4.1Redis自带的原语

Redis是一个key value数据库,我们可以用key当一个锁,哪个程序创建了这个key 哪个程序就相当于抢到了锁。当使用完资源之后,再删除该key。
SET key value [EX seconds] [PX milliseconds] [NX|XX]
EX:设置过期时间,单位s
PX:设置过期时间,单位ms
NX:只有当该key不存在时才set该key
XX:只有当key存在时才set该key
这里提一下其实很多redis的指令和使用可以直接在Redis官网查,很方便:https://redis.io/commands
为什么这样做可以从下面几个问题考虑:

如果key 已经存在了怎么办?那就相当于已经有程序占有锁了,所以抢所失败,所以set语句必须有NX后缀
如果一个程序创建了key,但之后它fail了那么这个key就无法删除,其他程序无法抢锁,因此需要EX或PX来设置一个过期时间,到时间后redis会自动删除。
Value值如何设?可以设置key对应的value为自己的一个唯一标识,这样通过获取key对应的value如果发现是自己那么就可以重入,同时在删除key的时候再通过检查value可以避免删除其他client创建的key。
不要先Set key value 然后再用Expire 设置过期时间,因为这两步之间不是原子的。
关于Redis实现分布式锁,Redis官方也给出了说明:https://redis.io/topics/distlock

4.2.1排他锁

上锁

简单版本:多个ZK的客户端同时抢锁,在一个确定的目录下创建临时节点(临时节点的生命周期和创建该节点的session有关,会话断开,zk会自动删除节点),谁创建成功就是谁抢到了锁.其它客户端在该节点注册watcher监听.但这样当并发量很大的时候,一个节点执行完被删除时zk要通知其他所有监听的程序,zk的压力会非常大,这就是Herd Effect羊群效应,所以高并发时这样的方案并不合适。
更适合高并发的版本:创建临时顺序节点(zk会按顺序创建,在创建的节点名后面自动加上一个递增的序号,并返回给客户端),然后获取该目录下所有子节点,确定顺序,如果自己创建的节点序号最小代表获得锁,否则按序号排序向前一个节点注册监听,这样当前一个节点执行完被删除时会通知该客户端去执行,第一个执行完通知第二个,第二个执行完通知第三个……这样其实获取锁的顺序在创建时就确定了,获取锁的顺序也就是按创建节点的序号确定。从描述上也可以看出来这样实现的是公平锁。

释放锁: 执行完后主动删除节点释放锁;因为创建的是临时节点,如果宕机,那么zk会自动删除临时节点.其它客户端可再去创建。

4.2.2共享锁

上锁:创建临时顺序节点,同时命名时标明是读锁还是写锁,如

/share_lock/xxxx-R-00001
/share_lock/xxxx-W-00002
然后获取该目录/share_lock下所有子节点确定顺序.

读锁:如果比自己小的子节点里,都是读,那么可以同时进行读,如果有写,那么向这个节点注册监听,等他写完再读;
写锁:前面如果有其它节点,不论是读还是写,向它前一个节点注册监听,等待前一个节点执行完再写。如果自己创建的节点最小,那么代表获得锁,可以执行后续代码。
就是说用写分割,写必须按顺序,写之间的读都可以同时读.
释放锁: 处理完自己删除节点或宕机由zookeeper自动释放.

posted on 2021-02-27 22:54  清浊  阅读(351)  评论(0编辑  收藏  举报