分佈式鎖:Redis+註解


title: “分佈式鎖:Redis+註解”

url: “https://wsk1103.github.io/”

tags:

  • 學習筆記
  • 分佈式鎖

1. 起因

使用 redis 來設計分佈式鎖,經常需要在每個方法開始執行 setNx(key) ,然後在方法運行結束後 del(key) 。而且當系統需要改進使用分佈式鎖的時候,需要修改的地方偏多,這樣造成了過程繁雜,而且有時候又忘記 del(key) ,導致間隔比較短請求被攔截。所以考慮設計一個結合 註解AOP 來完成分佈式鎖。

2. 思路流程圖

https://raw.githubusercontent.com/wsk1103/images/master/lock/1.1.png

3. 代碼設計

3.1 增加依賴

		<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.9.5</version>
		</dependency>

3.2 redis.setNx() 和 redis.del()

	/**
	 * 分佈式鎖
	 * 設置成功,返回1,
	 *
	 * @param key
	 * @param value
	 * @param time 過期時間,單位秒
	 * @return
	 */
	public static boolean setNx(String key, String value, int time) {
		Jedis jedis = null;
		try {
			if (StringUtils.isBlank(key)) {
				throw new NullPointerException("The key is not allowed null");
			}
			jedis = RedisDB.getPool().getResource();
			long result = jedis.setnx(key, value);
			if (result == 1) {
				// ==1 ,設置成功
				jedis.expire(key, time);
				return true;
			} else {
				return false;
			}
		} catch (Exception e) {
			ILogUtil.error("setNx fail:{}", e);
			return false;
		} finally {
			RedisDB.getPool().returnResourceObject(jedis);
		}
	}
	
	/**
	 * 刪除redis裏的主鍵
	 * 
	 * @param key redis主鍵
	 */
	public static void del(String key) {
		Jedis jedis = RedisDB.getPool().getResource();
		try {
			jedis.del(key);
		} finally {
			// 使用完後,將連接放回連接池
			RedisDB.getPool().returnResourceObject(jedis);
		}
	}

3.3 註解 @MyLock

import java.lang.annotation.*;

/**
 * @author sk
 * @time 2020/1/15
 * @desc say
 **/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyLock {
}

3.4 設計 AOP

package com.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * @author sk
 * @time 2020/1/15
 * @desc say
 **/
@Aspect
@Component
public class LockAspect {

    @Pointcut("@annotation(com.anno.MyLock)")
    public void myLock() {

    }

    @Before("myLock()")
    public void before(JoinPoint joinPoint) throws Exception {
        System.out.println("before");
        Object[] objects = joinPoint.getArgs();
        for (Object o : objects) {
            if (o instanceof String) {
                String method = joinPoint.getSignature().getName();
                String key = method + o.toString();
                boolean b = setNx(key, "", 60);
                if (!b) {
                    throw new Exception("請勿重複操作");
                } else {
                    System.out.println("=======獲取lock成功!!!!!!========");
                }
                break;
            }
        }
    }

    @AfterReturning("myLock()")
    public void after(JoinPoint joinPoint)  throws Exception {
        System.out.println("after");
        Object[] objects = joinPoint.getArgs();
        for (Object o : objects) {
            if (o instanceof String) {
                String method = joinPoint.getSignature().getName();
                String key = method + o.toString();
                del(method + o.toString());
                System.out.println("釋放鎖成功");
                break;
            }
        }
    }
}


3.5 方法注入

import com.anno.MyLock;
import org.springframework.stereotype.Service;

/**
 * @author sk
 * @time 2020/1/15
 * @desc say
 **/
@Service
public class MyService {

    @MyLock
    public String go(String go) {
        System.out.println(go);
        return "dd";
    }

}

4. 注意和設計優化

  • 4.1. 在AOP攔截時,是通過該方法入參中的第一個 String 來作爲 lockkey ,所以在調用方法時,需要使該 String 能夠識別用戶,所以該 key 一般爲用戶的身份標識,例如 sessionId
  • 4.2. 或者將入參統一修改爲一個 抽象類 ,該抽象類裏面有一個 用戶唯一標識 的字段,然後其他類繼承該類。
    例如
import lombok.Data;

/**
 * @author wsk1103
 * @date 2020/1/15
 * @description 描述
 */
@Data
public abstract class AbstractRequest {
    private String sessionId;
}

對應的 AOP 修改爲:

    @Before("myLock()")
    public void before(JoinPoint joinPoint) throws Exception {
        System.out.println("before");
        Object[] objects = joinPoint.getArgs();
        for (Object o : objects) {
            if (o instanceof AbstractRequest) {
                String method = joinPoint.getSignature().getName();
                String key = method + ((AbstractRequest)o).getSessionId();
                boolean b = setNx(key, "", 60);
                if (!b) {
                    throw new Exception("請勿重複操作");
                } else {
                    System.out.println("=======獲取lock成功!!!!!!========");
                }
                break;
            }
        }
    }

    @After("myLock()")
    public void after(JoinPoint joinPoint)  throws Exception {
        System.out.println("after");
        Object[] objects = joinPoint.getArgs();
        for (Object o : objects) {
            if (o instanceof AbstractRequest) {
                String method = joinPoint.getSignature().getName();
                String key = method + ((AbstractRequest)o).getSessionId();
                del(method + o.toString());
                System.out.println("釋放鎖成功");
                break;
            }
        }
    }
發佈了79 篇原創文章 · 獲贊 153 · 訪問量 60萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章