基於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<Long> 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<Long> 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;
    }
}

業務service實現類:
 

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章