日常工作中,面对订单重复提交、消息重复消费等并发场景时,经常需要采用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;
}
}