分布式锁原理探究
2018-07-02 15:24:19 0 举报
AI智能生成
分布式锁实现原理以及开源实现推荐
作者其他创作
大纲/内容
分布式锁与JUC包锁
JUC包的锁
ReentrantLock
ReentrantReadWriteLock
StampedLock
不同的JVM中,JUC包下的锁无法保证资源访问的安全性
保证不同的JVM进程中的线程访问资源的安全性的锁,就是分布式锁
数据库悲观锁
悲观锁
指对数据记录被外界修改持保守态度
悲观锁的实现,往往依靠数据库提供的行锁机制,数据库中实现是对数据记录操作前给行记录加排它锁
select * from 表 where id = #id for update
where后一定要加id或key,否则会锁全表
由于不同 JVM 中线程共同去竞争的同一个行记录,所以这就实现了一个分布式锁
优点
实现简单
缺点
当其他线程挂起等待时,会继续持有数据库链接,在高并发下,数据库链接可能会被占用完,从而影响正常业务
没有手动设置超时自动释放锁的概念,等待的线程要么获取到锁,要么等待数据库执行SQL超时
Redis
Redis 提供了一个保证原子性的 setnx 函数,多个线程调用该函数操作同一个 key 的时候,只有一个线程会返回 OK,其他线程返回 null
问题
释放锁的操作需要调用 get 方法,然后 if 语句进行判断,判断 OK 然后调用 del 删除,而这三步并不是原子性的
假设线程 A 调用 set 方法设置 key 对应的 value 为 AA 成功, 则线程 A 获取到了锁,然后在执行完业务逻辑后,首先通过 get 方法获取 key 对应的 value,然后通过 if 语句判断为 true,假设在执行 del 方法前对应的 key 已经超时了,并且线程 B 调用 set 方法设置 key 对应的 value 为 BB 成功了,也就是线程 B 获取到了锁,但是这时候线程 A 开始执行 del 方法了,则会把线程 B 对应的 key 的值删除了(不同线程调用 set 的时候 key 一样),也就是释放了锁,这时候其他线程就会竞争到该锁
解决方案
Redis 有一个叫做 eval 的函数,支持 Lua 脚本执行,并且能够保证脚本执行的原子性
if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end
优点
实现简单
可以使用cas等算法判断锁是否成功,提升空间大
setnx自带超时参数,可实现获取超时
缺点
未获得锁的线程,需要重试,自旋浪费资源,其他实现方式较复杂
开源实现:Redisson
https://github.com/redisson/redisson
Zookeeper
ZK中的节点类型
持久节点(PERSISTENT)
创建后一直存在,直到主动删除
临时节点(EPHEMERAL)
声明周期和客户端会话绑定,客户端会话失效,节点自动删除
序列节点(SEQUENTIAL)
多个线程创建同一个顺序节点,每个线程会得到一个带有编号的节点,节点编号是递增不重复的
线程1、2、3同时创建节点/locks/lock
线程1可能得到 /locks/lock001
线程2可能得到 /locks/lock002
线程3可能得到 /locks/lock003
三种模式可以混用
如:EPHEMERAL_SEQENTIAL
临时的序列节点,可以用于实现分布式锁
分布式锁实现步骤
创建临时顺序节点,比如 /locks/lockOne,假设返回结果为 /locks/lockOne000000000*
获取 /locks下所有子节点,找最小子节点。如果自己是最小,则获取锁,否则监听比自己小一号的节点,挂起当前线程
最小节点删除后,激活监听自己的线程
优点
相对于Reids,未获得锁的线程是挂起的,获取锁后才会激活
挂起前可以使用CountDownLatch重试几次,提升吞吐量
缺点
实现较复杂
开源实现:Apache的Curator
https://curator.apache.org/
0 条评论
下一页