一、基于redis实现
可用于实现接口幂等性,防止重复下单等。
package lock;
/**
* Created with IDEA
* author:liuhaotian
* Date:2019/9/14 15:51
* Description: 分布式锁
*/
public class DistributedLock {
/**
* 基于redis实现分布式锁
* 基于 REDIS 的 SETNX()、GET()、GETSET()方法做分布式锁
* 这个方案的背景主要是在 setnx() 和 expire() 的方案上针对可能存在的死锁问题(setnx() 执行成功了,expire失败了),
* 做了一些优化。
*/
/**
* 使用步骤
* 1.setnx(lockkey, 当前时间+过期超时时间),如果返回 1,则获取锁成功;如果返回 0 则没有获取到锁,转向 2。
* 2.get(lockkey) 获取值 oldExpireTime ,并将这个 value 值与当前的系统时间进行比较,如果小于当前系统时间,
* 则认为这个锁已经超时,可以允许别的请求重新获取,转向 3。
* 3.计算 newExpireTime = 当前时间+过期超时时间,然后 getset(lockkey, newExpireTime)
* 会返回当前 lockkey 的值currentExpireTime。
* 4.判断 currentExpireTime 与 oldExpireTime 是否相等,如果相等,说明当前 getset 设置成功,获取到了锁。
* 如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试。
* 5.在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,
* 如果小于锁设置的超时时间,则直接执行 delete 释放锁;如果大于锁设置的超时时间,则不需要再锁进行处理
*/
private static final int DEFAULT_EXPIRE_TIME = 60;
public boolean lock(String key) {
//1.0
long keyValue = System.currentTimeMillis() + DEFAULT_EXPIRE_TIME;
if (1 == RedisUtil.setNx(key, String.valueOf(keyValue))) {
return true;
}
//2.0
long oldExpireTime = Long.parseLong(RedisUtil.get(key));
//如果时间没过期,则说明锁还有效,否则 说明锁已失效
if (oldExpireTime < System.currentTimeMillis()) {
//旧锁过期,生成新的过期时间
long newExpireTime = System.currentTimeMillis() + DEFAULT_EXPIRE_TIME;
//设置新的过期时间并判断,此时旧的过期时间是否为前边判断的,如果不是,则说明已经有其他线程设置了新的过期时间
//也就是其他线程抢占了该锁。
long curExpireTime = Long.parseLong(RedisUtil.getset(key, String.valueOf(newExpireTime)));
if (curExpireTime == oldExpireTime) {
return true;
}
}
return false;
}
}
package lock;
/**
* Created with IDEA
* author:liuhaotian
* Date:2019/9/14 15:58
* Description: 模拟redis操作
*/
public class RedisUtil {
public static int setNx(String key,String value){
return 1;
}
public static String get(String key){
return "";
}
public static String getset(String key,String newValue){
return "";
}
public static void expire(String key,int time){
}
}
二、基于zookeeper
临时节点:跟zookeeper的连接断开后,临时节点会自动被删掉。
1.订阅一个节点的变更通知
2.重写节点变更时回调的方法(watch机制)
Zookeeper典型应用场景:
1.数据发布订阅-配置中心
2.master选举
3.集群管理
4.分布式锁
利用临时节点实现分布式锁:
0. implement Lock接口
1.先尝试创建一个key的临时节点,成功则获取锁成功
2.否则获取锁失败,阻塞继续获取锁
3.用CountDownLatch 计数器实现阻塞
4.订阅通知:当已经获取锁的线程释放锁之后,会发布一个通知,当前线程收到通知后,countDown 唤醒该线程继续抢占锁,并取消订阅。
5.如果再次抢占锁失败,则继续阻塞重复以上操作。
//cd1.countDown() 之后 如果计数器减到0,则该线程唤醒。
用zookeeper实现分布式锁的缺点:
每次都会有大量节点去抢占锁,不适合大型集群系统。
利用临时顺序节点实现分布式锁:*
- 在节点key下面创建一个临时顺序节点
- 获取子节点列表,判断自己是否为最小的子节点,如果是,则表示获取锁成功,否则:只对 比自己小一号的节点注册,然后阻塞等待。