Redis 实现分布式锁注解 及 Redission应用

Redis 实现分布式锁注解 及 Redission应用

写在前面

本文使用 Redis 实现的分布式锁,通过注解的方式应用,可以避免了对业务代码的侵入。

使用版本

jdk: 12
redis:5.0.4-alpine
spring-boot-starter-parent : 2.3.1.RELEASE
redission:3.11.4

文件说明

代码实现

ps: spring-boot-starter-parent 版本号2.3.1.RELEASE

pom.xml

  <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>2.3.1.RELEASE</version>
        </dependency>

        <!--   redisson   -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.11.4</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

application.yml

spring:
  redis:
    host: 127.0.0.1
    port: 6789
    password: root
    database: 0
    timeout: 3S

AnnotationResolver.java

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;

public class AnnotationResolver {

    private static AnnotationResolver resolver ;

    public static AnnotationResolver newInstance(){

        if (resolver == null) {
            return resolver = new AnnotationResolver();
        }else{
            return resolver;
        }

    }

    /**
     * 解析注解上的值
     * @param joinPoint
     * @param str 需要解析的字符串
     * @return
     */
    public Object resolver(JoinPoint joinPoint, String str) {

        if (str == null) return null ;

        Object value = null;
        if (str.matches("#\\{\\D*\\}")) {// 如果name匹配上了#{},则把内容当作变量
            String newStr = str.replaceAll("#\\{", "").replaceAll("\\}", "");
            if (newStr.contains(".")) { // 复杂类型
                try {
                    value = complexResolver(joinPoint, newStr);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                value = simpleResolver(joinPoint, newStr);
            }
        } else { //非变量
            value = str;
        }
        return value;
    }


    private Object complexResolver(JoinPoint joinPoint, String str) throws Exception {

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

        String[] names = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();
        String[] strs = str.split("\\.");

        for (int i = 0; i < names.length; i++) {
            if (strs[0].equals(names[i])) {
                Object obj = args[i];
                Method dmethod = obj.getClass().getDeclaredMethod(getMethodName(strs[1]), null);
                Object value = dmethod.invoke(args[i]);
                return getValue(value, 1, strs);
            }
        }

        return null;

    }

    private Object getValue(Object obj, int index, String[] strs) {

        try {
            if (obj != null && index < strs.length - 1) {
                Method method = obj.getClass().getDeclaredMethod(getMethodName(strs[index + 1]), null);
                obj = method.invoke(obj);
                getValue(obj, index + 1, strs);
            }

            return obj;

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private String getMethodName(String name) {
        return "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1).toUpperCase());
    }


    private Object simpleResolver(JoinPoint joinPoint, String str) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] names = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();

        for (int i = 0; i < names.length; i++) {
            if (str.equals(names[i])) {
                return args[i];
            }
        }
        return null;
    }
}

DistributedLock.java

import java.lang.annotation.*;
/**
 * DistributedLock
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedLock {

    String lockKey() default "NONE";
    String tip() default "正在操作中";
}

DistributedLockAop.java

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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

@Aspect
@Component
@Slf4j
public class DistributedLockAop {

    @Autowired
    private RedissonClient redissonClient;

    @Around("@annotation(com.example.demo.aop.DistributedLock)")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {

        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        DistributedLock distributedLock = method.getAnnotation(DistributedLock.class);
        String lockKey = (String) AnnotationResolver.newInstance().resolver(joinPoint, distributedLock.lockKey());
        String failGetLockTip = (String) AnnotationResolver.newInstance().resolver(joinPoint, distributedLock.tip());

        // 可自定义规则
        lockKey= "LOCK:"+method.getName()+":"+lockKey;
        RLock lock = redissonClient.getLock(lockKey);
        boolean locked = false;
        try {
            locked = getLock(lock, lockKey);
            if (!(locked)) {
                log.info("已经有人占用了锁...");
                throw new InterruptedException(failGetLockTip);
            }
            log.info("获取到锁...,{}",lockKey);
            return joinPoint.proceed();
        } finally {
            try {
                if (locked) {
                    lock.unlock();
                }
            } catch (Exception ignore) {}
        }
    }

    private boolean getLock(RLock rLock, String lockKey) {
        try {
            // leaseTime: 锁有效时间 防止锁一直被占用
            // waitTime: 等待时间 没有获取到锁继续等待的时间
            return rLock.tryLock(1, 1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            log.error("Fail add lock,  key: [{}] ", lockKey, e);
            return false;
        }
    }
}

RedissionConfig.java

@Configuration
public class RedissionConfig {

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private String port;
    @Value("${spring.redis.password}")
    private String password;

    @Bean
    public RedissonClient getRedisson() {

        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
        //添加主从配置
        //        config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
        return Redisson.create(config);
    }

}

TestService.java 业务类

import com.example.demo.aop.DistributedLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class TestService {

    @DistributedLock(lockKey ="#{id}",tip = "正在获取中...")
    public String getMsg(String id) {
        try {
            // 模拟业务运行 0.5s
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "MSG: "+id;
    }

}

测试类

@SpringBootTest
class DemoApplicationTests {

    @Autowired
    private TestService testService;

    @SneakyThrows
    @Test
    public void test() {
        String id  = "1";
        // 模拟并发
        for (int i = 0; i < 50; i++) {
            new Thread(()-> {
                try {
                    testService.getMsg(id);
                } catch (Exception ignored) {}
            }).start();
        }
        new CountDownLatch(1).await(); //防止程序没执行完退出
    }
}

ps: 以上的代码直接复制到项目可以直接跑,后面为解释。

使用方式:
在方法上使用@DistributedLock注解即可,lockKey 为唯一关键字,这些可以使用参数做为唯一值,tip 表示如果没有获取到锁抛出异常的提示内容

    @DistributedLock(lockKey ="#{key}",tip = "正在获取中...")
    public String test(String key) {
       //...  业务
    }

测试运行:
在测试类里模拟并发情况
效果展示:
业务0.1s执行完之后,陆续有人获取锁。而为什么获取锁的都在前面呢?因为在锁的配置(rLock.tryLock(1, 1, TimeUnit.SECONDS);)使用了waitTime 为1s,所以会等一秒再获取锁,没获取到才放弃。
前面的线程0.1s就可以获取到锁就先输出了获取到锁,后面的线程1s后一并到期,就都输出了已经有人占用了锁

--- [     Thread-135] com.example.demo.aop.DistributedLockAop  : 获取到锁...,LOCK:getMsg:1
--- [     Thread-104] com.example.demo.aop.DistributedLockAop  : 获取到锁...,LOCK:getMsg:1
--- [     Thread-108] com.example.demo.aop.DistributedLockAop  : 获取到锁...,LOCK:getMsg:1
--- [     Thread-145] com.example.demo.aop.DistributedLockAop  : 获取到锁...,LOCK:getMsg:1
--- [     Thread-136] com.example.demo.aop.DistributedLockAop  : 获取到锁...,LOCK:getMsg:1
--- [     Thread-110] com.example.demo.aop.DistributedLockAop  : 获取到锁...,LOCK:getMsg:1
--- [     Thread-125] com.example.demo.aop.DistributedLockAop  : 获取到锁...,LOCK:getMsg:1
--- [     Thread-146] com.example.demo.aop.DistributedLockAop  : 获取到锁...,LOCK:getMsg:1
--- [     Thread-148] com.example.demo.aop.DistributedLockAop  : 获取到锁...,LOCK:getMsg:1
--- [     Thread-106] com.example.demo.aop.DistributedLockAop  : 获取到锁...,LOCK:getMsg:1
--- [     Thread-142] com.example.demo.aop.DistributedLockAop  : 已经有人占用了锁...
--- [     Thread-119] com.example.demo.aop.DistributedLockAop  : 已经有人占用了锁...
--- [     Thread-105] com.example.demo.aop.DistributedLockAop  : 已经有人占用了锁...
--- [     Thread-109] com.example.demo.aop.DistributedLockAop  : 已经有人占用了锁...
...
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值