redis分布式锁:setNx自定义锁
redis分布式锁原理:视频教程:【免费】redis高可用分布式锁精讲-1-jvm锁与分布式锁对比-谭亮的在线视频教程-CSDN程序员研修院
- SET key value命令:
如果 key 已经持有其他值, SET 就覆写旧值,无视类型
- SETEX key seconds value命令:
将值 value 关联到 key ,并将 key 的生存时间设为 seconds (以秒为单位)
如果 key 已经存在, SETEX 命令将覆写旧值,设置成功时返回 OK,当 seconds 参数不合法时,返回一个错误
- SETNX key value命令:
将 key 的值设为 value ,当且仅当 key 不存在,若给定的 key 已经存在,则 SETNX 不做任何动作
成功返回1,失败返回0
流程
流程说明:
- A、B线程同时通过setEX,setNX命令用同一个key去添加redis,由于redis是单线程的,只能有一个线程成功返回ok, A成功去处理业务逻辑,B失败但是一直在循环添加redis
- A执行业务逻辑完成后删除redis的key,B就可以添加redis成功,去处理相应的业务逻辑
- redis锁一定要设置过期时间,防止A拿到锁,当时还没释放程序中途出了异常,导致B一直拿不到锁,程序卡死
自定义锁代码实现:(以下为只用setNX实现方式)
- lockkey可以是你的请求系统+请求流水等字段拼接的唯一的标识,过期时间比执行业务逻辑时间往上加一些就可以(一定要确保大于业务逻辑执行时间,否则A线程执行del时会因为自己的锁已经自动删除,而去把B线程的锁给删掉)
- 每个线程的key对应的value都不一样,在删除时可以根据value判断是否是自己的redis,避免把其他线程的key删除
- 如果A线程做删除操作时,通过value发现锁是B线程的,这时A线程应该回滚并抛出异常,防止业务重复执行
代码示例:基于redis组成的分布式锁解决方案为:
1、setNx一个锁key,相应的value为当前时间加上过期时间的时钟;
2、如果setNx成功,或者当前时钟大于此时key对应的时钟则加锁成功,否则加锁失败退出;
3、加锁成功执行相应的业务操作(处理共享数据源);
4、释放锁时判断当前时钟是否小于锁key的value,如果当前时钟小于锁key对应的value则执行删除锁key的操作
public boolean getLock(String lockKey, long timeout) {
boolean success; // 默认获取锁失败 false
long value = System.currentTimeMillis() + timeout; // 设置锁值
success = setnx(lockKey,value);
if (!success) {
Long keyValue = queryObjectByKey(lockKey, Long.class);
long oldValue = keyValue == null ? 0L : keyValue.longValue();
// 锁已经超时
if (oldValue < System.currentTimeMillis()) {
long getValue = getSet(lockKey, value, Long.class);
// 获取锁成功
if (getValue == oldValue) {
success = true;
} else {
// 已被其他进程捷足先登了
success = false;
}
} else {
// 未超时,则直接返回失败
success = false;
}
}
return success;
}
业务代码使用自定义锁
// 生成redisKey:根据自己业务去拼接一个不唯一的字符串作为Key
String lockKey = RedisKeyConstant.REDIS_SENTINEL_PREFIX + KeysGeneratorUtil.createRedisKey(
InfoBO.getRequestSystem(), InfoBO.getEquityNo());
try {
// 获取锁
boolean lockResult = redisUtil.getLock(lockKey, 5000L);
if (!lockResult) {
log.error("获取锁失败,redisKey:{}",lockKey);
throw new EquityServiceException("错误码");
}
} catch (Exception e) {
log.error("获取锁异常,redisKey:{}",lockKey);
throw new ServiceException("错误码");
}
// 加锁成功,处理业务代码
try {
// 加锁成功,处理业务代码
} catch (Exception e ) {
// 执行业务代码异常
} finally {
// 根据锁Key释放redis锁:实际上是删除的setNx中的key
redisUtil.deleteKey(lockKey);
}