1什么是分布式任务调度
1.1任务举例-定时任务
1.1.1实际案例
1.1.1.1网贷-日终任务
1.1.1.2其它业务-全日频繁
1.1.1.3 Crontab、shell
1.2产生问题 PK 解决问题
1.2.1迎面遇到第一个问题:集群重复执行
每台虚机在同一时间都会执行定时任务:
Tomcat |
Nginx |
Tomcat |
尝试解决:将任务单独拆出放入一台虚机(TASK)。
Nginx |
Tomcat |
TASK |
Tomcat |
产生新的问题:
1.即便TASK 100%可靠永远不宕。也有极大的停机风险,不确定在停机期间是否有任务被遗漏(misfire)。
2.任务更多,更复杂,占越来越多的资源,性能问题凸显,任务队列阻塞风险。
1.2.2Quartz集群
Nginx |
Tomcat |
Tomcat |
DB(quartz) |
之后看了眼quartz官方logo,感觉自己萌萌的。
1.2.3管理、调度任务产生问题
萌不过三天。发现这个定时任务策略要修改,
同样,下面这个任务不需要做,取消掉
也无法及时监控:报错了吗,运行成功了吗?
随着业务发展暴露出越来越多的问题:
例如:
----截图摘自沈建林“分布式调度系统介绍”中的五个问题
1.2.4解决问题 解决问题 解决问题
集思广益造轮子:
Quartz,Xxl-job,elastic-job,tbschedule,uncode-schedule,Opencron
在分布式环境中如何将任务集群中分散的、可靠性差的任务统一化管理和调度,实现分布式部署的管理方式,这就是分布式任务调度。
2.分布式任务调度框架
2.1初识
2.1.1Ferrari
https://www.cnblogs.com/xuxueli/p/5021979.html
XXL-JOB框架2015-11月发布1.0.RELEASE。
2016-01月,大众点评接入XXL-JOB,内部别名《Ferrari》。 自2016-01-21接入至2017-12-01期间,该系统已调度约100万次,经过数个大版本的更新,系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升,核心功能更加稳定高效。
2.1.2Elastic-Job
Elastic-Job分为Elastic-Job-Lite和Elastic-Job-Cloud
2.1.3Tbschedule
http://code.taobao.org/p/tbschedule/src/
http://code.taobao.org/p/tbschedule/wiki/tbschedule-quick-start/
2.1.4Uncode-Schedule
https://gitee.com/uncode/uncode-schedule/releases
2.2基础
2.2.1Quartz
2.2.1.1Quartz初体验
2.2.1.1.1Example1
当然也支持cron表达式如下,运行结果略。
2.2.1.1.2Example2-CronTrigger Misfire
Misfire策略
withMisfireHandlingInstructionDoNothing:不触发立即执行,等待下次调度; withMisfireHandlingInstructionIgnoreMisfires:以错过的第一个频率时间立刻开始执行; withMisfireHandlingInstructionFireAndProceed:以当前时间为触发频率立刻触发一次执行;
准备工作:
Job:
Misfires strategy1:
withMisfireHandlingInstructionIgnoreMisfires:以错过的第一个频率时间立刻开始执行;
Misfires strategy2:
withMisfireHandlingInstructionDoNothing:不触发立即执行,等待下次调度;
Misfires strategy3:
withMisfireHandlingInstructionFireAndProceed:以当前时间为触发频率立刻触发一次执行
2.2.1.2Quartz组件关系
2.2.1.2.1Quartz组件
QuartzSchedulerThread :负责执行向QuartzScheduler注册的触发Trigger的工作的线程。
ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提供运行效率。
QuartzSchedulerResources:包含创建QuartzScheduler实例所需的所有资源(JobStore,ThreadPool等)。
SchedulerFactory :提供用于获取调度程序实例的客户端可用句柄的机制。
JobStore: 通过类实现的接口,这些类要为org.quartz.core.QuartzScheduler的使用提供一个org.quartz.Job和org.quartz.Trigger存储机制。作业和触发器的存储应该以其名称和组的组合为唯一性。
QuartzScheduler :这是Quartz的核心,它是org.quartz.Scheduler接口的间接实现,包含调度org.quartz.Jobs,注册org.quartz.JobListener实例等的方法。
Scheduler :这是Quartz Scheduler的主要接口,代表一个独立运行容器。调度程序维护JobDetails和触发器的注册表。 一旦注册,调度程序负责执行作业,当他们的相关联的触发器触发(当他们的预定时间到达时)。
Trigger :具有所有触发器通用属性的基本接口,描述了job执行的时间出发规则。 - 使用TriggerBuilder实例化实际触发器。
JobDetail :传递给定作业实例的详细信息属性。 JobDetails将使用JobBuilder创建/定义。
Job:要由表示要执行的“作业”的类实现的接口。只有一个方法 void execute(jobExecutionContext context)
(jobExecutionContext 提供调度上下文各种信息,运行时数据保存在jobDataMap中)
Job有个子接口StatefulJob ,代表有状态任务。
有状态任务不可并发,前次任务没有执行完,后面任务处于阻塞等到。
2.2.1.2.2组件关系简要
2.2.1.2.3生命周期
只列重点不展开,用Quartz官网Example1
2.2.1.2.3.1Scheduler
(1)Scheduler
Instantiate()这是一个近千行的方法,
(1-1)Scheduler Members
(2)JobStore
(3)ThreadExecutor
(4)QuartzSchedulerThread
(4-1)run()
(4-2)sigLock.await()
(5)Job run
2.2.1.2.3.2Scheduler-start()-notifyAll()
2.2.2Spring-task
2.2.2.1TaskExecutor
2.2.2.2TaskScheduler
2.2.2.3Using the Quartz Scheduler
2.3方式
2.3.1抢占式
2.3.1.1 Quartz
2.3.1.1.1JobStore
2.3.1.1.2Lock & Trigger
2.3.1.1.3时序图
2.3.1.2 Uncode-Schedule
2.3.1.2.1功能概述
- 基于zookeeper+spring task/quartz的分布任务调度系统。
- 确保每个任务在集群中不同节点上不重复的执行。
- 单个任务节点故障时自动转移到其他任务节点继续执行。
- 任务节点启动时必须保证zookeeper可用,任务节点运行期zookeeper集群不可用时任务节点保持可用前状态运行,zookeeper集群恢复正常运期。
- 支持动态添加和删除任务。
- 添加ip黑名单,过滤不需要执行任务的节点。
- 简单管理后台
2.3.1.2.2架构
Nginx |
Tomcat |
Tomcat |
Zookeeper |
Spring task |
Quartz |
2.3.1.2.3ZKScheduleManager
继承ThreadPoolTaskScheduler,重写下图方法:
在子类和父类中任选一个方法对比,比如scheduleAtFixedRate()方法:
子类:
父类:
发现即是在第一个入参Runnable task包装了一个taskWrapper(task)
在容器启动以及心跳刷新服务的时候,会在zookeeper上创建节点
在节点
”/uncode_test/schedule/task/helloWorld#hi”
下面会有ip+uuid+seqno,在taskWrapper()中的isOwner即是根据此标识字符串:
“192.168.236.1$FA2165482C644A899076A9542BF68C25$0000000008”
每个机器自己均持有各自的标识字符串,任一时刻至多只有一台匹配的机器,持有匹配字符串的机器才能执行
2.3.1.2.4心跳-刷新任务节点
2.3.1.2.5Uncode schedule与Quartz集成
2.3.1.2.6监控
2.3.1.3 Elastic-job-site
2.3.1.3.1配置
2.3.1.3.2任务/分片 demo
2.3.2协同式
2.3.2.1 Xxl-job
2.3.2.1.1功能概述
https://www.cnblogs.com/xuxueli/p/5021979.html
2.3.2.1.2调度关系
Jetty |
Admin |
Xxl-job-core.jar |
Jetty |
2.3.2.1.3管理中心
路由策略
2.3.2.1.4管理端服务AdminBiz
XxlJobDynamicScheduler
服务发现+失败预警+远程触发
- 服务发现
JobRegistryMonitorHelper.getInstance().start();
执行器注册到registry,管理中心不断扫描同步注册进来的执行器至group中。
Server Registry |
Admin |
Executor |
Server Group |
- 失败预警
JobFailMonitorHelper.getInstance().start();
(3)RemotehttpJobBean
参见Uncode-Schedule与Quartz集成
(4)XxlJobTrigger策略模式按策略触发任务
(5)NetComClientProxy代理模式远程调用Executor
(6)路由策略
(6-1)第一个
(6-2)一致hash
(6-3)分片广播
对比其它策略的相同方法:
(6-4)failover
(6-5)busyover
(6-5)其它
其它策略:最后一个,轮询,随机,最不经常使用,最近最久不使用:略。
也可自己定制。
(7)Misfire
XXL-JOB默认misfire规则为:withMisfireHandlingInstructionDoNothing
2.3.2.1.5执行器端服务ExecutorBiz
(1)XxlJobExecutor
(1-1)NetComClientProxy代理模式远程调用Admin
(1-2)加载任务bean
(1-3)服务注册
(2)JettyServerHandler准备执行任务
(3)ExecutorBizImpl如何执行任务
(4)TriggerParam
2.3.3对比
抢占式:谁先获得资源谁就能执行,这种模式无法将单个任务的数据交给其它节点协同处理,一般用于处理数据量较小,任务较多的场景下。
协同式:可以将单个任务处理的数据分配到多个JVM中处理,提高数据的并行处理能力,能够充分利用计算机资源。
2.4补充
2.4.1Tbschedule & Uncode schedule
TbSchedule:
Uncode schedule:
TbSchedule:
Uncode schedule:
2.4.2作业分片
作业分片只是个逻辑概念,分片和实际数据其实框架是不做任何匹配关系的。而根据分片项和实际业务如何关联按业务自定义。
- 将一个数据表中所有数据的ID按10取模,就将数据划分成了0、1、2、3、4、5、6、7、8、9供10个任务项。
2、将一个目录下的所有文件按文件名称的首字母(不区分大小写), 就划分成了A、B、C、D、E、F、G、H、I、J、K、L、M、N、O、P、Q、R、S、T、U、V、W、X、Y、Z供26个任务项。
3、将一个数据表的数据ID哈希后按1000取模作为最后的HASHCODE,我们就可以将数据按[0,100)、[100,200) 、[200,300)、[300,400) 、[400,500)、[500,600)、[600,700)、[700,800)、[800,900)、 [900,1000)划分为十个任务项。
3.开源
来源:oschina
链接:https://my.oschina.net/u/2983845/blog/3000970