redis分布式锁(乐观锁)

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

流程

redis分布式锁(乐观锁)_第1张图片

流程说明:

  • 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);

}

你可能感兴趣的:(redis,redis,分布式,数据库)