redis 分布式锁

V1

tryLock(key){  
    if (( SETNX Key 1 ) == 1){   //@1
        EXPIRE Key Seconds       //@2
        return true
    }
    return false
}
release(key){  
  DELETE Key
}

缺点:

  1. 当@1执行成功后,由于[业务系统宕机]导致@2没有设置成功,造成其他client永远无法上锁
  2. 当@1执行成功后,由于[redis宕机发生主从切换]导致@2没有设置成功,造成其他client永远无法上锁
  3. tryLock成功后由于业务执行异常或者gc导致EXPIRE 过期超时,另一个client获取到锁,造成多个客户端同时执行
  4. 在第三个缺点的基础上可能误删除另一个客户端上的锁

V2

tryLock(key,UniqId){ 
    if (( SETNX Key UniqId ExpireSeconds) == 1){  //保证原子性
        return true
    }
    return false 
}
release(key,UniqId){  
    EVAL(                        //lua脚本保证原子性
      //LuaScript
      if redis.call("get",KEYS[1]) == ARGV[1] then  
          return redis.call("del",KEYS[1])
      else
          return 0
      end
    )
}

缺点:

  1. 这个方案redis单机情况下是目前最优的分布式锁方案,但是如果在Redis集群环境下依然存在问题:由于Redis集群数据同步为异步,假设在Master节点获取到锁后未完成数据同步情况下Master节点crash,此时在新的Master节点依然可以获取锁,所以多个Client同时获取到了锁,只能是业务代码做好幂等调用

java实现

package com.paulzhangcc.common.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.paulzhangcc.common.util.InetUtils.findFirstNonLoopbackAddress;


@Component
@ConditionalOnProperty(prefix = "lock.redis", name = "enabled", matchIfMissing = true)
public class RedisLock {

    @Autowired
    RedisTemplate<String, String> stringRedisTemplate;

    private final static Logger logger = LoggerFactory.getLogger(RedisLock.class);
    /**
     * 单个锁有效期
     */
    private static final int DEFAULT_SINGLE_EXPIRE_SECONDS = 1000;
    /**
     * 批量锁有效期
     */
    private static final int DEFAULT_BATCH_EXPIRE_SECONDS = 1000;
    /**
     * 尝试间隔时间
     */
    private static final int RETRY_INTERVAL_SECONDS = 100;

    private static InetAddress inetAddress = findFirstNonLoopbackAddress();

    public String generateUniqueValue() {
        String host = "";
        if (inetAddress != null) {
            host = host + inetAddress.getHostAddress() + "-";
        }
        return host + UUID.randomUUID().toString().replaceAll("-", "");
    }

    protected int retryInterval() {
        return RETRY_INTERVAL_SECONDS;
    }

    protected int singleLockExpireSeconds() {
        return DEFAULT_SINGLE_EXPIRE_SECONDS;
    }

    protected int batchLockExpireSeconds() {
        return DEFAULT_BATCH_EXPIRE_SECONDS;
    }

    protected boolean seeLockCompetition() {
        return false;
    }

    public boolean tryLock(String key, String generateUniqueValue) {
        return tryLock(key, generateUniqueValue, 0L, null);
    }

    public boolean tryLock(String key, String generateUniqueValue, long timeout, TimeUnit unit) {
        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(generateUniqueValue)) {
            throw new IllegalArgumentException();
        }
        try {
            logger.info("lock key start:" + key + ",value:" + generateUniqueValue);
            //系统计时器的当前值,以毫微秒为单位。
            long nano = System.nanoTime();
            do {
                Boolean setIfAbsent = stringRedisTemplate.opsForValue().setIfAbsent(key, generateUniqueValue, singleLockExpireSeconds(), TimeUnit.SECONDS);
                if (setIfAbsent != null && setIfAbsent.booleanValue()) {
                    logger.info("lock key end:" + key + ",isSuccess:true");
                    return Boolean.TRUE;
                } else if (logger.isDebugEnabled() || seeLockCompetition()) {
                    String desc = stringRedisTemplate.opsForValue().get(key);
                    logger.info("lock key: " + key + " locked by another business:" + desc);
                }
                if (timeout <= 0) {
                    break;
                }
                Thread.sleep(retryInterval());
            } while ((System.nanoTime() - nano) < unit.toNanos(timeout));
        } catch (Exception e) {
            logger.error("lock key error:" + key, e);
        }
        logger.info("lock key end:" + key + ",isSuccess:false");
        return Boolean.FALSE;
    }

    public boolean tryLock(List<String> keys, String generateUniqueValue) {
        return tryLock(keys, generateUniqueValue, 0L, null);
    }

    public boolean tryLock(List<String> keys, String generateUniqueValue, long timeout, TimeUnit unit) {
        if (keys == null || keys.size() == 0 || StringUtils.isEmpty(generateUniqueValue)) {
            throw new IllegalArgumentException();
        }
        logger.info("lock keys start:" + keys + ",value:" + generateUniqueValue);
        try {
            Map<String, String> mkeys = new HashMap<>(8);
            long nano = System.nanoTime();
            for (String key : keys) {
                mkeys.put(key, generateUniqueValue);
            }
            do {
                Boolean multiSetIfAbsent = stringRedisTemplate.opsForValue().multiSetIfAbsent(mkeys);
                if (multiSetIfAbsent != null && multiSetIfAbsent.booleanValue()) {
                    for (String key : keys) {
                        stringRedisTemplate.expire(key, batchLockExpireSeconds(), TimeUnit.SECONDS);
                    }
                    logger.info("lock keys end:" + keys + ",isSuccess:true");
                    return Boolean.TRUE;
                } else if (logger.isDebugEnabled() || seeLockCompetition()) {
                    String desc = stringRedisTemplate.opsForValue().get(keys.get(0));
                    logger.info("lock keys: " + keys + " locked by another business:" + desc);
                }

                if (timeout <= 0) {
                    break;
                }
                Thread.sleep(retryInterval());
            } while ((System.nanoTime() - nano) < unit.toNanos(timeout));

        } catch (Exception e) {
            logger.error("lock keys error:" + keys, e);
        }
        logger.info("lock keys end:" + keys + ",isSuccess:false");
        return Boolean.FALSE;
    }

    public RedisScript<String> unlockScript = RedisScript.of(
            "local v = ARGV[1]\n" +
                    "for i,key in pairs(KEYS) do\n" +
                    "\tlocal v1 = redis.call(\"get\",key)\n" +
                    "\tif (v == v1) \n" +
                    "\tthen\n" +
                    "\t\tredis.call(\"del\",key)\n" +
                    //"\t\tredis.log(redis.LOG_NOTICE,\"delete key=\"..key..\",value=\"..v1)\n" +
                    "\tend\n" +
                    "end  ");

    public void unLock(String key, String generateUniqueValue) {
        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(generateUniqueValue)) {
            throw new IllegalArgumentException();
        }
        List<String> keyList = new ArrayList();
        keyList.add(key);
        unLock(keyList, generateUniqueValue);
    }

    public void unLock(List<String> keys, String generateUniqueValue) {
        if (keys == null || keys.size() == 0 || StringUtils.isEmpty(generateUniqueValue)) {
            throw new IllegalArgumentException();
        }
        stringRedisTemplate.execute(unlockScript, keys, generateUniqueValue);
    }
}


package com.paulzhangcc.common.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.Enumeration;

/**
 * @author paul
 * @description
 * @date 2019/3/13
 */

public class InetUtils {
    private final static Logger logger = LoggerFactory.getLogger(InetUtils.class);

    public static InetAddress findFirstNonLoopbackAddress() {
        InetAddress result = null;
        try {
            int lowest = Integer.MAX_VALUE;
            for (Enumeration<NetworkInterface> nics = NetworkInterface
                    .getNetworkInterfaces(); nics.hasMoreElements();) {
                NetworkInterface ifc = nics.nextElement();
                if (ifc.isUp()) {
                    logger.trace("Testing interface: " + ifc.getDisplayName());
                    if (ifc.getIndex() < lowest || result == null) {
                        lowest = ifc.getIndex();
                    }
                    else if (result != null) {
                        continue;
                    }
                }
            }
        }
        catch (IOException ex) {
            logger.error("Cannot get first non-loopback address", ex);
        }

        if (result != null) {
            return result;
        }

        try {
            return InetAddress.getLocalHost();
        }
        catch (UnknownHostException e) {
            logger.warn("Unable to retrieve localhost");
        }
        return null;
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 目标:1.种房子:四房二厅二卫,130平方米以上,全明,交通便利,带一个固定车位,小区环境优美,学区房 2.种财富...
    1C611艳燕阅读 1,033评论 0 0
  • 自从工作一直用的是outlook系统,其他系统不太熟悉,以下所讲均为outlook使用的情况,不再复述 首先我觉得...
    军_862c阅读 4,284评论 0 0
  • 说起妈宝男,大家可能会比较熟悉。妈宝男,顾名思议就是什么都听妈妈的,什么都认为妈妈是对的,什么都以妈妈为中心的男人...
    伊文说阅读 11,401评论 4 7
  • 如果我是小草,宁愿期待燎原的星火,将我化为灰烬,转年,便又是一株嫩嫩的新芽。如果我是枝头的枯叶,宁愿狂风吹散...
    永恒丫丫阅读 1,670评论 0 1