伪代码如下:
//整体流程
public void acquireLockMethod(){
Map<String,Object> lockMap = new HashMap<>();
try{
lockMap = acquireLock(lockKey,expire);
if(lockMap!=null){
todoBusiness();//获得锁,执行业务逻辑方法
}
}catch(Exception e){
throw e;
}finally{
releaseLock(lockKey,lockMap);
}
}
//acquireLock()方法:
public Map<String,Object> acquireLock(String lock,long expired){
Map<String,Object> map = new HashMap<>();
long value = System.currentTimeMillis() + expired + 1;
long acquired = jedis.setnx(lock, String.valueOf(value));
if (acquired == 1)
map.put("isSuccess",true);
map.put("expireTimeStr":value);
return map;
else {
long oldValue = Long.valueOf(jedis.get(lock));
//如果其他资源之前获得锁已经超时
if (oldValue < System.currentTimeMillis()){
String getValue = jedis.getSet(lock, String.valueOf(value));
//上一个锁超时后会有很多线程去争夺锁,所以只有拿到oldValue的线程才是获得锁的。
if (Long.valueOf(getValue) == oldValue)
map.put("isSuccess",true);
map.put("expireTimeStr":value);
return map;
else
return null;
}
else return null ;
}
//解锁
private void release(ShardedJedis jedis, String lockKey,Map<String,Object> lockMap) {
if(CollectionUtils.isEmpty(lockMap)){
return;
}
Boolean locked = (Boolean) lockMap.get("isSuccess");
String lockExpiresStr = (String) lockMap.get("expireTimeStr");
if (locked) {
String oldValueStr = jedis.get(lockKey);
if (oldValueStr != null) {
// 竞争的 redis.getSet 导致其时间跟原有的由误差,若误差在 超时范围内,说明仍旧是 原来的锁
Long diff = Long.parseLong(lockExpiresStr) - Long.parseLong(oldValueStr);
if (diff < expireMsecs) {
jedis.del(lockKey);
} else {
// 这个进程的锁超时了,被 新的进程锁获得替换了。则不进行任何操作。打印日志,方便后续跟进
log.error("the lockKey over time.lockKey:{}.expireMsecs:{},over time is", lockKey, expireMsecs, System.currentTimeMillis() - Long.valueOf(lockExpiresStr));
}
}
}
}
此分布式锁:
利用redis本身的单线程特性,以及setNX和getSet方法的原子特性,保证了多请求的加锁的互斥性,最后只有一个请求获得锁。
可以通过使用while循环实现阻塞特定时间的锁。
通过getSet方法,没有使用expire方法,保证了此分布式锁不会造成死锁。
此分布式锁依旧存在的问题:
- 如果业务逻辑执行时间超时(锁此时自动失效了),此时业务逻辑的数据已经不安全。虽然在解锁的时候,判断了锁的value值,做到了不会错误释放锁的情况,但是对超时业务逻辑只是做了日志记录,不太靠谱。
- redis集群部署时,这样的分布式锁会存在什么问题吗。这个没有试验过。