分布式锁
1、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
2、高可用的获取锁与释放锁
3、高性能的获取锁与释放锁
4、具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)
5、具备锁失效机制,防止死锁
6、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败
基于zookeeper的分布式锁
1.zookeeper的一些特性
-
有序节点:假如当前有一个父节点为/lock,我们可以在这个父节点下面创建子节点;zookeeper提供了一个可选的有序特性,例如我们可以创建子节点“
/lock/node-
”并且指明有序,那么zookeeper在生成子节点时会根据当前的子节点数量自动添加整数序号,也就是说如果是第一个创建的子节点,那么生成的子节点为/lock/node-0000000000
,下一个节点则为/lock/node-0000000001
,依次类推。 -
临时节点:客户端可以建立一个临时节点,在会话结束或者会话超时后,zookeeper会自动删除该节点。
-
事件监听:在读取数据时,我们可以同时对节点设置事件监听,当节点数据或结构变化时,zookeeper会通知客户端。当前zookeeper有如下四种事件:
1.节点创建;
2.节点删除;
3.节点数据修改;
4.子节点变更。
2.实现
①客户端连接zookeeper,并在/lock下创建临时的且有序的子节点,第一个客户端对应的子节点为/lock/lock-1,第二个为/lock/lock-2,以此类推。
②客户端获取/lock下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁,否则监听/lock的子节点变更消息,获得子节点变更通知后重复此步骤直至获得锁;
③执行业务代码;
④完成业务流程后,删除对应的子节点释放锁。
基于curator的zookeeper分布式锁实现
public static void main(String[] args) throws Exception {
//创建zookeeper的客户端
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient("10.21.41.181:2181,10.21.42.47:2181,10.21.49.252:2181", retryPolicy);
client.start();
//创建分布式锁, 锁空间的根节点路径为/curator/lock
InterProcessMutex mutex = new InterProcessMutex(client, "/curator/lock");
mutex.acquire();
//获得了锁, 进行业务流程
System.out.println("Enter mutex");
//完成业务流程, 释放锁
mutex.release();
//关闭客户端
client.close();
}
基于redis的分布式锁
1.流程
1、服务A为了获得锁,向Redis发起如下命令: SET productId:lock 0xx9p03001 NX PX 30000
其中,"productId"由自己定义,可以是与本次业务有关的id,"0xx9p03001"是一串随机值,必须保证全局唯一(原因在后文中会提到),“NX"指的是当且仅当key(也就是案例中的"productId:lock”)在Redis中不存在时,返回执行成功,否则执行失败。"PX 30000"指的是在30秒后,key将被自动删除。执行命令后返回成功,表明服务成功的获得了锁。
2、服务B为了获得锁,向Redis发起同样的命令: SET productId:lock 0000111 NX PX 30000
由于Redis内已经存在同名key,且并未过期,因此命令执行失败,服务B未能获得锁。服务B进入循环请求状态,比如每隔1秒钟(自行设置)向Redis发送请求,直到执行成功并获得锁。
3、服务A的业务代码执行时长超过了30秒,导致key超时,因此Redis自动删除了key。此时服务B再次发送命令执行成功,假设本次请求中设置的value值为0000222。
4、服务A执行完毕,为了释放锁,服务A会主动向Redis发起删除key的请求。
2.实现
①加锁
客户端集成Redisson,在加锁之前,首先需要通过hash算法选定集群内某一个Redis Master,后续加锁、解锁等各种过程都是在这个Redis Master和与之绑定的slave节点之间。
②执行lua脚本实现加锁
③watch dog自动延期
watch dog是一个后台线程,它会每隔10秒观察当前客户端是否仍然持有锁,如果持有,说明客户端可能仍然在使用锁,因此延长锁的剩余生存时间。
④释放锁机制
如果执行lock.unlock(),Redis会找到上方test数据结构,将加锁次数减一。如果减完后发现加锁次数为0,则说明当前客户端不再持有锁,因此执行: del test命令, 从Redis中删除这条key。
// 准备为名为"test"的key加锁
RLock lock = redisson.getLock("test");
// 加锁
lock.lock();
// 解锁
lock.unlock();