基於redis分布式鎖注解實現


基於redis分布式鎖注解實現

  • 1、編寫注解

  • 2、編寫切面

  • 3、如何使用

1、編寫注解

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
public @interface RedisLock {
    
    String key();
    
    // 並發鎖key
    String value();
    
    // 鎖定時長 默認單位秒
    long ttl() default 5;

}

 

 

2、編寫切面

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import com.yun.common.api.redis.RedisService;
import com.yun.common.base.response.BusinessException;
import com.yun.common.base.utils.AssertUtil;

import lombok.extern.slf4j.Slf4j;

@Aspect
@Component
@Slf4j
public class RedisLockAspect {
    
    @Autowired
    private RedisService redisService;

    @Pointcut("execution(public * com.yun.*.app.*.service..*.*(..))")
    public void appPointCut(){};
    
    @Pointcut("execution(public * com.yun.*.domain..*.*(..))")
    public void domainPointCut(){};

    @Around("(appPointCut() && @annotation(redisLock)) || (domainPointCut() && @annotation(redisLock)))")
    public Object before(ProceedingJoinPoint pJoinPoint, RedisLock redisLock) throws Throwable {
        String key = redisLock.key();
        String value = redisLock.value();
        long time = redisLock.ttl();
        
        AssertUtil.notNull(key, "獲取並發鎖key為空。", key);
        AssertUtil.notNull(value, "獲取並發鎖value為空。", value);

        value = this.getRedisKey(pJoinPoint, value);
        String lockKey = key.concat(value);
        
        // 1、獲取鎖
        RLock lock = redisService.getRLock(lockKey);
        // 2、鎖定
        try {
            AssertUtil.isTrue(lock.tryLock(time, TimeUnit.SECONDS), "獲取並發鎖[%s]失敗。", lockKey);
            // 3、業務邏輯
            return pJoinPoint.proceed();
        } catch (BusinessException e) {
            log.error("獲取鎖失敗。", e);
            throw e;
        }finally {
            // 4、釋放鎖
            lock.unlock();
        }
        
    }

    private String getRedisKey(ProceedingJoinPoint pJoinPoint, String key) {
        //使用SpringEL表達式解析注解上的key
        SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(key);
        //獲取方法入參
        Object[] parameterValues = pJoinPoint.getArgs();
        //獲取方法形參
        MethodSignature signature = (MethodSignature)pJoinPoint.getSignature();
        Method method = signature.getMethod();
        DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
        String[] parameterNames = nameDiscoverer.getParameterNames(method);
        if (parameterNames == null || parameterNames.length == 0) {
            //方法沒有入參,直接返回注解上的key
            return key;
        }
        //解析表達式
        EvaluationContext evaluationContext = new StandardEvaluationContext();
        // 給上下文賦值
        for(int i = 0 ; i < parameterNames.length ; i++) {
            evaluationContext.setVariable(parameterNames[i], parameterValues[i]);
        }
        try {
            Object expressionValue = expression.getValue(evaluationContext);
            if (expressionValue != null && !"".equals(expressionValue.toString())) {
                //返回el解析后的key
                return expressionValue.toString();
            }else{
                //使用注解上的key
                return key;
            }
        } catch (Exception e) {
            //解析失敗,默認使用注解上的key
            return key;
        }

    }
    
}

3、如何使用

3.1、方式一:編碼方式

  • 注入RedisService

  • 應用分布式鎖

適用場景:邏輯復雜,長事務場景。

注意:

1、存在第三方調用邏輯時,必須指定超時時間,且超時時間必須小於鎖定時間。

2、應盡量提煉業務,縮短鎖定范圍。

3、合理設置鎖定時間,避免出現鎖超時的情況。

3.1.1、注入RedisService

@Autowired
protected RedisService redisService;

 

3.1.2、應用分布式並發鎖

     // 1、獲取鎖
      RLock lock = redisService.getRLock(key);
      // 2、鎖定
  AssertUtil.isTrue(lock.tryLock(20, TimeUnit.SECONDS), ResultEnum.DATA_LOCKED);
  try {
  //TODO 3、業務邏輯
  } catch (Exception e) {
log.error("業務異常", e);
} finally {
// 4、釋放鎖
lock.unlock();
}

 

3.2、方式二:注解方式

  • 添加注解

適用場景:邏輯簡單,耗時短。

注意:合理設置鎖定時間,避免出現鎖超時的情況。

如下:

 @RedisLock(key = ProductRedisKey.TEST_KEY, value = "#listProductQuery.orderNo", ttl = 20)
@Override
public List<ProductVO> list(ListProductQuery listProductQuery) {

// TODO 業務邏輯

return BeanCopierUtil.copyPropertiesOfList(
productDubboService
.list(BeanCopierUtil.copyProperties(listProductQuery, ListProductDTO.class)),
ProductVO.class);
}

參數解釋:

@RedisLock注解可以添加在應用端的 com.yun.*.app.*.service 下

可以添加在業務端的 com.yun.*.domain 下

key 表示鎖的目錄,比如“system:test:”

value為鎖定的具體業務單據號"123456",可以使用el表達式,比如 #listProductQuery.orderNo

ttl為鎖定時間,默認為5秒

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM