当前位置: 首页 > Linux

那些年,我们用的是“定时调度”

时间:2023-04-06 20:44:55 Linux

定时调度作为后端开发,我们总会遇到这样的业务场景:每周同步一批数据;每半小时检查一次服务器运行状态;早上8点给用户发一封邮件,里面有今天的待办事项等等,这些场景都离不开“定时器”,就像一个设定了时间规则的闹钟,它会在指定的时间触发,执行我们调度的任务想要定义。那么今天就来数一数,那些年我们用的“定时调度”。1、Job(oracle)从工作开始就一直在使用oracle数据库,我最早接触的定时任务就是oracle数据库的job。作业具有定时执行的功能,任务可以在指定的时间执行,也可以在每天的某个时间执行。而且oracle重启后,job会继续运行,不需要重启。而且作业机制非常完善,可以查询相关的表或视图,查询作业的定时规则和执行状态。缺点是作为oracle数据库级别的工具,自定义的功能扩展使得二次开发难度加大。1.1创建工作DECLAREjobNUMBER;BEGINsys.dbms_job.submit(job=>job,what=>'prc_name;',--要执行的存储过程的名称next_date=>to_date('22-11-201309:09:41','dd-mm-yyyyhh24:mi:ss'),--下次执行时间间隔=>'sysdate+1/24');--一天24小时,即每小时运行一次prc_name进程END;--job参数是一个输出参数,submit()进程返回的binary_ineger,这个值用来唯一标识一个job。一般定义一个变量接收,可以去user_jobs视图查询job值。--什么参数是要执行的PL/SQL代码块,存储过程名称等--next_date参数表示作业将在什么时候运行。--重新执行此作业时的间隔参数1.2deletejobDECLAREBEGINdbms_job.remove(1093);--1093为当前job值COMMIT;END;1.3queryjob--查询当前用户的jobselect*fromuser_jobs;--查询所有jobselect*fromdba_jobs;--查询所有正在运行的jobselect*fromdba_jobs_running;2、crontab(linux)crond是linux下用来周期性执行某些任务或等待某些事件处理的守护进程,类似于windows下的定时任务,安装操作系统时,会安装这个服务工具default,并且会自动启动crond进程,crond进程每分钟会定时检查是否有任务要执行,如果有任务就自动执行任务。cron是服务名,crond是后台进程,crontab是自定义的定时任务表。大多数linux系统默认安装了cron,你可以查看一下。--查看crontab工具是否安装crontab-l--查看crond服务是否启动servicecrondstatus--centos安装yuminstallvixie-cronyuminstallcrontab基本操作命令--列出用户cron服务crontab的详细信息--l--编辑用户的cron服务crontab-ecrontab表达式格式{minute}{hour}{day-of-month}{month}{day-of-week}{full-path-to-shell-script}minute:间隔为0–59小时:间隔为0–23day-of-month:间隔为0–31month:间隔为1–12。1是一月。12isDecemberDay-of-week:区间为0-7。Sunday可以是0或7。在以上各字段中,还可以使用以下特殊字符:星号(*):代表所有可能的值,对于例如,如果月份字段是星号,则表示其他字段满足约束条件后的每个月执行此命令。逗号(,):可以用逗号分隔的值指定列表范围,例如“1,2,5,7,8,9”整数,例如“2-6”表示“2,3,4,5,6”正斜杠(/):可以使用正斜杠指定时间间隔频率,例如“0-23/2”表示每两个小时一次。同时,正斜杠可以和星号一起使用,比如*/10,如果用在分钟字段,表示每十分钟执行一次。推荐一个crontab表达式验证网站(https://tool.lu/crontab/)3.Timer和ScheduledExecutorService(java)Timer是jdk中提供的一个定时器工具,使用时会在主线程外启动一个单独的线程执行指定的定时任务,可指定执行一次或重复多次。//只执行一次publicvoidschedule(TimerTasktask,longdelay);publicvoidschedule(TimerTasktask,Datetime);//循环执行//在循环执行范畴中,按照循环时间间隔分为两种publicvoidschedule(TimerTasktask,longdelay,longperiod);publicvoidschedule(TimerTasktask,DatefirstTime,longperiod);publicvoidscheduleAtFixedRate(TimerTasktask,longdelay,longperiod)publicvoidscheduleAtFixedRate(TimerTaskirstTimetask,longperiod)period)TimerTask是一个实现了Runnable接口的抽象类,代表一个可以被Timer执行的任务。TimerTask类是一个抽象类,由Timer作为一次性或重复性任务进行调度。它有一个抽象方法run()方法,用于执行相应定时器任务要执行的操作。因此,每个具体的任务类都必须继承TimerTask,然后重写run()方法。另外,它还有两个非抽象的方法--取消这个定时器任务booleancancel()--返回这个任务实际执行的最后一次预定执行时间longscheduledExecutionTime()当然,一般很少使用Timer,因为缺点很明显:单线程,当多个定时器同时运行时,会等待上一个执行完成,再执行下一个。Timer线程不会捕获异常。如果TimerTask抛出未经检查的异常,将导致Timer线程终止。所以一般使用ScheduledExecutorService代替Timer。ScheduledExecutorService:也是jdk自带的一个基于线程池设计的定时任务类。它的每一个调度任务都会分配给线程池中的一个线程执行,所以它的任务是并发执行的,互不影响。4、SpringTask(spring)Timer和ScheduledExecutorService都是jdk层面实现定时调度的类,它们的功能并不足以让我们满意。下面介绍一个比较完善的定时调度工具——SpringTask,它是Spring提供的,支持注解和配置文件形式,支持crontab表达式,简单易用但功能强大。我个人非常喜欢SpringTask,仅仅是因为支持crontab表达式。在springboot中的使用方式很简单:在启动类中添加注解@EnableScheduling开启定时调度。在需要定时执行的方法上加上注解@Scheduled(cron="crontabexpression")。默认的简单步骤只有上面两步。不过SpringTask的默认用法也有一些缺点:默认线程池的poolsize为1,可以理解为类似于Timer的单线程模式。crontab表达式不能动态修改,修改只能在重新部署后生效。问题1的解决方法是通过自定义TaskExecutor修改当前线程池。问题2,可以直接使用threadPoolTask??Scheduler类实现自定义定时调度规则。附上解决这两个问题的源码TaskTimer.class@ComponentpublicclassTaskTimer@Autowired私有TaskRepotaskRepo;/******定时引擎*****实例化一个线程池任务调度类*ThreadPoolTask??Scheduler默认poolSize为1,类似newSingleThreadExecutor单线程模式,只能执行一个调度,然后执行其他调度可以执行*需要自定义扩展poolSize,允许一定程度的多线程并行场景*@return*/@BeanpublicThreadPoolTask??SchedulerthreadPoolTask??Scheduler(){ThreadPoolTask??SchedulerthreadPoolTask??Scheduler=newThreadPoolTask??Scheduler();threadPoolTask??Scheduler.setPoolSize(100);threadPoolTask??Scheduler.setThreadNamePrefix("线程-");threadPoolTask??Scheduler.setWaitForTasksToCompleteOnShutdown(true);threadPoolTask??Scheduler.setAwaitTerminationSeconds(60);返回threadPoolTask??Scheduler;}/***开始定时调度*@paramtaskCode*@paramcron*@paramrunnable*@return*/publicbooleanstart(StringtaskCode,Stringcron,Runnablerunnable){ScheduledFuturecurrentFuture=taskRepo.findTask(taskCode);//现有调度无法创建if(currentFuture!=null){thrownewRuntimeException("调度\""+taskCode+"\"已经存在,无法创建");}//创建一个新的schedule并添加到taskMapcurrentFuture=threadPoolTask??Scheduler.schedule(runnable,newCronTrigger(cron));如果(currentFuture!=null){this.taskRepo.addTask(taskCode,currentFuture);返回真;}thrownewRuntimeException("任务启动失败!!!");}/***暂停计划调度*@paramtaskCode*@return*/publicbooleanstop(StringtaskCode){//taskId不存在,无法停止,只有ScheduledFuturecurrentFuture=this.taskRepo.findTask(任务代码);如果(currentFuture!=null){返回currentFuture.cancel(真);}返回真;}/***删除定时调度*@paramtaskCode*@return*/publicbooleanremove(StringtaskCode){ScheduledFuturecurrentFuture=this.taskRepo.findTask(taskCode);如果(currentFuture!=null){currentFuture.cancel(真);taskRepo.removeTask(taskCode);返回真;}返回假;}/***修改定时调度*@paramtaskCode*@paramcron*@paramrunnable*@return*/publicbooleanupdate(StringtaskCode,Stringcron,Runnablerunnable){ScheduledFuturecurrentFuture=this.taskRepo.findTask(任务代码);//已有的scheduledschedule,先停止,再添加,更新新的ScheduledFutureif(currentFuture!=null){currentFuture.cancel(true);}currentFuture=threadPoolTask??Scheduler.scheduler(runnable,newCronTrigger(cron));如果(currentFuture!=null){this.taskRepo.addTask(taskCode,currentFuture);返回真;}返回假;开源作业调度该框架为Java应用程序中的作业调度提供了一种简单但功能强大的机制。是一款功能强大成熟的重量级产品,同时支持负载均衡和分布式调度。然而,你必须在Quartz的安装上多花一点功夫,从在数据库中构建哪些表到如何部署应用程序。对于如此庞大的产品,本文不包括其使用说明书。参考文档SpringBoot并发定时任务的实现及动态定时任务的实现