1. 程式人生 > >註解形式實現,Redis分散式鎖

註解形式實現,Redis分散式鎖

Redis工具類參考我的博文:https://blog.csdn.net/weixin_38399962/article/details/82753763

一個註解就可以實現分散式鎖?這麼神奇麼?

首先定義註解:

/**
 * Description:分散式Redis鎖
 * User: zhouzhou
 * Date: 2018-09-25
 * Time: 10:55
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface DLock {

    String lockName() default ""; // 鎖名
    int retryTimes() default 0; // 重試次數
    long retryWait() default 0; // 重試等待時間,單位 : ms

}

再定義切面:

 *     先獲取鎖, 獲取不到則繼續等待(指定時間), 失敗次數(指定)次後跳出, 消費降級(丟擲,系統繁忙稍後再試)
 *     如果沒有重試次數,方法返回null 記得捕獲NP
 *     當重試次數有, 但是重試間隔時間沒寫, 預設200ms 間隔

/**
 * Description: 分散式鎖
 * <p>
 *     先獲取鎖, 獲取不到則繼續等待(指定時間), 失敗次數(指定)次後跳出, 消費降級(丟擲,系統繁忙稍後再試)
 *     如果沒有重試次數,方法返回null 記得捕獲NP
 *     當重試次數有, 但是重試間隔時間沒寫, 預設200ms 間隔
 * </p>
 * User: zhouzhou
 * Date: 2018-09-25
 * Time: 10:58
 * version: 1.0
 */
@Aspect
@Component
public class DLockAspect {

    private static final Logger log = LoggerFactory.getLogger(DLockAspect.class);
    private static final String LOCK_NAME = "lockName";
    private static final String RETRY_TIMES = "retryTimes";
    private static final String RETRY_WAIT = "retryWait";


    @Autowired
    private CommonRedisHelper redisHelper;

    @Pointcut("@annotation(com.shuige.components.cache.annotation.DLock)")
    public void dLockAspect() {
    }

    @Around("dLockAspect()")
    public Object lockAroundAction(ProceedingJoinPoint proceeding) throws Throwable {
        System.out.println("分散式鎖測試");
        //獲取註解中的引數
        Map<String, Object> annotationArgs = this.getAnnotationArgs(proceeding);
        String lockName = (String) annotationArgs.get(LOCK_NAME);
        Assert.notNull(lockName, "分散式,鎖名不能為空");
        int retryTimes = (int) annotationArgs.get(RETRY_TIMES);
        long retryWait = (long) annotationArgs.get(RETRY_WAIT);

        // 獲取鎖
        boolean lock = redisHelper.lock(lockName);
        if (lock) {
            // 執行主邏輯
            return execut(proceeding, lockName);
        } else {
            // 如果重試次數為零, 則不重試
            if (retryTimes <= 0) {
                log.info(String.format("{%s}已經被鎖, 不重試", lockName));
                throw new RuntimeException(String.format("{%s}已經被鎖, 不重試", lockName));
            }

            if (retryWait == 0) {
                retryWait = 200;
            }
            // 設定失敗次數計數器, 當到達指定次數時, 返回失敗
            int failCount = 1;
            while (failCount <= retryTimes) {
                // 等待指定時間ms
                try {
                    Thread.sleep(retryWait);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (redisHelper.lock(lockName)) {
                    // 執行主邏輯
                    return execut(proceeding, lockName);
                } else {
                    log.info(String.format("{%s}已經被鎖, 正在重試[ %s/%s ],重試間隔{%s}毫秒", lockName, failCount, retryTimes, retryWait));
                    failCount++;
                }
            }
            throw new RuntimeException("系統繁忙, 請稍等再試");
        }

    }

    private Object execut(ProceedingJoinPoint proceeding, String lockName) throws Throwable {
        try {
            // 執行主邏輯
            Object proceed = proceeding.proceed();
            return proceed;
        } catch (Throwable throwable) {
            log.error(String.format("執行分散式鎖發生異常鎖名:{%s},異常名稱:{%s}", lockName, throwable.getMessage()));
            throw throwable;
        } finally {
            redisHelper.delete(lockName);
        }
    }

    /**
     * 獲取鎖引數
     *
     * @param proceeding
     * @return
     */
    private Map<String, Object> getAnnotationArgs(ProceedingJoinPoint proceeding) {
        Class target = proceeding.getTarget().getClass();
        Method[] methods = target.getMethods();
        String methodName = proceeding.getSignature().getName();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Map<String, Object> result = new HashMap<String, Object>();
                DLock redisLock = method.getAnnotation(DLock.class);
                String firstArg = getFirstArg(proceeding);
                result.put(LOCK_NAME, redisLock.lockName().concat("_").concat(firstArg));
                result.put(RETRY_TIMES, redisLock.retryTimes());
                result.put(RETRY_WAIT, redisLock.retryWait());
                return result;
            }
        }
        return null;
    }

    /**
     * 獲取第一個String型別的引數為鎖的業務引數
     *
     * @param proceeding
     * @return
     */
    public String getFirstArg(ProceedingJoinPoint proceeding) {
        Object[] args = proceeding.getArgs();
        if (args != null && args.length > 0) {
            for (Object object : args) {
                String type = object.getClass().getName();
                if ("java.lang.String".equals(type)) {
                    return (String) object;
                }
            }
        }
        return null;
    }

}

使用場景:

在方法上定義註解即可, redis的key就是 lockName_cityCode, 重試次數5次, 重試間隔300ms