嗨
翻着写的加锁代码很别扭,为啥要重复写这些加锁逻辑,干脆改一改
市面上已有成熟的封装好的分布式锁,Redission,有兴趣可以去了解下
重复造下轮子....
原本是这样的,代表我要在加锁的地方每次都得写这些业务外的代码
public void test(){
String requestId = redisService.lockRetry("锁的Key",避免死锁设置过期时间);
if (requestId == null){
//获取锁异常
return;
}
try {
//执行service业务
}catch (Exception e){
}finally {
redisService.releaseDistributedLock("锁的key",requestId);
}
}
优化后
@RdLock(bizKey = LockBizKey.SHOES_CARD_EXCHANGE_KEY)
public void tset(){
//执行业务逻辑
}
优化优化........
- 定义一个注解,通过切面进行加锁解锁
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
/**
* lockKey = 项目名称(自动拼上)+bizKey+value 保证唯一
* 支持可重入,注意内部调用aop失效问题
* @author wangcheng
* @date 2020/8/12
*/
public @interface RdLock {
/**
* 业务key,如果bizKey不传默认类名+方法名,如果bizKey和动态参数都不传,这样会只允许一个线程进入(根据业务需求设定合理的key)
* @return: java.lang.String
**/
String bizKey() default "";
/**
* 如果需要bizKey后拼接动态参数用这种格式,#xxx ,多个用{#xxx,#xxx},对象使用 #user.getName()
* @return: java.lang.String
**/
String value() default "";
/**
* 锁超时释放时间,避免死锁(根据业务合理设定)
* @return: long
**/
long lockTimeOut() default 60000L;
/**
* 是否尝试阻塞重新获取锁
* @return: boolean
**/
boolean attemptedWait() default true;
int retryCount() default 3;
long retryInterval() default 500L;
/**
* 获取不到锁,异常提示
* @return: java.lang.String
**/
String errMsg() default "请稍后再试";
}
- 定义一个操作redis加锁解锁的类
/**
* @author wangcheng
* @date 2020/8/12
*/
@Component
@Slf4j
public class RdLockService {
@Autowired
private JedisCluster jedis;
/**
* Only set the key if it does not already exist.
*/
private static final String SET_IF_NOT_EXIST = "NX";
/**
* Set the specified expire time, in milliseconds.
*/
private static final String SET_WITH_EXPIRE_MILL_TIME = "PX";
/**
* Set the specified expire time, in seconds.
*/
private static final String SET_WITH_EXPIRE_SECOND_TIME = "EX";
private static final String LOCK_SUCCESS = "OK";
private static final Long RELEASE_SUCCESS = 1L;
/**
* 设置分布式锁.(过期时间毫秒)
*
* @param lockKey 锁的名称.
* @param requestId 持锁人ID.
* @param expireTime 锁的超时时间.
* @return 是否获取锁.
*/
public boolean tryGetDistributeLock(String lockKey, String requestId, long expireTime) {
log.info("lock key: {}, ownerId: {}, expire time: {}", lockKey, requestId, expireTime);
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_MILL_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
/**
* 释放分布式锁.
*
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功.
*/
public boolean releaseDistributedLock(String lockKey, String requestId) {
log.debug("release lock key: {}, requestId: {}", lockKey, requestId);
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
- 处理注解的核心逻辑
/**
* @author wangcheng
* @date 2020/8/12
*/
@Aspect
@Component
@Slf4j
public class RdHandler implements ApplicationContextAware {
private ApplicationContext applicationContext;
private RdLockService rdLockService;
/**
* 根据项目实际配置xxx.application.name 是spring开头 还是server开头
**/
private static final String BASE_NAME = "server.application.name";
private static ThreadLocal<AtomicInteger> holder = new ThreadLocal<>();
@Pointcut("@annotation(rdLock)")
public void annotationPointCut(RdLock rdLock) {
}
@Around(value = "annotationPointCut(rdLock)")
public Object lockHandler(ProceedingJoinPoint pjp, RdLock rdLock) throws Throwable {
boolean lock;
String lockKey = "";
String requestId = "";
if (holder.get()==null) {
holder.set(new AtomicInteger(1));
}else {
int incr = holder.get().incrementAndGet();
log.info("进行重入重新获取锁操作 thread: {} ,reentrant: {} ", Thread.currentThread().getName(), incr);
}
try {
String className = pjp.getSignature().getDeclaringTypeName();
MethodSignature signature = (MethodSignature) pjp.getSignature();
String methodName = signature.getName();
log.info("handler lock method: {} ", className + "#" + methodName);
String bizKey = rdLock.bizKey();
if (StringUtils.isBlank(bizKey)) {
bizKey = className + ":" + methodName;
}
String elValue = rdLock.value();
String argValue = "";
boolean elValueFlag = StringUtils.isNotBlank(elValue);
if (elValueFlag) {
argValue = parseElExpression(pjp.getArgs(), ((MethodSignature) pjp.getSignature()).getParameterNames(), elValue, String.class);
}
lockKey = buildKey(bizKey, argValue, elValueFlag);
requestId = getRequestId();
if (rdLock.attemptedWait()) {
lock = lockRetry(lockKey, requestId, rdLock.lockTimeOut(), rdLock.retryInterval(), rdLock.retryCount());
} else {
lock = rdLockService.tryGetDistributeLock(lockKey, requestId, rdLock.lockTimeOut());
}
log.info("加锁是否成功: {} ,lockKey: {} ,requestId: {} ", lock, lockKey, requestId);
if (!lock) {
throw new BusinessException(300, rdLock.errMsg());
}
return pjp.proceed();
} finally {
int decrement = holder.get().decrementAndGet();
log.info("thread: {} ,reentrant end after decrement one return: {} ", Thread.currentThread().getName(), decrement);
boolean release = rdLockService.releaseDistributedLock(lockKey, requestId);
log.info("释放锁====lockKey: {} ,requestId: {} 释放是否成功", lockKey, requestId,release);
if (holder.get().get() < 1) {
holder.remove();
}
}
}
private String buildKey(String bizKey, String argValue, boolean elValueFlag) {
if (StringUtils.isBlank(argValue) && !elValueFlag) {
return serverPath().concat(bizKey);
}
if (argValue == null || StringUtils.contains(argValue, "null")) {
log.error("设定的动态参数有Null值,argValue: {} ", argValue);
throw new RuntimeException("业务key异常");
}
String[] split = argValue.split(",");
StringJoiner joiner = new StringJoiner(":");
for (String s : split) {
joiner.add(s);
}
return serverPath().concat(bizKey).concat(":").concat(joiner.toString());
}
private static <T> T parseElExpression(Object[] args,String[] parameters , String elExpression, Class<T> resultType) {
StandardEvaluationContext elContext = new StandardEvaluationContext();
for (int i = 0; i < parameters.length; i++) {
String paraName = parameters[i];
Object paraValue = args[i];
elContext.setVariable(paraName, paraValue);
}
return new SpelExpressionParser().parseExpression(elExpression)
.getValue(elContext, resultType);
}
private String getRequestId() {
return UUID.randomUUID().toString();
}
public boolean lockRetry(String key, String requestId, long timeOut, long interval, int retryCount) {
boolean flag;
try {
for (int i = 0; i < retryCount; i++) {
flag = rdLockService.tryGetDistributeLock(key, requestId, timeOut);
if (flag) {
return true;
}
Thread.sleep(interval);
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
public static void main(String[] args) {
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
this.rdLockService = applicationContext.getBean(RdLockService.class);
}
private String serverPath() {
Environment bean = applicationContext.getBean(Environment.class);
return bean.getProperty(BASE_NAME) == null ? "" :
(bean.getProperty(BASE_NAME).replace("-", ":").concat(":"));
}
}
针对对象获取参数名称的时候,可能一些低版本获取不到参数名称,试了一下如果jdk动态代理的话可能获取不到参数名称
可以开启cjlb代理
@EnableAspectJAutoProxy(proxyTargetClass = true)
或者通过mvc有个类可以解析到参数名称
private final DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
protected Method getMethod(JoinPoint joinPoint) {
Method method = null;
try {
Signature signature = joinPoint.getSignature();
MethodSignature ms = (MethodSignature) signature;
Object target = joinPoint.getTarget();
method = target.getClass().getMethod(ms.getName(), ms.getParameterTypes());
} catch (NoSuchMethodException e) {
log.error("SystemLogAspect getMethod error", e);
}
return method;
}
String[] params = discoverer.getParameterNames(getMethod(pjp));
网友评论