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