基于Reids与AOP实现的定时任务锁-ScheduledLock

2022-11-22 20:18:08 浏览数 (1)

需求: 项目中的定时任务会定时执行,但如果部署多个服务器的话就会在同一时间,每个服务都会执行一次,如果是新建或者修改类的操作的话就会有冲突,因此需要一个方案处理这个多处执行的问题。

简单来说就是通过aop环绕切片将需要加锁的方法包起来,然后在执行前往redis中用setIfAbsent写入一个key,如果返回true,说明没有其他服务正在执行该方法,就继续执行,如果返回false,说明已经有其他服务正在执行该方法,就不执行。

为了方便使用采用了自定义注解的方式,如果哪个定时任务需要使用的话直接加一个@ScheduledLock注解即可。

该方案使用到了redis,使用以及配置方法略过。

实现如下:

引入相关依赖

代码语言:javascript复制
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

添加自定义注解接口

代码语言:javascript复制
package com.miracle.qaodo.annotation;
import java.lang.annotation.*;

/**
 * @Author Diuut
 * @Date 2021/2/23  16:17
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ScheduledLock {
}

实现AOP逻辑

代码语言:javascript复制
package com.miracle.qaodo.aspect;

import com.miracle.qaodo.annotation.ScheduledLock;
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.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

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

/**
 * @Author Diuut
 * @Date 2021/2/23  16:20
 */
@Aspect
@Component
@Slf4j
public class ScheduleLockAspect implements ApplicationContextAware {
    //直接autowired redistemplate会报错Unexpected error occurred in scheduled task
    //因为@Scheduled注解方式级别高于资源注入级别,导致了资源注入失败
    //使用ApplicationContextAware,它实现了这个接口的bean,当spring容器初始化的时候,会自动的将ApplicationContext注入进来

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    public static ApplicationContext getContext() {
        return context;
    }

    public static Object getBean(String name) {
        return getContext().getBean(name);
    }

    @Around(value = "@annotation(scheduledLock)")
    public Object around(ProceedingJoinPoint point, ScheduledLock scheduledLock) {
        //拦截的类名
        Class clazz = point.getTarget().getClass();
        //拦截的方法
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        log.info("定时任务锁 拦截了类:"   clazz   " 方法:"   method);
        Object proceed = null;
        RedisTemplate<String,String> redisTemplate = (RedisTemplate)getBean("redisTemplate");
        if (redisTemplate.opsForValue().setIfAbsent("qdchess-SchdulesLock-"   method.getName(), "lock",10, TimeUnit.SECONDS)) {
        //此处的key是可以根据自己的使用情况进行设置,只要方法之间不重复即可。
            log.info("其他服务未执行,通过执行");
            //获取锁,如果为false说明有其他服务正在执行,跳过执行
            try {
                proceed = point.proceed();          //执行定时任务
               redisTemplate.delete("qdchess-SchdulesLock-"   method.getName());
                return proceed;
            } catch (Throwable throwable) {
                redisTemplate.delete("qdchess-SchdulesLock-"   method.getName());
                throwable.printStackTrace();
                return null;
            }
        }
        log.info("其他服务已执行,未通过执行");
        return proceed;
    }
}

在测试编写期间反复报错Unexpected error occurred in scheduled task,经查询是因为@Scheduled注解方式级别高于资源注入级别,导致了资源注入失败,解决方案是类实现ApplicationContextAware,它实现了这个接口的bean,当spring容器初始化的时候,会自动的将ApplicationContext注入进来。

使用方法就是在使用定时任务的地方加一个@ScheduledLock注解即可。如:

代码语言:javascript复制
    @Scheduled(cron = "0,20,40 * * * * ?")
    @ScheduledLock
    public void testLock(){
        log.info(ServerTimer.getFull());
    }

Post Views: 291

0 人点赞