SpringBoot:解决定时任务多机器部署问题

背景:

项目中 定时任务的场景很多,单机部署没问题,多台机器部署就会有重复执行问题,那么如何解决这类问题!

方案:

1、 拆分,单独拆分出来,单独跑一个应用
2、基于aop拦截处理(抢占执行),只要有一个执行,其它都不执行(前提:服务器时间一致

第一种方案就不用说了,说下基于redis如何实现:

定时任务Aop一样可以处理的,多台同个任务类似抢占,先抢到的则打标识记录在Redis中,根据有无标识去执行任务

代码:

1.自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface RedisLock {
    //锁前缀
    String lockPrefix() default "redisLock:";

    //键
    String lockKey() default "";

    //默认超时时间 秒
    long timeOut() default 6;

    TimeUnit timeUnit() default TimeUnit.SECONDS;
}

2.AOP部分

@Aspect
@Component
public class RedisLockAspect {
    private static final Integer Max_RETRY_COUNT = 3;
    private static final String LOCK_PRE_FIX = "lockPreFix";
    private static final String LOCK_KEY = "lockKey";
    private static final String TIME_OUT = "timeOut";
    private static final int PROTECT_TIME = 2 << 11;//4096
    private static final Logger log = LoggerFactory.getLogger(RedisLock.class);
    @Autowired
    private CommonRedisHelper commonRedisHelper;

    @Pointcut("@annotation(com.zanzung.common.annotation.RedisLock)")
    public void redisLockAspect() {
    }

    @Around(value = "redisLockAspect()")
    public void lockAroundAction(ProceedingJoinPoint proceeding) {
        //获取redis锁
        boolean flag = this.getLock(proceeding, 0, System.currentTimeMillis());
        if (flag) {
            try {
                proceeding.proceed();
                Thread.sleep(PROTECT_TIME);
            } catch (Throwable throwable) {
                throw new RuntimeException("分布式锁执行发生异常" + throwable.getMessage(), throwable);
            } finally {
                // 删除锁
                this.delLock(proceeding);
            }
        } else {
            log.info("其他系统正在执行此项任务");
        }
    }

    //获取锁
    private boolean getLock(ProceedingJoinPoint proceeding, int count, long currentTime) {
        //获取注解中的参数
        Map<String, Object> annotationArgs = this.getAnnotationArgs(proceeding);
        String lockPrefix = (String) annotationArgs.get(LOCK_PRE_FIX);
        String key = (String) annotationArgs.get(LOCK_KEY);
        long expire = (long) annotationArgs.get(TIME_OUT);
        if (StringUtils.isEmpty(lockPrefix) || StringUtils.isEmpty(key)) {
            throw new RuntimeException("RedisLock,锁前缀|锁名未设置");
        }
        if (commonRedisHelper.setNx(lockPrefix, key, expire)) {
            log.info("@@@-> " + Thread.currentThread().getName() + "已获取到锁");
            return true;
        } else {
            //如果当前时间与锁的时间差,大于保护时间,则强制删除锁(防止死锁)
            long createTime = commonRedisHelper.getLockValue(lockPrefix, key);
            if ((currentTime - createTime) > (expire * 1000 + PROTECT_TIME)) {
                count++;
                if (count > Max_RETRY_COUNT) {
                    return false;
                }
                commonRedisHelper.delete(lockPrefix, key);
                getLock(proceeding, count, currentTime);
            }
            log.error("正在执行的定时任务Key:" + key);
            log.info("@@@-> " + Thread.currentThread().getName() + "获取锁失败");
            return false;
        }

    }

    /**
     * 删除锁
     */
    private void delLock(ProceedingJoinPoint proceeding) {
        Map<String, Object> annotationArgs = this.getAnnotationArgs(proceeding);
        String lockPrefix = (String) annotationArgs.get(LOCK_PRE_FIX);
        String key = (String) annotationArgs.get(LOCK_KEY);
        commonRedisHelper.delete(lockPrefix, key);
    }

    /**
     * 获取锁参数
     *
     * @param proceeding
     * @return
     */
    private Map<String, Object> getAnnotationArgs(ProceedingJoinPoint proceeding) {
        Class target = proceeding.getTarget().getClass();
        Method[] methods = target.getMethods();
        String methodName = proceeding.getSignature().getName();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Map<String, Object> result = new HashMap<String, Object>();
                RedisLock redisLock = method.getAnnotation(RedisLock.class);
                result.put(LOCK_PRE_FIX, redisLock.lockPrefix());
                result.put(LOCK_KEY, redisLock.lockKey());
                result.put(TIME_OUT, redisLock.timeUnit().toSeconds(redisLock.timeOut()));
                return result;
            }
        }
        return new HashMap<>();
    }

}

3. CommonRedisHelper 部分

@Component
public class CommonRedisHelper {

    @Autowired
    RedisTemplate<Object, Object> redisTemplate;

    /**
     * 添加分布式锁
     */
    public boolean setNx(String track, String sector, long timeout) {
        boolean flag = false;
        ValueOperations<Object, Object> valueOperations = redisTemplate.opsForValue();
        flag = valueOperations.setIfAbsent(track + sector, System.currentTimeMillis());
        if (flag) {
            valueOperations.set(track + sector, getLockValue(track, sector), timeout, TimeUnit.SECONDS);
        }
        return flag;
    }

    /**
     * 删除锁
     *
     * @param lockPrefix 前缀
     * @param key        键
     */
    public void delete(String lockPrefix, String key) {
        redisTemplate.delete(lockPrefix + key);
    }

    /**
     * 查询锁
     *
     * @return 写锁时间
     */
    public long getLockValue(String track, String sector) {
        ValueOperations valueOperations = redisTemplate.opsForValue();
        return (long) valueOperations.get(track + sector);
    }


}

4. 方法上加上自定义注解

定时任务加上注解

										END
  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值