美文网首页
Aop实现redis分布式锁-基于jedis

Aop实现redis分布式锁-基于jedis

作者: wang_cheng | 来源:发表于2020-08-13 10:49 被阅读0次

翻着写的加锁代码很别扭,为啥要重复写这些加锁逻辑,干脆改一改

市面上已有成熟的封装好的分布式锁,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));

相关文章

网友评论

      本文标题:Aop实现redis分布式锁-基于jedis

      本文链接:https://www.haomeiwen.com/subject/qokbdktx.html