redis luaスクリプトベースの同期ロック(ビジネス編)
lockツールクラス:
ビジネスサービス実装クラス:
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.Objects;
@Slf4j
public class LockUtils {
private static StringRedisTemplate stringRedisTemplate = SpringContextUtils.getBean(StringRedisTemplate.class);
private static final String LUA_LOCK_SCRIPT = "if redis.call('get',KEYS[1]) then return 0 else " +
"if redis.call('set',KEYS[1],ARGV[1]) then if redis.call('expire',KEYS[1],ARGV[2]) " +
"then return 1 else return 0 end else return 0 end end";
private static final String LUA_RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return " +
"redis.call('del', KEYS[1]) else return 0 end ";
public static boolean lockByLua(String key, String val, long expiredTime, int retryCount) {
if (!initRedisTemplate()) {
return false;
}
if (StringUtils.isBlank(key) || StringUtils.isBlank(val) || expiredTime < 1L) {
log.error("LockUtils: get lock`s parameters error! key=({}), val=({}),expiredTime=({})," +
"retryCount=({})", key, val, expiredTime, retryCount);
return false;
}
RedisCallback redisCallback = connection -> {
Object conObj = connection.getNativeConnection();
if (conObj instanceof RedisClusterConnection) {
return ((RedisClusterConnection) conObj).eval(LUA_LOCK_SCRIPT.getBytes(), ReturnType.INTEGER, 2,
key.getBytes(), key.getBytes(), val.getBytes(), (expiredTime + "").getBytes());
}
return connection.eval(LUA_LOCK_SCRIPT.getBytes(), ReturnType.INTEGER, 2, key.getBytes(),
key.getBytes(), val.getBytes(), (expiredTime + "").getBytes());
};
Object result;
do {
result = stringRedisTemplate.execute(redisCallback);
if (Objects.nonNull(result) && Integer.parseInt(result.toString()) == 1) {
log.info("LockUtils: get lock success!");
return true;
}
retryCount--;
} while (retryCount > 0);
log.info("LockUtils: get lock fail!");
return false;
}
public static boolean releaseLockByLua(String key, String val) {
log.info("LockUtils: release lock`s parameters ! key=({}), val=({}))", key, val);
if (!initRedisTemplate()) {
return false;
}
if (StringUtils.isBlank(key) || StringUtils.isBlank(val)) {
return false;
}
RedisCallback redisCallback = connection -> {
Object conObj = connection.getNativeConnection();
if (conObj instanceof RedisClusterConnection) {
return ((RedisClusterConnection) conObj).eval(LUA_RELEASE_LOCK_SCRIPT.getBytes(), ReturnType.INTEGER, 1,
key.getBytes(), val.getBytes());
}
return connection.eval(LUA_RELEASE_LOCK_SCRIPT.getBytes(), ReturnType.INTEGER, 1,
key.getBytes(), val.getBytes());
};
Object result = stringRedisTemplate.execute(redisCallback);
if (Integer.parseInt(result.toString()) == 1) {
log.info("LockUtils: release lock success!");
return true;
}
log.info("LockUtils: release lock fail!");
return false;
}
private static boolean initRedisTemplate() {
if (Objects.isNull(stringRedisTemplate)) {
log.error("LockUtils: StringRedisTemplate initialize fail!");
return false;
}
return true;
}
}
ビジネスサービス実装クラス:
@Override
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
public CommunityApplyInfo applyWithoutRsp(CommunityQualificationApplyReqVo req, Long uid) {
//
Date now = DateTime.now().toDate();
String lockKey = IcommunityConstants.SYNC_LOCK_KEY.APPLY_UID + uid;// id key
String lockVal = Thread.currentThread().getName() + UUIDUtils.getUUID() + 123456 + now.getTime();// value uuid
boolean lockStatus = false;
try {
lockStatus = LockUtils.lockByLua(lockKey, lockVal, expiredTime, retryCount);
if (!lockStatus) {
log.error("lockKey={}, ", lockKey);
throw new BusinessException(IcommunityErrorInfoEnum.LOCK_ERROR_APPLY);
}
// ##########
.....
.....
// ##########
} catch (BusinessException be) {
throw new BusinessException(be.getCode(), be.getErrorMsg());
} catch (Exception e) {
log.error(" ,message:{},error:{}", e.getMessage(), e);
throw new BusinessException(IcommunityErrorInfoEnum.SERVICE_ERROR);
} finally {
if (lockStatus && !LockUtils.releaseLockByLua(lockKey, lockVal)) {
throw new BusinessException(IcommunityErrorInfoEnum.UNLOCK_ERROR_APPLY);
}
}
}