SpringBoot 定时任务 @Scheduled 集群环境优化 (使用分布式锁, 注解形式)

SpringBoot提供了 Schedule模块完美支持定时任务的执行

在实际开发中由于项目部署在分布式或集群服务器上 会导致定时任务多次触发

因此,使用redis分布锁机制可以有效避免多次执行定时任务

  核心方法是org.springframework.data.redis.core包下的

 setIfAbsent() 方法 返回值为布尔类型

  方法类似redis的SETNX命令 即”SET if Not Exists”

  服务器在执行邮件定时发送任务之前会向redis缓存中写入lock_key即任务锁 表明此服务器正在执行定时任务

  另一台服务器在写入锁时 由于锁已经存在就不做任何操作

  执行定时任务的服务器在执行完成后需释放任务锁
 

具体代码实现如下:

定义注解:

/**
 * redis锁注解
 * @author zhouzhou
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface RedisLock {
 
    String lockPrefix() default "";
    String lockKey() default "";
    long timeOut() default 5;
    TimeUnit timeUnit() default TimeUnit.SECONDS;
 
}

定义切面@Aspect, pointCut就是 RedisLock注解

/**
 * Description: redis锁拦截器实现
 * User: zhouzhou
 * Date: 2018-09-05
 * Time: 15:30
 */
@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(RedisLockAspect.class);
 
    @Autowired
    private CommonRedisHelper commonRedisHelper;
 
 
    @Pointcut("@annotation(com.shuige.components.cache.annotation.RedisLock)")
    public void redisLockAspect() {
    }
 
    @Around("redisLockAspect()")
    public void lockAroundAction(ProceedingJoinPoint proceeding) throws Exception {
      
        //获取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("其他系统正在执行此项任务");
        }
 
    }
 
    /**
     * 获取锁
     *
     * @param proceeding
     * @return
     */
    private boolean getLock(ProceedingJoinPoint proceeding, int count, long currentTime) {
        //获取注解中的参数
        Map 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);
        //String key = this.getFirstArg(proceeding);
        if (StringUtils.isEmpty(lockPrefix) || StringUtils.isEmpty(key)) {
            // 此条执行不到
            throw new RuntimeException("RedisLock,锁前缀,锁名未设置");
        }
        if (commonRedisHelper.setNx(lockPrefix, key, expire)) {
            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);
            }
            return false;
        }
    }
 
    /**
     * 删除锁
     *
     * @param proceeding
     */
    private void delLock(ProceedingJoinPoint proceeding) {
        Map 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 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 result = new HashMap();
                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 null;
    }
 
    /**
     * 获取第一个String类型的参数为锁的业务参数
     *
     * @param proceeding
     * @return
     */
    @Deprecated
    public String getFirstArg(ProceedingJoinPoint proceeding) {
        Object[] args = proceeding.getArgs();
        if (args != null && args.length > 0) {
            for (Object object : args) {
                String type = object.getClass().getName();
                if ("java.lang.String".equals(type)) {
                    return (String) object;
                }
            }
        }
        return null;
    }
 
}

CommonRedisHelper

/**
 * Description:
 * User: zhouzhou
 * Date: 2018-09-05
 * Time: 15:39
 */
@Component
public class CommonRedisHelper {
 
    @Autowired
    RedisTemplate redisTemplate;
 
    /**
     * 加分布式锁
     *
     * @param track
     * @param sector
     * @param timeout
     * @return
     */
    public boolean setNx(String track, String sector, long timeout) {
        ValueOperations valueOperations = redisTemplate.opsForValue();
 
        Boolean flag = valueOperations.setIfAbsent(track + sector, System.currentTimeMillis());
        // 如果成功设置超时时间, 防止超时
        if (flag) {
            valueOperations.set(track + sector, getLockValue(track, sector), timeout, TimeUnit.SECONDS);
        }
        return flag;
    }
 
    /**
     * 删除锁
     *
     * @param track
     * @param sector
     */
    public void delete(String track, String sector) {
        redisTemplate.delete(track + sector);
    }
 
    /**
     * 查询锁
     * @return 写锁时间
     */
    public long getLockValue(String track, String sector) {
        ValueOperations valueOperations = redisTemplate.opsForValue();
        long createTime = (long) valueOperations.get(track + sector);
        return createTime;
    }
 
}

使用场景:

    @Scheduled(cron = "0,30 * * * * ? ")
    @RedisLock(lockPrefix = "hello",lockKey = "world")
    public void hello(){
        System.out.println("每隔30秒定时任务测试,当前时间为:" + new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒").format(new Date()));
    }

你可能感兴趣的:(spring,boot,分布式锁,Scheduled,集群)