把调度和任务执行,隔离成两个部分:
-
调度中心
只需要负责任务调度属性,触发调度命令
-
执行器
执行器接收调度命令,去执行具体的业务逻辑
两者都可以进行横向扩容。
1 MQ
调度中心依赖Quartz集群模式,当任务调度时,发送消息到RabbitMQ 。业务应用收到任务消息后,消费任务信息。
充分利用MQ解耦:
- 调度中心发任务
- 应用方作为执行器,接收任务并执行
1.1 缺点
多引入中间件,强依赖MQ,可扩展性和功能,系统负载都和MQ有关。
2 XXL-JOB
分布式任务调度平台,设计目标:开发迅速、学习简单、轻量级、易扩展。
2.1 xxl-job 2.3.0架构图
2.2 网络通讯 server-worker 模型
调度中心和执行器 两个模块之间通讯是 server-worker 模式。调度中心本身就是一个SpringBoot 工程,启动会监听8080端口。
执行器启动后,会启动内置服务( EmbedServer )监听9994端口。这样双方都可以给对方发送命令。
调度中心咋知道执行器地址信息?
执行器会定时发送注册命令 ,这样调度中心就可获取在线执行器集。
通过执行器集,即可根据任务配置的路由策略选择节点执行任务。
路由策略
随机节点执行
选择集群中一个可用的执行节点执行调度任务。
适用场景:离线订单结算。
广播执行
在集群中所有的执行节点分发调度任务并执行。
适用场景:批量更新应用本地缓存。
分片执行
按用户自定义分片逻辑进行拆分,分发到集群中不同节点并行执行,提升资源利用效率
适用场景:海量日志统计。
调度器
调度器是任务调度系统里面非常核心的组件。XXL-JOB 的早期版本是依赖Quartz。
但在v2.1.0版本中完全去掉了Quartz的依赖,原来需要创建的 Quartz表也替换成了自研的表。
核心的调度类是:JobTriggerPoolHelper 。调用start方法后,会启动两个线程:scheduleThread 和 ringThread 。
首先 scheduleThread 会定时从数据库加载需要调度的任务,这里从本质上还是基于数据库行锁保证同时只有一个调度中心节点触发任务调度。
ExplainConnection conn = XxlJobAdminConfig.getAdminConfig()
.getDataSource().getConnection();
connAutoCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
preparedStatement = conn.prepareStatement(
"select * from xxl_job_lock where lock_name = 'schedule_lock' for update");
preparedStatement.execute();
# 触发任务调度 (伪代码)
for (XxlJobInfo jobInfo: scheduleList) {
// 省略代码
}
# 事务提交
conn.commit();
调度线程会根据任务的「下次触发时间」,采取不同的动作:
已过期的任务需要立刻执行的,直接放入线程池中触发执行 ,五秒内需要执行的任务放到 ringData 对象里。
ringThread 启动后,定时从 ringData 对象里获取需要执行的任务列表 ,放入到线程池中触发执行。