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释放锁;如果大于锁设置的超时时间,则不需要再锁进行处理。
代码如下:
/**
* 带超时时间的锁
*
* @param lockKey 锁的键值
* @param timeUnit 锁的时间单位
* @param timeout 锁的时间长度
* @param relyOnRedisAvailable 是否依赖redis可用性
* true则redis不可用或异常情况下返回加锁失败;
* false则redis不可用或异常情况下返回加锁成功。
* @return 是否添加锁成功
*/
@Override
public boolean lock(String lockKey, TimeUnit timeUnit, long timeout, boolean relyOnRedisAvailable) {
//获取当前时间
long currentTimeMillis = System.currentTimeMillis();
String repeatSign = UUID.randomUUID().toString().replace("-", "");//重复标记
//获取超时时间的ms
long lockTimeMs = timeUnit.toMillis(timeout);
long setNx;
try {
//这里我们以 超时时间为value+标记
String setLockValue = String.valueOf(currentTimeMillis + lockTimeMs)+repeatSign;
setNx = redisClientUtil.setnx(lockKey, setLockValue);
logger.info("键值[{}]设置的setNx为:{}, setLockValue为:{}", lockKey, setNx, setLockValue);
} catch (Exception e) {
logger.error("{}设置锁异常",lockKey,e);
pubEvent(lockKey,e);
return true;
}
//如果成功了,那么我们就设置超时时间
if (setNx == 1 ) {
logger.info("键值{}获取redis锁成功",lockKey);
try {
redisClientUtil.expire(lockKey, (int)timeUnit.toSeconds(timeout));
} catch (Exception e) {
logger.error("{}设置超时时间异常,异常信息为",lockKey,e);
}
return true;
}
//以下是不成功的处理情况,即 setNx 等于0的时候,我们先获取旧的值
String lockValue = redisClientUtil.getValueByKey(lockKey);
logger.info("键值[{}],设置的lockValue为:{},重复标记为:{}", lockKey, lockValue, repeatSign);
if (StrUtil.isBlank(lockValue)) {
//如果锁value为空,直接返回成功
return true;
}
if(lockValue.contains(repeatSign)){
//如果锁value包含重复标记,则说明其为本次设置,直接返回成功
return true;
}
String lockTime = lockValue.substring(0, lockValue.indexOf("|"));//锁超时时间
if (null == lockTime) {
logger.warn("键值[{}]获取到时间戳为空, 按照获取成功返回!", lockKey);
return true;
} else {
try {
logger.info("键值[{}]redis存储的时间戳:{}, 格式化后:{}", lockKey, lockTime, DateUtil.dateToString(new Date(Long.valueOf(lockTime)), DateUtil.DEFAULT_TIMESTAMP_FORMAT));
} catch (Exception e) {
logger.warn("键值[{}]redis存储的时间戳:{}, 格式化时出现异常,不影响流程", lockKey, lockTime, e);
}
}
if (lockTime != null && Long.valueOf(lockTime)
< System.currentTimeMillis() ) {
String oldValue = redisClientUtil.getSet(lockKey,
String.valueOf(currentTimeMillis + lockTimeMs)+ "|" + repeatSign);
String oldLockTime = oldValue.substring(0, lockValue.indexOf("|"));//锁超时时间
//将两次获取的值对比,这里也就是文章第五条,如果相等说明没有被其他线程修改
if (oldValue != null && oldLockTime.equals(lockTime)) {
//这里用该还要在设置一次超时时间,可以用lua 脚本保持一致性
return true;
}
}
return false;
}