1. 程式人生 > >redis實現分散式鎖(基於lua指令碼操作)

redis實現分散式鎖(基於lua指令碼操作)

lua指令碼能保證redis的原子性操作,redis使用springboot的redistemplate


/**
 * create by abel
 * create date 2018/11/16 11:28
 * describe:請輸入專案描述
 */
public class RedisLockService {
    private static Logger logger = LoggerFactory.getLogger(RedisLockService.class);
    private RedisTemplate<String, Object> redisTemplate;

    private static final Long SUCCESS = 1L;
    /**
     * 鎖的key
     */
    private String lockKey;
    /**
     * 預設過期時間/ms
     */
    private int expireTime = 30 * 1000;
    /**
     * 預設重試時間/ms
     */
    private int retryTime = 60 * 1000;
    /**
     * 預設睡眠時間/ms
     */
    private int sleepTime = 200;
    /**
     * uuid
     */
    private static final String requestId = UUID.randomUUID().toString().replace("-", "");
    /**
     * 是否鎖定標誌
     */
    private volatile boolean locked = false;

    /**
     * 構造器
     *
     * @param redisTemplate
     * @param lockKey       鎖的key
     */
    public RedisLockService(RedisTemplate<String, Object> redisTemplate, String lockKey) {
        this.redisTemplate = redisTemplate;
        this.lockKey = lockKey;
    }

    /**
     * 構造器
     *
     * @param redisTemplate
     * @param lockKey       鎖的key
     * @param retryTime     獲取鎖的超時時間
     */
    public RedisLockService(RedisTemplate<String, Object> redisTemplate, String lockKey, int retryTime) {
        this(redisTemplate, lockKey);
        this.retryTime = retryTime;
    }

    /**
     * 構造器
     *
     * @param redisTemplate
     * @param lockKey       鎖的key
     * @param retryTime     獲取鎖的超時時間
     * @param expireTime    鎖的有效期
     */
    public RedisLockService(RedisTemplate<String, Object> redisTemplate, String lockKey, int retryTime, int expireTime) {
        this(redisTemplate, lockKey, retryTime);
        this.expireTime = expireTime;
    }

    /**
     * 構造器
     *
     * @param redisTemplate
     * @param lockKey       鎖的key
     * @param retryTime     獲取鎖的超時時間
     * @param expireTime    鎖的有效期
     * @param sleepTime     鎖的睡眠期
     */
    public RedisLockService(RedisTemplate<String, Object> redisTemplate, String lockKey, int retryTime, int expireTime, int sleepTime) {
        this(redisTemplate, lockKey, retryTime, expireTime);
        this.sleepTime = sleepTime;
    }

    public String getLockKey() {
        return lockKey;
    }

    /**
     * 獲取鎖
     *
     * @return
     */
    private boolean lock() {
        try {
            long startTime = System.currentTimeMillis();
            while (true) {
                if (this.setNx()) {
                    locked = true;
                    logger.info("[" + lockKey + "]redis get lock success");
                    return true;
                }

                //這一步是key存在返回false時執行,原因可能是key未刪除或者未到過期時間
                //在這個時間(retryTime)內,可以重試多次,如果這個時間內鎖還未釋放,那麼需要主動釋放
                if (System.currentTimeMillis() - startTime > retryTime) {
                    locked = false;
                    this.del();
                    logger.info("[" + lockKey + "]redis get lock error");
                    return false;
                }
                Thread.sleep(sleepTime);
            }
        } catch (InterruptedException e) {
            logger.error("[" + lockKey + "]redis lock error::{}", e);
            locked = false;
            return false;
        }
    }

    /**
     * 釋放鎖
     *
     * @return
     */

    private boolean unLock() {
        try {
            //刪除key
            boolean delFlag = this.del();
            logger.info("[" + lockKey + "]redis unlock success->" + delFlag);
            return delFlag;
        } catch (Exception e) {
            logger.error("[" + lockKey + "]redis unlock error::{}", e);
            return false;
        }
    }

    /**
     * lua指令碼執行能保證原子性:
     * 1.如果key不存在,設定key成功,同時設定過期時間,返回true;
     * 2.如果key存在,設定key失敗,返回false;
     *
     * @return true-成功,false-失敗
     */
    private boolean setNx() {
        String script = "if redis.call('setNx',KEYS[1],ARGV[1])  then " +
                "   if redis.call('get',KEYS[1])==ARGV[1] then " +
                "      return redis.call('expire',KEYS[1],ARGV[2]) " +
                "   else " +
                "      return 0 " +
                "   end " +
                "end";

        RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);

        //對非string型別的序列化
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId, String.valueOf(expireTime));

        return SUCCESS.equals(result);
    }

    /**
     * 刪除key
     *
     * @return true-成功,false-失敗
     */
    private boolean del() {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                "then " +
                "   return  redis.call('del', KEYS[1])" +
                "else " +
                "   return 0 " +
                "end";

        RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId);
        return SUCCESS.equals(result);
    }


    /**
     * 匿名類包裝:無返回值
     *
     * @param runnable 需要鎖住的執行程式碼
     * @return true 獲鎖成功;false 獲鎖失敗
     */
    public boolean wrap(Runnable runnable) {
        if (lock()) {
            try {
                runnable.run();
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                if (e instanceof BusinessException)
                    throw (BusinessException) e;
                else
                    throw new BusinessException(ErrorCode.SERVER_BUSY, e.getMessage());
            } finally {
                unLock();
            }
            return true;
        } else
            return false;
    }

    /**
     * 匿名類包裝:帶返回值
     */
    public <V> V wrap(Callable<V> callable) {
        if (lock()) {
            try {
                return callable.call();
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                if (e instanceof BusinessException)
                    throw (BusinessException) e;
                else
                    throw new BusinessException(ErrorCode.SERVER_BUSY, e.getMessage());
            } finally {
                unLock();
            }
        } else
            return null;
    }
}

使用方法:

        String lockKey = "key";
        RedisLockService lock = new RedisLockService(redisTemplate, lockKey);
        boolean result = lock.wrap(() -> {
            try {
                //程式碼塊

            } catch (Exception e) {
                logger.info("lock error::{}", e);
                if (e instanceof BusinessException) {
                    if (ErrorCode.SERVER_ERROR.equals(((BusinessException) e).getErrorCode())) {
                        throw new BusinessException(ErrorCode.SERVER_ERROR, "lock error");
                    }
                }
            }
        });

        if (!result) {
            throw new BusinessException(ErrorCode.SERVER_ERROR, "lock error");
        }