什么是任务调度
咱们能够先思考一下下面业务场景的解决方案:服务器
- 某电商系统须要在天天上午10点,下午3点,晚上8点发放一批优惠券。
- 某银行系统须要在信用卡到期还款日的前三天进行短信提醒。
- 某财务系统须要在天天凌晨0:10结算前一天的财务数据,统计汇总。
- 12306会根据车次的不一样,而设置某几个时间点进行分批放票。
- 某网站为了实现天气实时展现,每隔5分钟就去天气服务器获取最新的实时天气信息。
以上场景就是任务调度所须要解决的问题。网络
任务调度是指系统为了自动完成特定任务,在约定的特定时刻去执行任务的过程。有了任务调度便可解放更多的人力由系统自动去执行任务。多线程
任务调度如何实现?
多线程方式实现:架构
学过多线程的同窗,可能会想到,咱们能够开启一个线程,每sleep一段时间,就去检查是否已到预期执行时间。并发
如下代码简单实现了任务调度的功能:框架
public static void main(String[] args) { //任务执行间隔时间 final long timeInterval = 1000; Runnable runnable = new Runnable() { public void run() { while (true) { //TODO:something try { Thread.sleep(timeInterval); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread thread = new Thread(runnable); thread.start(); }
上面的代码实现了按必定的间隔时间执行任务调度的功能。运维
Jdk也为咱们提供了相关支持,如:Timer、ScheduledExecutor,下边咱们了解下。分布式
Timer方式实现:ide
public static void main(String[] args){ Timer timer = new Timer(); timer.schedule(new TimerTask(){ @Override public void run() { //TODO:something } }, 1000, 2000); //1秒后开始调度,每2秒执行一次 }
Timer 的优势在于简单易用,每一个Timer对应一个线程,所以能够同时启动多个Timer并行执行多个任务,同一个Timer中的任务是串行执行。学习
ScheduledExecutor方式实现:
public static void main(String [] agrs){ ScheduledExecutorService service = Executors.newScheduledThreadPool(10); service.scheduleAtFixedRate( new Runnable() { @Override public void run() { //TODO:something System.out.println("todo something"); } }, 1, 2, TimeUnit.SECONDS); }
Java 5 推出了基于线程池设计的 ScheduledExecutor,其设计思想是,每个被调度的任务都会由线程池中一个线程去执行,所以任务是并发执行的,相互之间不会受到干扰。
Timer 和 ScheduledExecutor 都仅能提供基于开始时间与重复间隔的任务调度,不能胜任更加复杂的调度需求。好比:设置每个月第一天凌晨1点执行任务、复杂调度任务的管理、任务间传递数据等等。
Quartz 是一个功能强大的任务调度框架,它能够知足更多更复杂的调度需求,Quartz 设计的核心类包括 Scheduler, Job 以及 Trigger。其中:Job 负责定义须要执行的任务,Trigger 负责设置调度策略,Scheduler 将两者组装在一块儿,并触发任务开始执行。Quartz支持简单的按时间间隔调度、还支持按日历调度方式,经过设置CronTrigger表达式(包括:秒、分、时、日、月、周、年)进行任务调度。
第三方Quartz方式实现:
public static void main(String [] agrs) throws SchedulerException { //建立一个Scheduler SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); //建立JobDetail JobBuilder jobDetailBuilder = JobBuilder.newJob(MyJob.class); jobDetailBuilder.withIdentity("jobName","jobGroupName"); JobDetail jobDetail = jobDetailBuilder.build(); //建立触发的CronTrigger 支持按日历调度 CronTrigger trigger = TriggerBuilder.newTrigger() .withIdentity("triggerName", "triggerGroupName") .startNow() .withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")) .build(); //建立触发的SimpleTrigger 简单的间隔调度 /*SimpleTrigger trigger = TriggerBuilder.newTrigger() .withIdentity("triggerName","triggerGroupName") .startNow() .withSchedule(SimpleScheduleBuilder .simpleSchedule() .withIntervalInSeconds(2) .repeatForever()) .build();*/ scheduler.scheduleJob(jobDetail,trigger); scheduler.start(); } public class MyJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext){ System.out.println("todo something"); } }
经过以上内容咱们学习了什么是任务调度,任务调度所解决的问题,以及任务调度的多种实现方式。
2.什么是分布式任务调度
什么是分布式?
当前软件的架构正在逐步转变为分布式架构,将单体结构分为若干服务,服务之间经过网络交互来完成用户的业务处理,以下图:电商系统为分布式架构,由订单服务、商品服务、用户服务等组成:

分布式系统具体以下基本特色:
- 分布性:每一个部分均可以独立部署,服务之间交互经过网络进行通讯,好比:订单服务、商品服务。
- 伸缩性:每一个部分均可以集群方式部署,并可针对部分结点进行硬件及软件扩容,具备必定的伸缩能力。
- 高可用:每一个部分均可以集群部分,保证高可用。
什么是分布式调度?
一般任务调度的程序是集成在应用中的,好比:优惠卷服务中包括了定时发放优惠卷的的调度程序,结算服务中包括了按期生成报表的任务调度程序,因为采用分布式架构,一个服务每每会部署多个冗余实例来运行咱们的业务,在这种分布式系统环境下运行任务调度,咱们称之为分布式任务调度,以下图:

分布式调度要实现的目标:
不论是任务调度程序集成在应用程序中,仍是单独构建的任务调度系统,若是采用分布式调度任务的方式就至关于将任务调度程序分布式构建,这样就能够具备分布式系统的特色,而且提升任务的调度处理能力:
1.并行任务调度
并行任务调度实现靠多线程,若是有大量任务须要调度,此时光靠多线程就会有瓶颈了,由于一台计算机CPU的处理能力是有限的。
若是将任务调度程序分布式部署,每一个结点还能够部署为集群,这样就可让多台计算机共同去完成任务调度,咱们能够将任务分割为若干个分片,由不一样的实例并行执行,来提升任务调度的处理效率。
2.高可用
若某一个实例宕机,不影响其余实例来执行任务。
3.弹性扩容
当集群中增长实例就能够提升并执行任务的处理效率。
4.任务管理与监测
对系统中存在的全部定时任务进行统一的管理及监测。让开发人员及运维人员可以时刻了解任务执行状况,从而作出快速的应急处理响应。
5.避免任务重复执行
当任务调度以集群方式部署,同一个任务调度可能会执行屡次,好比在上面提到的电商系统中到点发优惠券的例子,就会发放屡次优惠券,对公司形成不少损失,因此咱们须要控制相同的任务在多个运行实例上只执行一次,考虑采用下边的方法:
- 分布式锁,多个实例在任务执行前首先须要获取锁,若是获取失败那么久证实有其余服务已经再运行,若是获取成功那么证实没有服务在运行定时任务,那么就能够执行。

- ZooKeeper选举,利用ZooKeeper对Leader实例执行定时任务,有其余业务已经使用了ZK,那么执行定时任务的时候判断本身是不是Leader,若是不是则不执行,若是是则执行业务逻辑,这样也能达到咱们的目的。
