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

猜你喜欢

转载自blog.csdn.net/u012903200/article/details/85008105