解决XXLJOB重复执行问题--Redis加锁+注解+AOP-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

基于Redis加锁+注解+AOP解决JOB重复执行问题

现象

线上xxljob有时候会遇到同一个任务在调度的时候重复执行如下图
在这里插入图片描述

线上JOB服务运行了2个实例有时候会重复调度到同一个实例有时候会重复调度到不同实例上对于Job重复执行会存在很多风险可以采用Redis加锁的方式来解决。这里用统一的方式提供这个内部功能其他Job或者从管理页面进来的请求直接执行Job可以都限制住保证同一时间分布式环境中只有一个实例在运行。

解决方案

自定义注解

首先定义一个自定义注解将redis加锁需要的参数可以通过注解声明

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface JobNoRepete {

    String name();

    String redisKey();

    long expireTime();

    TimeUnit timeUnit();
}

定义AOP策略

@Component
@Aspect
@Slf4j
public class JobNoRepeteAop {

    @Resource
    private RedisService redisService;

    @Around(value = "@annotation(annotation)", argNames = "pj,annotation")
    public Object around(ProceedingJoinPoint pj, JobRepetitionDefense annotation) throws Throwable {
        String name = annotation.name();
        String redisKey = annotation.redisKey();
        long expireTime = annotation.expireTime();
        TimeUnit timeUnit = annotation.timeUnit();
        log.info("job执行防重开始执行,name={},redisKey={},expireTime={},timeUnit={}",
                name, redisKey, expireTime, timeUnit);
        try {
            return redisService.executeOnlyOnce(redisKey, expireTime, timeUnit, pj::proceed);
        } finally {
            log.info("job执行防重执行完成,name={}", name);
        }
    }

}

redis 加锁

redis 加锁逻辑使用spring redis中的StringRedisTemplate

@Slf4j
@Component
public class RedisService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public <T> T executeOnlyOnce(String redisKey, long expireTime, TimeUnit timeUnit, CustomCallable<T> callable) throws Throwable {
        if (StrUtil.isBlank(redisKey) || expireTime <= 0 || Objects.isNull(timeUnit) || Objects.isNull(callable)) {
            throw new IllegalArgumentException("参数错误");
        }
        String uuid = UUID.randomUUID().toString();
        if (!stringRedisTemplate.opsForValue().setIfAbsent(redisKey, uuid, expireTime, timeUnit)) {
            throw new RuntimeException("任务正在执行,请稍后再试");
        }
        //执行逻辑
        try {
            return callable.call();
        } finally {
            //执行完成主动释放锁
            try {
                String oldValue = stringRedisTemplate.opsForValue().get(redisKey);
                if (Objects.equals(uuid, oldValue)) {
                    stringRedisTemplate.delete(redisKey);
                }
            } catch (Exception e) {
                //释放锁失败等待expireTime后自动释放
                log.error("释放锁异常", e);
            }
        }
    }
}

public interface CustomCallable<V> {
    V call() throws Throwable;
}

实践

对于适用的场景就可以直接使用注解的方式进行声明例如

@Service
@Slf4j
public class testService {

    private static final int EXPIRE_HOURS = 24;

    @JobNoRepete(name = "测试redis", redisKey = Constant.JOB_LOCK_TO_REDIS,
        expireTime = EXPIRE_HOURS, timeUnit = TimeUnit.HOURS)
    public void test(LocalDate localDate) {
         //内部逻辑
    }
}
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: redis

“解决XXLJOB重复执行问题--Redis加锁+注解+AOP-CSDN博客” 的相关文章

Redis教程:Redis 持久化1年前 (2023-02-02)
15.SpringBoot 整合 Redis1年前 (2023-02-02)
Redis面试题1年前 (2023-02-02)
redis集群1年前 (2023-02-02)
17道Redis 面试题1年前 (2023-02-02)
redis 数据库简介1年前 (2023-02-02)
Redis常用的数据类型1年前 (2023-02-02)