分布式系统利器-分布式锁

136 阅读4分钟

日常工作中,面对订单重复提交、消息重复消费等并发场景时,经常需要采用synchronized或lock操作对请求进行加锁处理,如果处于分布式场景中时,就需要采用分布式锁了。常见实现分布式锁的实现方式有redis、zk等,下面以redis+自定义注解来实现分布式锁

类图

定义分布式锁注解

@Documented
@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
/**
 * 方法维度并发锁注解
 */
public @interface ConcurrentLock {

    /**
     * 对个key场景表达式,支持EL表达式,args+参数索引=具体入参
     * 例如:singleKey=args0.key
     * @return
     */
    String singleKey() default "";

    /**
     * 多个key场景表达式,支持EL表达式,args+参数索引=具体入参
     * 例如:keyList=args0.keyList
     * @return
     */
    String keyList() default "";

    /**
     * key的类型
     * @return
     */
    Class<?> keyClassType() default Long.class;

    /**
     * 过期级别(默认是:20秒)
     * @return
     */
    ExpireLevelEnum expireLevel() default ExpireLevelEnum.TWENTY_SECOND;

    /**
     * 锁类型
     * @return
     */
    ConcurrentLockTypeEnum lockType() default ConcurrentLockTypeEnum.FAST_FAIL;

    /**
     * 锁等待最大超时时间,在指定锁类型为等待时使用,单位s
     * @return
     */
    int maxWaitTime() default 2;
}

分布式锁工厂

public class LockFactory {

    private final static Map<String, AbstractLock> LOCK_MAP = new HashMap<>();

    static {
        LOCK_MAP.put(ConcurrentLockTypeEnum.FAST_FAIL.name(), new FastFailLock());
        LOCK_MAP.put(ConcurrentLockTypeEnum.WAIT.name(), new WaitLock());
    }


    public static AbstractLock getLock(String type) {
        return LOCK_MAP.get(type);
    }

}

抽象分布式锁定义

public interface AbstractLock {

    Boolean tryLock(ConcurrentLock lockAnno, TccLock tccLock, Long startTs);
}

快速失败的分布式锁实现

public class FastFailLock implements AbstractLock {

    @Override
    public Boolean tryLock(ConcurrentLock lockAnno, TccLock tccLock, Long startTs) {
        return tccLock.tryLock(lockAnno.expireLevel().getTime());
    }
}

支持重试的分布式锁实现

public class WaitLock implements AbstractLock {

    @Override
    public Boolean tryLock(ConcurrentLock lockAnno, TccLock tccLock, Long startTs) {
        boolean result = false;
        while(!result && System.currentTimeMillis() - startTs < lockAnno.maxWaitTime() * 1000) {
            result = tccLock.tryLock(lockAnno.maxWaitTime());
            try {
                if(!result) {
                    ComponentLogUtil.logConcurrent("wait lock fail", "WaitLock", "tryLock", startTs,
                        tccLock.getKeyVal(), false);
                    Thread.sleep(100L);
                }
            } catch (InterruptedException e) {
                break;
            }
        }
        return result;
    }
}

分布式锁切面

@Aspect
@Component
@Order(10)
public class ConcurrentLockAspect {

    /**
     * key的前缀
     */
    @Value("${operate.lock.group}")
    private String keyPrefix;

    @Autowired
    private TccLockManager tccLockManager;

    @Resource
    private ComponentSwitchConfig componentSwitchConfig;

    private static final String APPENDER = "_";

    @Pointcut("@annotation(com.cainiao.wms.wmp.common.component.concurrentlock.ConcurrentLock)")
    public void concurrentLockPointcut() {}

    @Around("concurrentLockPointcut()")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String signature = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        if (!componentSwitchConfig.isConcurrentOpen()) {
            return joinPoint.proceed(args);
        }
        long starTime = System.currentTimeMillis();

        List<TccLock> tccLockList = Lists.newArrayList();
        Method currentMethod = AopUtil.getMethodFromJoinPoint(joinPoint);
        ConcurrentLock lockAnnotation = currentMethod.getAnnotation(ConcurrentLock.class);
        //没有配置key也直接返回
        if (methodHasNoKey(lockAnnotation)) {
            return joinPoint.proceed(args);
        }
        Map<String, Boolean> lockResultMap = Maps.newHashMap();
        try {
            List<String> keyList = buildKeyList(args, joinPoint, lockAnnotation);
            List<String> unReentrantKeyList = keyList.stream().filter(
                key -> !ThreadContextHolder.getReentrantFlag().contains(key))
                .collect(Collectors.toList());
            //发现重入,直接返回
            if (CollectionUtils.isEmpty(unReentrantKeyList)) {
                return joinPoint.proceed(args);
            }
            tccLockList = getKeyList(unReentrantKeyList);
            for (TccLock tccLock : tccLockList) {
                boolean success = LockFactory.getLock(lockAnnotation.lockType().name()).tryLock(lockAnnotation, tccLock,
                    System.currentTimeMillis());
                lockResultMap.put(tccLock.getKeyVal(), success);
                ComponentLogUtil.logConcurrent("try lock", className, signature, starTime,
                    tccLock.getKeyVal(), success);
                if (!success) {
                    throw new WmpBaseException("concurrent", "操作太频繁,稍后重试");
                }
            }

            //标记已经重入
            ThreadContextHolder.addReentrantFlags(Sets.newHashSet(unReentrantKeyList));
            return joinPoint.proceed(args);
        } catch (Throwable t) {
            throw t;
        } finally {
            if (CollectionUtils.isNotEmpty(tccLockList)) {
                Set<String> removeKeySet = tccLockList.stream().map(lock -> lock.getKeyVal()).collect(
                    Collectors.toSet());
                ThreadContextHolder.removeReentrantFlags(removeKeySet);
                for (TccLock tccLock : tccLockList) {
                    Boolean lockSuccess = lockResultMap.get(tccLock.getKeyVal());
                    if (lockSuccess != null && lockSuccess) {
                        boolean success = tccLock.releaseLock();
                        ComponentLogUtil.logConcurrent("release lock", className, signature, starTime,
                            tccLock.getKeyVal(), success);
                    }
                }
            }
        }
    }

    /**
     * 构建锁列表
     *
     * @param args
     * @param joinPoint
     * @param lockAnnotation
     * @return
     */
    public List<String> buildKeyList(Object[] args, ProceedingJoinPoint joinPoint, ConcurrentLock lockAnnotation) {
        List<Object> keyList = ElUtil.evaluateKeyList(joinPoint, lockAnnotation, args);
        return keyList.stream().map(key -> keyPrefix + APPENDER + key).collect(Collectors.toList());
    }

    /**
     * 获取锁
     *
     * @param keyList
     * @return
     */
    public List<TccLock> getKeyList(List<String> keyList) {
        return keyList.stream().map(key -> tccLockManager.getLock(key, key)).collect(Collectors.toList());
    }

    /**
     * 注解没有配置key
     *
     * @param lockAnnotation
     * @return
     */
    private boolean methodHasNoKey(ConcurrentLock lockAnnotation) {
        if (lockAnnotation == null) {
            return true;
        }
        if (StringUtils.isEmpty(lockAnnotation.singleKey()) && StringUtils.isEmpty(lockAnnotation.keyList())) {
            return true;
        }
        return false;
    }

}

Rdb分布式锁Manager

public class RdbLockManager implements TccLockManager {

    public static RdbLockManager build(RdbLockRespository rdbLockRepository) {
        RdbLockManager rdbLockManager = new RdbLockManager();
        rdbLockManager.setRdbLockRespository(rdbLockRepository);
        return rdbLockManager;
    }

    private RdbLockRespository rdbLockRespository;

    public TccLock getLock(String key,String value) {
        return new TccRdbLock(rdbLockRespository, key, value);
    }

    public RdbLockRespository getRdbLockRespository() {
        return rdbLockRespository;
    }

    public void setRdbLockRespository(RdbLockRespository rdbLockRespository) {
        this.rdbLockRespository = rdbLockRespository;
    }
}

Rdb分布式锁实现

public class TccRdbLock implements TccLock {


    /**
     * 分布式锁句柄
     */
    private RdbLockRespository lockRepository;


    /**
     * key
     */
    private String lockKey;

    /**
     * value
     */
    private String expectVal;

    public TccRdbLock(RdbLockRespository rdbLockRespository, String lockKey,String expectVal) {
        this.lockKey = lockKey;
        this.expectVal = expectVal;
        this.lockRepository = rdbLockRespository;
    }

    /**
     * 对KEY正常加锁
     * @param timeoutSeconds
     * @return
     */
    @Override
    public synchronized Boolean tryLock(int timeoutSeconds) {

        return lockRepository.setnx(lockKey, expectVal, timeoutSeconds);
    }



    /**
     * 释放异常锁
     * @param
     * @return
     * @throws Exception
     */
    @Override
    public synchronized Boolean releaseLock() {

        return lockRepository.deleteKey(lockKey);
    }


    @Override
    public String getRealVal(){
        return lockRepository.get(lockKey);
    }

    @Override
    public String getExpectVal(){
        return expectVal;
    }

    @Override
    public String getKeyVal(){
        return lockKey;
    }

    public RdbLockRespository getLockRepository() {
        return lockRepository;
    }

    public void setLockRepository(RdbLockRespository lockRepository) {
        this.lockRepository = lockRepository;
    }
}

Rdb分布式锁句柄

public class RdbLockRespository extends AbstractRdbOperateRespository implements TccLockRespository{

    public static RdbLockRespository build(RdbSmartApi rdbSmartApi) {
        RdbLockRespository rdbLockRespository = new RdbLockRespository();
        RedisSyncApi redisSyncApi = rdbSmartApi.sync();
        RedisSyncApiWrap apiWrap = RedisSyncApiWrap.wrap(redisSyncApi);

        rdbLockRespository.setRdbSmartApi(rdbSmartApi);
        rdbLockRespository.setRedisSyncApi(redisSyncApi);
        rdbLockRespository.setApiWrap(apiWrap);
        rdbLockRespository.setRedisAsyncApi(rdbSmartApi.async());
        return rdbLockRespository;
    }

    /**
     *  KEY前缀
     */
    private String keyPrefix = "tcc:rdblock";

    @Override
    public synchronized Boolean setnx(final String key, final String value, final int timeoutSeconds) {
        String statusCode = RdbHelper.execute("tryLock", RdbHelper.W, getDotThreshold(), key,
            new RdbCallback<String>() {

            @Override
            public String doInRdbWrapper() throws Exception {
                //获取锁
                SetParams nxAndExParam = SetParams.setParams().nx().ex(timeoutSeconds);
                return apiWrap.set(key, value, nxAndExParam);
            }
        });

        if(OK.equalsIgnoreCase(statusCode)) {
            return true;
        }

        return false;
    }

    @Override
    public synchronized Boolean cad(final String key, final String value) {
        Long statusCode = RdbHelper.execute("unlock", RdbHelper.W, getDotThreshold(), key,
            new RdbCallback<Long>() {

            @Override
            public Long doInRdbWrapper() throws Exception {
                //cad if(get(key) == value) delete(key)
                return apiWrap.cad(key, value);
            }
        });

        return statusCode == 1L;
    }

    @Override
    public synchronized Boolean deleteKey(final String key) {
        Long statusCode = RdbHelper.execute("unlock", RdbHelper.W, getDotThreshold(), key,
            new RdbCallback<Long>() {

                @Override
                public Long doInRdbWrapper() throws Exception {
                    //cad if(get(key) == value) delete(key)
                    return apiWrap.del(key);
                }
            });

        return statusCode == 1L;
    }

    @Override
    public  String get(final String key) {
        return RdbHelper.execute("getLock", RdbHelper.R, getDotThreshold(), key,
            new RdbCallback<String>() {

                @Override
                public String doInRdbWrapper() throws Exception {
                    return apiWrap.get(key);
                }
            });
    }

    public String getKeyPrefix() {
        return keyPrefix;
    }

    public void setKeyPrefix(String keyPrefix) {
        this.keyPrefix = keyPrefix;
    }
}

Redis分布式锁Manager

public class RedisLockManager implements TccLockManager {

    public static RedisLockManager build(RedisLockRespository redisLockRespository) {
        RedisLockManager redisLockManager = new RedisLockManager();
        redisLockManager.setRedisLockRespository(redisLockRespository);
        return redisLockManager;
    }

    private RedisLockRespository redisLockRespository;

    public TccLock getLock(String key,String value) {
        return new TccRedisLock(redisLockRespository, key, value);
    }

    public RedisLockRespository getRedisLockRespository() {
        return redisLockRespository;
    }

    public void setRedisLockRespository(RedisLockRespository redisLockRespository) {
        this.redisLockRespository = redisLockRespository;
    }
}

Redis分布式锁实现

public class TccRedisLock implements TccLock {


    /**
     * 分布式锁句柄
     */
    private RedisLockRespository lockRepository;


    /**
     * key
     */
    private String lockKey;

    /**
     * value
     */
    private String expectVal;

    public TccRedisLock(RedisLockRespository redisLockRespository, String lockKey, String expectVal) {
        this.lockKey = lockKey;
        this.expectVal = expectVal;
        this.lockRepository = redisLockRespository;
    }

    /**
     * 对KEY正常加锁
     * @param timeoutSeconds  超时时间是s
     * @return
     */
    @Override
    public synchronized Boolean tryLock(int timeoutSeconds) {
        return lockRepository.setnx(lockKey, expectVal, timeoutSeconds);
    }



    /**
     * 释放异常锁
     * @param
     * @return
     * @throws Exception
     */
    @Override
    public synchronized Boolean releaseLock() {
        return lockRepository.deleteKey(lockKey, expectVal);
    }


    @Override
    public String getRealVal(){
        return lockRepository.get(lockKey);
    }

    @Override
    public String getExpectVal(){
        return expectVal;
    }

    @Override
    public String getKeyVal(){
        return lockKey;
    }

    public RedisLockRespository getLockRepository() {
        return lockRepository;
    }

    public void setLockRepository(RedisLockRespository lockRepository) {
        this.lockRepository = lockRepository;
    }
}

Redis分布式句柄

public class RedisLockRespository extends AbstractRedisOperateRespository implements TccLockRespository{

    public static RedisLockRespository build(JedisPool jedisPool) {
        RedisLockRespository redisLockRespository = new RedisLockRespository();
        redisLockRespository.setJedisPool(jedisPool);
        return redisLockRespository;
    }

    /**
     *  KEY前缀
     */
    private String keyPrefix = "tcc:redislock";

    @Override
    public synchronized Boolean setnx(final String key, final String value, final int expireTime) {
        String statusCode = RedisHelper.execute(getJedisPool(), RedisHelper.SETNX, RedisHelper.W, getDotThreshold(), new JedisCallback<String>() {

            @Override
            public String doInJedis(Jedis jedis) {
                return jedis.set(key, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
            }

        });

        if (LOCK_SUCCESS.equals(statusCode)) {
            return true;
        }
        return false;
    }

    @Override
    public Boolean deleteKey(String key) {
        return null;
    }


    @Override
    public synchronized Boolean deleteKey(final String key, final String value) {
        Long result = RedisHelper.execute(getJedisPool(), RedisHelper.DELETE, RedisHelper.W, getDotThreshold(), new JedisCallback<Long>() {

            @Override
            public Long doInJedis(Jedis jedis) {
                //String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                jedis.del(key);
                //return (Long) jedis.eval(script, Collections.singletonList(key), Collections
                //    .singletonList(value));
                return 1L;
            }

        });

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

    @Override
    public  String get(final String key) {
        return RedisHelper.execute(getJedisPool(), RedisHelper.QUERY, RedisHelper.R, getDotThreshold(), new JedisCallback<String>() {

            @Override
            public String doInJedis(Jedis jedis) {
                return jedis.get(key);
            }

        });
    }

    public String getKeyPrefix() {
        return keyPrefix;
    }

    public void setKeyPrefix(String keyPrefix) {
        this.keyPrefix = keyPrefix;
    }
}