一种redis命令搞定基于redis的分布式锁

基于redis的分布式锁项目肯定经常用到,主要是为了避免重复处理,或者由于并发带来的脏数据或者错误的处理。

使用锁就必须注意一下几点:
1、互斥,同一时间不能有多个client能获取到锁
2、不能发生死锁,不能因为有锁的client因为崩溃或者解锁命令请求失败导致无法释放锁
3、自己只能解自己上的锁,不能删除别人的锁

下面介绍一下代码为什么这么写
1、为什么不用setnx和expire两个命令来实现加锁,因为如果再执行setnx后加过期时间崩溃了,就无法解锁。
2、为什么需要使用随机字符串来做lockvalue,主要是为了防止不同client的锁都是一样的,防止client误删
3、为什么还需要Lua脚本代码来实现删除锁,纵然我们在删除之前比对了锁,但是get和del之间不是原子性的,所以也防止删除别人的锁,用lua脚本代码实现原子操作

下面是一个不侵占业务的通过redis分布式锁来执行的方法代码,当然这里只是参考,一种解决问题的方案,至于业务实现怎么写,大家可以随便发挥。

/**
 * 分布式锁
 *
 * @param r             继承于Redis
 * @param lockKey       分布式锁的key
 * @param voidMethod    获得到锁后需要执行的方法
 * @param expireSeconds 锁定时间 单位/s(需要评估方法执行时间)
 * @param retrySeconds  重试间隔 单位/s
 * @throws ServiceException
 */
public <R extends Redis> void lock(R r, String lockKey, VoidMethodInterface voidMethod, int expireSeconds, int retrySeconds) throws ServiceException {
    long begin = 0L;
    long retryMills = retrySeconds * 1000;
    //获取随机字符串,避免lockValue相同
    String lockValue = UUID.randomUUID().toString();
    while (begin <= retryMills) {
        String response = r.set(lockKey, lockValue, "nx", "ex", expireSeconds);
        if (response != null) {
            logger.debug("lock method exec begin");
            try {
                voidMethod.exec();
            } catch (Exception e) {
                logger.error("method exec failed ,msg:%s", e.getMessage());
            } finally {
                String value = r.get(lockKey);
                //当缓存里还有该key对应的值时,才去删除锁,避免执行时间过长导致锁被释放
                if (!StringUtils.isEmpty(value) && lockValue.equals(value)) {
                    // 避免若在此时,这把锁突然不是这个客户端的,则会误解锁
                    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                    r.eval(script, Collections.singletonList(lockKey), Collections.singletonList(lockValue));
                }
            }
            logger.debug("lock method exec success");
            return;
        } else {
            long waitMills = random.nextInt(WAIT_INTERVAL_MIN_MILLS, WAIT_INTERVAL_MAX_MILLS);
            try {
                Thread.sleep(waitMills);
            } catch (InterruptedException ex) {
                throw new UnexpectedStateException(ex);
            }
            begin = begin + waitMills;
            logger.debug("等待获取锁,当前等待时间:%sms", begin);
        }
    }
    logger.error("等待获取锁超时");
}
上一篇:两个并发集合ConcurrentHashMap和CopyOnWriteArrayList知识点汇集


下一篇:《资本说》极客帮创始人蒋涛(一)