springBoot切面AOP注解实现开箱即用redis分布式锁

本文介绍了一种使用Redis实现分布式锁的方法,通过Hutool库简化了锁的获取和释放过程。文章详细展示了如何配置Spring Boot项目以使用Redis锁,并提供了完整的代码示例,包括锁的切面实现和测试案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

redis实现分布式锁简单又实用,这里是一个开箱即用redis分布式锁
pom文件添加除springBoot相关依赖以外的以下依赖包

<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.6.1</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.39.Final</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.1.6.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>2.0.3.RELEASE</version>
        </dependency>
/**
 * redis 锁 key
 *
 * Create Time : 2019/7/24 9:08
 *
 * @author cmw
 */
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LockKey {
}
/**
 * redis 锁
 *
 * Create Time : 2019/7/23 10:02
 *
 * @author cmw
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {

    @AliasFor("lockKey") String value() default "";

    @AliasFor("value") String lockKey() default "";

    /**
     * 超时时间 默认 30秒超时
     *
     * @return
     */
    long expireTime() default 30;

    /**
     * 读取超时时间 默认 不复读
     * @return
     */
    long readTimeout() default -1;
}
/**
 * redis 锁 切面
 *
 * Create Time : 2019/7/23 10:13
 *
 * @author cmw
 */
@Component
@Aspect
@Scope
@Order(1)
public class RedisLockAspect {

    private final Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    private RedisLockTool redisLockTool;

//    @Around("lockAspect(redisLock)")
    @Around("@annotation(redisLock)")
    public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
        System.out.println("进入切面~~~~~~~~~~~~~~~~~~~~~~~~~~");
        redisLock = AnnotationUtils.getAnnotation(redisLock, RedisLock.class);
        boolean res = false;
        String lockKey = redisLock.lockKey();
        String requestId = UUID.randomUUID().toString();

        HashedWheelTimer hashedWheelTimer = new HashedWheelTimer(Executors.defaultThreadFactory());
        Object obj = null;
        try {
            lockKey += getMethodParameterLockKey(joinPoint);
            if (!StringUtils.hasText(lockKey)) {
                lockKey = joinPoint.getSignature().getDeclaringTypeName();
            }

            if (redisLock.readTimeout() > 0) {
                res = redisLockTool.tryGetDistributedLock(lockKey, requestId, Duration.ofSeconds(redisLock.expireTime()), Duration.ofSeconds(redisLock.readTimeout()));
            } else {
                res = redisLockTool.tryGetDistributedLock(lockKey, requestId, Duration.ofSeconds(redisLock.expireTime()));
            }

            if (res) {
				// 每隔 redisLock.expireTime() / 3 秒延期一次,这里一般可以省略,让key自己过期就好了
                long delay = redisLock.expireTime() / 3;
                String finalLockKey = lockKey;
                hashedWheelTimer.newTimeout(timeout -> {
                    redisLockTool.lockWatchdog(finalLockKey);
                    hashedWheelTimer.newTimeout(timeout.task(), delay, TimeUnit.SECONDS);
                }, delay, TimeUnit.SECONDS);

                obj = joinPoint.proceed();
            } else {
                log.error("redis lock -> get lock fail!");
                //这里建议返回给客户的约定的信息
            }
        } catch (Throwable e) {
            log.error("redis lock -> ", e);
            throw e;
        } finally {
            if (res) {
				//释放锁 停止 延期行为
                hashedWheelTimer.stop();
                redisLockTool.releaseDistributedLock(lockKey, requestId);
            }
        }
        return obj;
    }

    private String getMethodParameterLockKey(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();

        Object[] objects = joinPoint.getArgs();
        StringBuffer lockKey = new StringBuffer();

		// 带特定注解的参数下标集合
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (Annotation an : parameterAnnotations[i]) {
                if (an instanceof LockKey) {
                    Assert.notNull(objects[i], "redis lock -> LockKey is null");
                    lockKey.append(objects[i].toString());
                }
            }
        }

        return lockKey.toString();
    }
}
@Component
public class RedisLockTool {

    private final Logger log = LoggerFactory.getLogger(getClass());

    public static final String PRDFIX = "lock:";

    private final StringRedisTemplate stringRedisTemplate;

    public static final String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    public RedisLockTool(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 尝试获取分布式锁
     *
     * @param lockKey    锁
     * @param requestId  请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public boolean tryGetDistributedLock(String lockKey, String requestId, Duration expireTime) {
        log.info("redis lock -> do tryGetDistributedLock lockKey:{},requestId:{}", lockKey, requestId);
//(PRDFIX + lockKey, requestId, expireTime);
        Expiration expiration = Expiration.from(expireTime.toMillis(), TimeUnit.MILLISECONDS);
        StringRedisSerializer serializer = new StringRedisSerializer();
        return stringRedisTemplate.execute(connection -> connection.set(serializer.serialize(PRDFIX + lockKey),
                serializer.serialize(requestId),
                expiration,
                RedisStringCommands.SetOption.ifAbsent()), true);
    }

    public boolean tryGetDistributedLock(String lockKey, String requestId, Duration expireTime, Duration readTimeout) {
        log.info("redis lock -> do tryGetDistributedLock lockKey:{},requestId:{}", lockKey, requestId);
        //(PRDFIX + lockKey, requestId, expireTime);
        Expiration expiration = Expiration.from(expireTime.toMillis(), TimeUnit.MILLISECONDS);
        StringRedisSerializer serializer = new StringRedisSerializer();

        long start = System.currentTimeMillis();
        long timeout = readTimeout.toMillis();

        boolean result;

        while (isTimeout(start, timeout)) {
            result = stringRedisTemplate.execute(connection -> connection.set(serializer.serialize(PRDFIX + lockKey),
                    serializer.serialize(requestId),
                    expiration,
                    RedisStringCommands.SetOption.ifAbsent()), true);

            if (result) {
                return true;
            }
        }

        return false;
    }

    private boolean isTimeout(long start, long timeout) {
        return start + timeout > System.currentTimeMillis();
    }

    /**
     * 释放分布式锁
     *
     * @param lockKey   锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public boolean releaseDistributedLock(String lockKey, String requestId) {
        log.info("redis lock -> do releaseDistributedLock lockKey:{},requestId:{}", lockKey, requestId);
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript(script, Boolean.class);
        Boolean bool = stringRedisTemplate.execute(redisScript,
                stringRedisTemplate.getKeySerializer(),
                new FastJsonRedisSerializer<>(Boolean.class),
                Collections.singletonList(PRDFIX + lockKey),
                requestId);
        if (bool == null) {
            return false;
        } else {
            return bool;
        }
    }

    /**
     * 延长锁 存活时间
     *
     * @param lockKey
     */
    public void lockWatchdog(String lockKey) {
        log.info("redis lock -> do lockWatchdog lockKey:{}", lockKey);
        stringRedisTemplate.expire(lockKey, 30, TimeUnit.SECONDS);
    }
}

测试

@Test
    public void test03() throws InterruptedException {
        SynchronousQueue queue = new SynchronousQueue<Runnable>();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, queue);
        //ExecutorService executor = Executors.newFixedThreadPool(5);
        CountDownLatch countDownLatch = new CountDownLatch(5);

        for (int j = 0; j < 5; j++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    weiXinService.t1();
                    countDownLatch.countDown();
                }
            });

        }

        countDownLatch.await();
        executor.shutdown();
    }
@RedisLock(expireTime = 2,readTimeout = 9)
    @Override
    public void t1() {
        String name = java.lang.Thread.currentThread().getName();

        System.out.println(name +"~~开始~~"+System.currentTimeMillis());
        try {
            java.lang.Thread.sleep(1000);
        } catch (Exception e) {
            System.out.println("wwtefysho;");
            e.printStackTrace();
        }
        System.out.println(name +"~~结束~~"+System.currentTimeMillis());
    }

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值