分布式锁:Redis+注解

萝らか妹 提交于 2020-01-16 00:54:29

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/aspectjrt -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>1.9.5</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.9.5</version>
			<scope>runtime</scope>
		</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.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

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

    @Before("@annotation(com.anno.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;
            }
        }
    }

    @After("@annotation(com.anno.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.masget.business.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("@annotation(com.anno.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("@annotation(com.anno.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;
            }
        }
    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!