springboot的schedule集群实现

原理:

1.利用spring的schedule功能实现定时任务
2.利用redis的过期策略实现集群中定时任务的分配(单机版redis,集群的redis请考虑redission)

内容概述:

1.多线程调度定时任务
2.增加定时任务管理表(数据库或者redis中持久化)
3.基于反射机制实现动态调用不同的自定义定时任务
4.自动的根据定时任务管理表对定时任务进行增删改查
5.通过定时任务管理使定时任务只执行一次。

1.多线程调度定时任务

默认的schedule使用的是单线程,即多个定时任务需要排队执行,如果某些定时任务耗时过长,会导致其他任务排队过久,且不利于使用redis的过期策略实现分布式定时任务的分配,所以在集群情况下,定时任务需要使用多线程实现,建议:线程数大于同时可执行任务数

/**
 * @author Bight Chen
 * @Date: 2021/9/17 10:27
 * 定时任务线程池
 */
@Configuration
public class ScheduleConfig {
    @Bean
    public TaskScheduler taskScheduler(){
        //此bean对象支持根据cron表达式创建周期性任务
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        //定时任务执行线程池核心数
        //线程数一定大于当前可执行任务数
        taskScheduler.setPoolSize(50);
        //此方法会使得任务一旦被取消将立即被移除
        taskScheduler.setRemoveOnCancelPolicy(true);
        taskScheduler.setThreadNamePrefix("Schedule-");
        return taskScheduler;
    }

}

2.增加定时任务管理表(数据库或者redis中持久化)

1.需要的原因是利于自动增加或者删除所需定时任务
ps:
在mysql中做为数据表
@Data是基于lombok来简化编写 get() set() toString()等方法

@Data
public class ScheduleJob  {
 	//id
    private Long scheduleJobId;
    //动态bean
    private String beanName;
    //方法
    private String methodName;
    //参数
    private String jobParams;
  	//表达式
    private String jobCron;
    //任务名
    private String jobName;

    private String remark;

    //0停止,1正常,2已完成
    private String status;

    private String createdBy;

    private Date createdTime;

    private String lastUpdatedBy;
    }

3.基于反射机制实现动态调用不同的自定义定时任务

1.反射调用bean的工具类

/**
 * @author Bight Chen
 * @Date: 2021/9/17 11:54
 */
@SuppressWarnings("unchecked")
@Component
public class SpringToolsConfig implements ApplicationContextAware {


    private static ApplicationContext applicationContext;

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

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


}

2.动态调用bean

/**
 * @author Bight Chen
 * @Date: 2021/9/17 11:40
 */
public class TaskRunable implements Runnable {
    private static final Logger logger = LogManager.getLogger(TaskRunable.class);

    private ScheduleJob scheduleJob;

    public TaskRunable(ScheduleJob scheduleJob) {
        this.scheduleJob = scheduleJob;
    }


    private RedisTemplate redisTemplate = (RedisTemplate) SpringToolsConfig.getBean("redisTemplate");

    @Override
    public void run() {
        //周期性任务:加锁的过期时间要大于不同服务器时间之差,且小于同个任务2次执行间隔(cron)
        //抢到锁的执行,没抢到锁的等待下一次任务执行
        try {
            Object target = SpringToolsConfig.getBean(scheduleJob.getBeanName());

            Method method = null;

            if (!StringUtils.isEmpty(scheduleJob.getJobParams())) {
                method = target.getClass().getDeclaredMethod(scheduleJob.getMethodName(), String.class);
            } else {
                method = target.getClass().getDeclaredMethod(scheduleJob.getMethodName());
            }
            //考虑到集群部署时,旧版本的查找不到新增的定时任务时处理,就需要先找到bean再存redis
            //保证原子性
            if(!redisTemplate.opsForValue().setIfAbsent("schdeleJob:"+scheduleJob.getScheduleJobId().toString(), "1", 15000L,TimeUnit.MILLISECONDS)){
                logger.info("{}正在执行!!,不能重复执行",scheduleJob.getJobName());
                return;
            }
            logger.info("定时任务开始执行 - 参数:{}", scheduleJob.toString());
            long startTime = System.currentTimeMillis();
            try {
                ReflectionUtils.makeAccessible(method);

                if (!StringUtils.isEmpty(scheduleJob.getJobParams())) {
                    JSONObject jsonObject = JSONObject.parseObject(scheduleJob.getJobParams());
                    jsonObject.put("scheduleJobId", scheduleJob.getScheduleJobId());
                    method.invoke(target, JSONObject.toJSONString(jsonObject));
                } else {
                    method.invoke(target);
                }
            } catch (Exception e) {
                logger.error("定时任务执行异常 -参数:{} ,异常:{}", scheduleJob.toString(), e);
            } finally {
                Object oDelete = redisTemplate.opsForValue().get("schdeleJob:" + scheduleJob.getScheduleJobId());
                if (oDelete != null) {
                    //锁依旧存在则自动删除
                    redisTemplate.delete("schdeleJob:" + scheduleJob.getScheduleJobId().toString());
                }
            }
            long times = System.currentTimeMillis() - startTime;
            logger.info("定时任务执行结束 -参数:{},耗时:{} 毫秒", scheduleJob.toString(), times);
        } catch (Exception e) {
            logger.error("定时任务执行异常 -参数:{} ,异常:{}", scheduleJob.toString(), e);
        }
    }
}

4.自动的根据定时任务管理表对定时任务进行增删改查

/**
 * @author Bight Chen
 * @Date: 2021/9/17 13:59
 * 分布式定时任务初始化
 */
@Component
public class ScheduleInitConfig {

    private static final Logger logger = LogManager.getLogger(ScheduleInitConfig.class);

    //内存中保存定时任务数据
    private HashMap<Long, ScheduledFuture> map = new HashMap<>();


    @Autowired
    private TaskScheduler autoTaskScheduler;


    @Autowired
    private ScheduleJobService scheduleJobService;

    /**
     * 定时自动查询增加任务数据,注入定时任务
     * schedule.cron:0/15 * * * * ?
     */
    @Scheduled(cron = "${schedule.cron}")
    public void autoAddTask() {
        Long time = System.currentTimeMillis();
        try {
            List<ScheduleJob> list = scheduleJobService.selectByExample(new ScheduleJobExample());
            for (ScheduleJob scheduleJob : list) {
                if (map.get(scheduleJob.getScheduleJobId()) == null && "1".equals(scheduleJob.getStatus())) {
                    //存在 启动状态的 定时任务自动增加
                    TaskRunable taskRunable = new TaskRunable(scheduleJob);
                    ScheduledFuture future = autoTaskScheduler.schedule(taskRunable, new CronTrigger(scheduleJob.getJobCron()));
                    map.put(scheduleJob.getScheduleJobId(), future);
                    logger.info("autoAddTask,自动增加任务,参数:{}", scheduleJob.toString());
                }

            }
        } catch (Exception e) {
            logger.error("autoAddTask,error:{}", e);
        }

        //logger.info("autoAddTask,end:{}", System.currentTimeMillis() - time);
    }

    /**
     * 定时自动查询任务数据,删除过期任务列表
     * schedule.cron:0/15 * * * * ?
     */
    @Scheduled(cron = "${schedule.cron}")
    public void autoDeleteTask() {
        Long time = System.currentTimeMillis();
        try {
            List<ScheduleJob> list = scheduleJobService.selectByExample(new ScheduleJobExample());
            for (ScheduleJob scheduleJob : list) {
                if (map.get(scheduleJob.getScheduleJobId()) != null &&!"1".equals(scheduleJob.getStatus())) {
                    //非启动中的都删除
                    ScheduledFuture future = map.get(scheduleJob.getScheduleJobId());
                    future.cancel(true);
                    map.remove(scheduleJob.getScheduleJobId());
                    logger.info("autoDeleteTask,自动删除任务,参数:{}", scheduleJob.toString());
                }
            }
        } catch (Exception e) {
            logger.error("autoDeleteTask,error:{}", e);
        }

        //logger.info("autoDeleteTask,end:{}", System.currentTimeMillis() - time);
    }
}

5.通过定时任务管理使定时任务只执行一次。

/**
 * @author Bight Chen
 * @Date: 2021/9/17 13:53
 */
@Component("testTask")
public class TestTask implements BaseTask {
    private static final Logger logger = LogManager.getLogger(TestTask.class);

    @Autowired
    private ScheduleJobService scheduleJobService;
    
    @Override
    public void runTask() { }
    @Override
    public void runTask(String params) {
        JSONObject jsonObject = JSON.parseObject(params);
        Long scheduleJobId =  Long.parseLong( jsonObject.get("scheduleJobId")+"");
        try {
            Thread.sleep(16000);
        } catch (Exception e) {
            logger.info("runTask>>>"+e.getMessage());
        }finally {
        if (scheduleJobId != null) {
            //最后关闭当前任务,使任务执行一次
            scheduleJobService.updateStatusById(scheduleJobId, "2");
        }
    }
        logger.info(Thread.currentThread().getName() + ":" + params);
    }
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: Spring Boot ScheduleSpring Boot框架中的一个定时任务调度模块,可以帮助开发者在应用程序中实现定时任务的调度和执行。它基于Spring框架的TaskExecutor和ScheduledExecutorService实现,提供了一种简单、灵活、可靠的方式来管理和执行定时任务。开发者可以使用注解或XML配置来定义定时任务,也可以自定义定时任务的执行策略和参数。Spring Boot Schedule还提供了一些有用的特性,如任务的并发控制、任务的错误处理、任务的持久化等,可以帮助开发者更好地管理和维护定时任务。 ### 回答2: Spring Boot的Schedule是一个简单而强大的定时任务调度框架,它通过@EnableScheduling注解来启用定时任务。 使用Spring Boot的Schedule非常简单。首先,在需要进行定时任务调度的方法上添加@Scheduled注解,指定调度的时间规则。例如,可以使用@Scheduled(cron="0 0 0 * * ?")来表示每天凌晨执行任务。 除了cron表达式,@Scheduled注解还支持fixedRate和fixedDelay属性。fixedRate表示任务的固定间隔时间,而fixedDelay表示任务结束后延迟多长时间再次执行。 在运行Spring Boot应用时,定时任务会自动以多线程的方式执行。可以通过配置ThreadPoolTaskScheduler来调整线程池的大小和其他属性。 Spring Boot的Schedule还提供了一些其他功能,例如支持异步任务调度,可以使用@Async和@Scheduled注解一起使用,实现异步执行定时任务。 总体来说,Spring Boot的Schedule提供了一种简单而灵活的方式来实现定时任务调度。它可以方便地与其他Spring Boot特性和框架集成,让我们能够更轻松地编写和管理定时任务。 ### 回答3: Spring Boot是一个开源的Java开发框架,简化了Java应用的开发过程。其中的Schedule功能可以使得开发者能够轻松地创建和管理定时任务Spring Boot的Schedule功能基于Spring框架中的Task Scheduler,可以通过注解和配置文件来定义定时任务。它提供了多种方式去定义定时任务执行的时间间隔,如使用固定的间隔、使用Cron表达式等。 使用Spring Boot的Schedule功能,开发者可以快速地创建定时任务,定时执行一些重复性的任务。比如,可以使用@Scheduled注解来标记一个方法为定时任务,并设置执行频率。方法体内的代码会在设定的时间间隔内自动执行。 另外,Spring Boot的Schedule功能还可以支持异步任务的执行。通过在方法上标记@Async注解,可以将定时任务的执行异步化,提高应用的性能和响应速度。 除了定义定时任务的执行时间和频率,Spring Boot的Schedule功能还提供了异常处理机制。可以在定时任务的方法体内对可能出现的异常进行捕获和处理,避免任务执行出错导致整个应用崩溃。 总之,Spring Boot的Schedule功能为开发者提供了一种简单、高效的方式去处理定时任务。它的灵活性和易用性使得开发者能够轻松地创建和管理定时任务,提高应用的效率。无论是对于一些重复性的后台任务,还是一些定时触发的业务逻辑,Spring Boot的Schedule功能都能够满足开发者的需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值