1. 基于redis的分布式锁,使用Jedis客户端
SET
SET key value [EX seconds] [PX milliseconds] [NX|XX]
- EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
- PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
- NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
- XX :只在键已经存在时,才对键进行设置操作。
非阻塞锁
@Component
public class RedisDistributedLock1 {
@Autowired
private JedisCluster jedisCluster;
private int exprieTime = 10;
private String prefix ="lock:";
private String defaultVal = "default";
/**
* 获取锁:
* 获取到锁后设置过期时间返回 true
* @param lockKey
* @return
*/
public boolean lock(String lockKey){
String defaultKey = prefix+lockKey;
String result = jedisCluster.set(defaultKey, defaultVal,"NX", "EX", exprieTime);
if("OK".equals(result){
return true;
}
return false;
}
/**
* 释放锁:直接删除key
* @param lockKey
*/
public void unlock(String lockKey){
jedisCluster.del(lockKey);
}
}
阻塞锁:
@Component
public class RedisDistributedLock2 {
//redis缓存
@Autowired
private JedisCluster jedisCluster;
//锁超时时间 防止死锁(时间不能太短,防止正常业务过程执行慢 而导致超时)(单位是秒)
private static final int TIME_OUT = 60;
//获取不到锁的等待重试时间(毫秒)
private static final int RETRY_TIME = 100;
public void acquire(String key) {
String lockVal = UUID.randomUUID().toString();
while(true){
String result = jedisCluster.set(defaultKey, defaultVal,"NX", "EX", exprieTime);
if(!"OK".equals(result){
//当客户端无法获取锁时,它应该在一个随机延迟后重试,从而避免多个客户端同时试图获取锁
try {
Thread.sleep(RETRY_TIME + (int)(Math.random() * RETRY_TIME));
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//获取锁成功, 跳槽循环。
break;
}
}
}
public void release(String key) {
jedisCluster.del(key);
}
}
2.基于zookeeper的分布式锁
思路:利用名称唯一性,加锁操作时,只需要所有客户端一起创建lockPath节点,只有一个创建成功,成功者获得锁。解锁时,只需删除lockPath节点,其余客户端再次进入竞争创建节点,直到所有客户端都获得锁。
保证锁释放: 客户端会话结束时,zookeeper会将该ephemeral znode删除,ephemeral znode不可以有子节点。
public class ZkDistributeLock1 {
private ZkClient client;
private long wait_interval = 50;
public void acquire(String lockPath) throws Exception {
if (attemptLock(lockPath, -1) == null ) {
throw new IOException("连接丢失!在路径:'"+lockPath+"'下不能获取锁!");
}
}
public boolean acquire(String lockPath, long millisecond) throws Exception {
return attemptLock(lockPath, millisecond) != null;
}
public void release(String lockPath) {
client.delete(lockPath);
}
/**
* 尝试获取锁 ,直到获取成功或者超时。
* @param millisecond
* @return
* @throws Exception
*/
private String attemptLock(String lockPath, long millisecond) throws Exception{
long startMillis = System.currentTimeMillis();
Long millisToWait = (millisecond < 0) ? null :millisecond;
String returnPath = null;
while(returnPath == null){
try {
//如果客户端宕机, zookeeper会自动释放锁, 防止死锁。
returnPath = client.create(lockPath,null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
} catch (Exception e) {
Thread.sleep(wait_interval);
}
if(millisToWait != null && returnPath == null){
millisToWait -= (System.currentTimeMillis() - startMillis);
startMillis = System.currentTimeMillis();
if ( millisToWait <= 0 ) break;
}
}
return returnPath;
}
}
3. 基于TAIR的版本号的分布式锁
Tair中的每个数据都包含版本号,版本号在每次更新后都会递增。插入的时候初始版本号1,更新的时候需要比较版本号是否一致,一致才能更新成功,不一致就会更新失败。
分布式锁给定一个比较大的版本号LOCK_VERSION,比如Integer.MAX_VALUE, 这样只有第一次PUT可以成功,更新数据都会失败。
/**
* 分布式锁,加锁
*
* @param key
* @param expireTime
* @return
*/
public boolean lock(String key, int expireTime) {
ResultCode rc = tairManager.put(namespace, key, 0, LOCK_VERSION, expireTime);
if (ResultCode.SUCCESS.equals(rc)) {
return true;
} else {
return false;
}
}
/**
* 分布式锁,释放锁
*
* @param key
* @return
*/
public boolean unlock(String key){
ResultCode rc = tairManager.delete(namespace, key);
if (ResultCode.SUCCESS.equals(rc)
|| ResultCode.DATANOTEXSITS.equals(rc)) {
return true;
} else {
return false;
}
}