分布式锁的区别
分布式锁,是一种思想,它的实现方式有很多。比如,我们将沙滩当做分布式锁的组件,那么它看起来应该是这样的
- 加锁
在沙滩上踩一脚,留下自己的脚印,就对应了加锁操作。其他进程或者线程,看到沙滩上已经有脚印,证明锁已被别人持有,则等待。
- 解锁
把脚印从沙滩上抹去,就是解锁的过程。
- 锁超时
分布式锁的实现有很多,比如基于数据库、memcached、Redis、系统文件、zookeeper等。它们的核心的理念跟上面的过程大致相同。具备的条件:
1、一个方法同一时间只能被一个机器一个线程执行那么如何实现分布式锁呢,有如下几种方式:
2、高可用的获取锁和释放锁
3、高性能的获取锁和释放锁
4、具备可重入性
5、具备锁失效机制,防止死锁
6、具备非阻塞锁特性,即没有获取锁直接放回获取锁失败。
一、基于Zookeeper实现
- 基于Zookeeper实现
- 基于缓存(redis实现)
- 基于数据库实现方式
Zookeeper数据存储结构是一颗树,树由节点组成,节点叫ZNode
Znode分四种类型:
1、持久节点(persistent)
默认节点类型,创建节点的客户端和Zookeeper断开链接后,节点依旧存在2、持久节点顺序节点(persistent_sequential)
创建节点时,根据创建时间给节点编号。3、临时节点
断开链接后,节点被删除4、临时顺序节点
Zookeeper分布式锁的原理:
获取锁:
- 在Zookeeper创建一个持久节点ParentLock,当客户端想要获取锁时,在ParentLock节点下创建临时顺序节点。
- 然后客户端再去获取临时节点是否是最靠前的一个,如果是则获取锁。
- 另外一个客户端先创建临时节点,然后获取临时节点是靠前,如果不是靠前的,并且不是最小的序号,此时向前面的节点注册Watcher,用于监听前一个节点的锁是否存在。
释放锁:
- 任务完成删除临时节点
- 由于节点都是相互监听,当前一个节点消失,下一个节点被置顶
- 如果机器宕机了,会自动删除临时节点。
二、基于缓存(redis实现)
- 【分布式锁的区别】性能上不如缓存服务高,创建锁和释放锁过程上,都动态创建、销毁临时节点实现锁功能,Zk中创建和删除节点只能通过leader服务器执行,然后将数据同步到所有follower机器上。
- 可能会因为网络抖动导致链接中断,删除临时节点,但是ZK有多种重试策略,重试之后才会删除临时节点。
1、加锁
加锁实际上就是在redis中,给Key键设置一个值,为避免死锁,并给定一个过期时间。
SET lock_key random_value NX PX 5000
值得注意的是:
random_value 是客户端生成的唯一的字符串。
NX 代表只在键不存在时,才对键进行设置操作。
PX 5000 设置键的过期时间为5000毫秒。
这样,如果上面的命令执行成功,则证明客户端获取到了锁。
2、解锁
解锁的过程就是将Key键删除。但也不能乱删,不能说客户端1的请求将客户端2的锁给删除掉。这时候random_value的作用就体现出来。
为了保证解锁操作的原子性,我们用LUA脚本完成这一操作。先判断当前锁的字符串是否与传入的值相等,是的话就删除Key,解锁成功。
if redis.call('get',KEYS[1]) == ARGV[1] then
return redis.call('del',KEYS[1])
else
return 0
end
3、使用
首先,我们在pom文件中,引入Redis包。在这里,笔者用的是最新版本,注意由于版本的不同,API可能有所差异。
org.springframework.boot
spring-boot-starter-data-redis
加锁的过程很简单,就是通过SET指令来设置值,成功则返回;否则就循环等待,在timeout时间内仍未获取到锁,则获取失败。缺点:
客户端A从master获取锁,master将锁同步到slave钱,master 宕机,slave节点晋升master节点,客户端B取得了被客户端A锁定的同一个资源。安全失效。三、基于数据库实现方式
数据库拍他锁
1、根据名字获取锁信息
2、更新锁信息(比如版本,状态等)占有锁
缺点:
1、数据库的强依赖性,数据库不可用会导致业务系统不可用。解决方案:
2、锁没有失效时间,解锁失败,会使锁一直存在,其他线程无法获取锁。
3、锁只能是非阻塞,插入失败报错,线程不会进入排队队列中,会再次出发获取锁的操作。
4、锁是非重入,同一个线程没有释放锁,无法再次获得该锁,因为数据库中锁的数据已经存在。
1、主从复制四、三种分布式锁优缺点
2、定时任务,或者添加上次更新的时间
3、死循环insert
4、数据库存入当前线程的信息。
分布式锁 | 优点 | 缺点 |
---|---|---|
Zookeeper | 1、封装好的框架,容易实现 2、有等待锁的队列,提升抢锁的概率 添加和删除节点性能低 |
添加和删除节点性能低 |
Redis | set和del指令性能较高 | 1、实现复杂:需要考虑超时、原子性、误删等情况 2、没有等待锁的队列,需要在客户端自旋等待锁,效率低 |
数据库 | 容易理解 | 复杂度较高,需要设计表、策略等如果获取锁失败,需要不断的链接数据库,查库操作。 |
推荐阅读
- 热闹中的孤独
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 放屁有这三个特征的,请注意啦!这说明你的身体毒素太多
- 一个人的旅行,三亚
- 布丽吉特,人生绝对的赢家
- 慢慢的美丽
- 尽力
- 一个小故事,我的思考。
- 家乡的那条小河
- 《真与假的困惑》???|《真与假的困惑》??? ——致良知是一种伟大的力量