-
应用场景
当某个资源在多个系统中存在共享时,为了保证各个系统获得资源数据是一致的,那就需要保证该资源在同一时刻只能被一个系统访问,不能存在并发现象,否则会存在同一时刻有人读有人写的情况,导致数据不一致。这个时候就需要用到分布式锁。
对于锁我们并不陌生,在单机的时候经常会碰到多线程访问同一个共享数据时,采用锁机制:一个线程获得该数据后,进行加锁,使用完成之后进行解锁,这样可以使其他线程使用,这样就保证同一时刻,只有一条线程操作操作该共享数据。在java中我们经常使用lock、synchronize等。
-
分布式锁要满足的要求
1、排他性
在同一时间只能有一个客户端获得锁,其他客户端不能同时获得
2、避免死锁
获得锁后,一定时间后肯定会释放锁(正常释放和异常释放)
3、高可用
获取锁和释放锁必须高可用且高性能
-
实现方式
1、基于数据库
1)基于数据库的乐观锁
乐观锁机制是给数据库表加version(版本号)字段,来确定当前数据的版本。比如,当用户A和用户B同时对一条数据做查询,两者读出来的数据是同一个版本的;然后A和B同时提交更新操作,但是A的事务先执行完成,此时会对版本号version做加1操作;当B进行数据更新时,会先判断自己读出来的version是否与当前数据库值一致,不同则操作失败。
2)基于数据库的悲观锁(也叫排他锁)
在对数据库一条记录操作时,会对该条记录做加锁操作,使其他操作是无法进行的。例如·mysql时基于for update 做加锁操作。
2、基于Redis
主要依赖与redis的原子性操作:
set key value NX PX 100
NX:当键不存在时,才对键进行设置值操作
PX: 设置键的过期时间(毫秒),当超过该时间,键失效
上述代码的意思是,当key不存在时,才能设置成功值,且100毫秒键过期。这就保证当多个进程对key设置值,只能有一个设置成功,当该条进程成功,执行业务逻辑后,就可以解锁(清除该key)
3、基于ZooKeeper
其实基于ZooKeeper,就是使用它的临时有序节点来实现的分布式锁。
原理就是:当某客户端要进行逻辑的加锁时,就在zookeeper上的某个指定节点的目录下,去生成一个唯一的临时有序节点, 然后判断自己是否是这些有序节点中序号最小的一个,如果是,则算是获取了锁。如果不是,则说明没有获取到锁,那么就需要在序列中找到比自己小的那个节点,并对其调用exist()方法,对其注册事件监听,当监听到这个节点被删除了,那就再去判断一次自己当初创建的节点是否变成了序列中最小的。如果是,则获取锁,如果不是,则重复上述步骤