1 架构概览
在我的职业生涯里,接触过如下 TimerTask 、Quartz 、SpringTask、 时间轮 HashWheelTimer 、Elastic-Job 、XXL-JOB、PowerJob、AirFlow 等任务调度系统,也曾在一家汽车租赁公司自研过基于 XXL-JOB 改造的任务调度系统。
不少同学对我原来的自研经历很感兴趣,于是我编写了一个教学型的任务调度系统(支持 10万 + 调度任务),希望能帮助中高级工程师快速提升架构思维。
思考了很长时间,好几次都推翻了设计思路,经过多轮思考,架构图如下 :
任务调度系统分为三个核心组件:
1、网关层负责应用的接入,任务的推送。
2、Admin 层负责任务的管理、任务的分片、UI 界面等。
3、Worker 层负责任务的调度,并将任务触发到网关。
之所以这么设计,必须保证所有的组件是可水平扩展的 。
技术栈如下表:
技术名称 | 简介 |
---|---|
Spring Boot | 基于 Spring 的快速开发框架,简化配置,提供内嵌服务器和自动配置功能。 |
MySQL | 开源关系型数据库,支持 SQL 和事务,适用于结构化数据存储与管理。 |
MyBatis | 半自动化 ORM 框架,通过 XML/注解灵活映射 SQL 与 Java 对象,支持动态 SQL。 |
RocksDB | 高性能嵌入式 Key-Value 存储引擎,适用于高吞吐、低延迟的本地数据持久化场景。 |
Netty | 异步事件驱动网络框架,支持高并发通信,常用于构建 TCP/UDP/HTTP 协议服务端或客户端。 |
Guava | Google 开发的 Java 核心工具库,提供集合、缓存、字符串处理等高效工具类,简化开发。 |
Quartz | 开源作业调度框架,支持复杂定时任务(如按计划执行、周期性任务)。 |
Zookeeper | 开源分布式协调框架,提供分布式同步、配置管理和服务发现,常用于分布式系统中。 |
项目仓库截图:
2 部署流程
勇哥将服务部署到 1 台阿里云服务器上,数据库部署在腾讯云。
1 数据库 MySQL 和 Zookeeper
- 数据库
创建数据库 platform_schedule ,执行 doc 目录下的 SQL 脚本:
执行完成后,如下图:
- Zookeeper
下载 zookeeper-3.6.0 版本,解压后复制一份 zoo_sample.cfg ,重命名为 zoo.cfg ,保持默认配置即可。
2 部署 Admin、GateWay、Worker
- 打包
在 Admin 模块、GateWay 模块、Worker 模块执行三个步骤:修改数据库配置、打包、拷贝部署包到服务器。
- 启动 Admin 服务
bash
nohup java -Xms300m -Xmx350m -jar schedule-admin.jar &
- 启动 GateWay 服务
bash
nohup java -Xms200m -Xmx400m -jar schedule-gateway.jar &
- 启动 Worker 服务
bash
nohup java -Xms200m -Xmx300m -jar schedule-worker.jar &
3 线上体验
01 登录
02 集群管理
因为 GateWay 、Woker 都需要向 Admin 注册实例信息,我们可以将 Admin 当做简单的注册中心,所以需要配置如下信息:
- Admin 注册服务地址 ,格式是:ip1:10001;ip2:10001。
- zk 集群地址 ,用于 Admin 集群,实现主从模式 。
03 添加应用
我们生产环境创建了1个应用,应用名是: mytest , appKey 为 1400001,客户端访问任务调度系统需要配置 appKey 和 appSecret 。
04 添加任务
我们定义了一个任务 ,名称是:测试任务,每 15 秒执行一次,jobHandler 定义为 myJobHandler 。
05 测试任务
从 gitcode 下载源码后 ,查看 Demo 模块 ,模块需要添加客户端依赖 和 编写任务实现。
1、客户端依赖
xml
<!-- 依赖 客户端 start -->
<dependency>
<groupId>cn.javayong</groupId>
<artifactId>schedule-client</artifactId>
<version>${parent.version}</version>
</dependency>
<!-- 依赖 客户端 end -->
2、配置网关地址以及秘钥
3、编写任务实现
java
@Service
public class DemoJob {
private final static Logger logger = LoggerFactory.getLogger(DemoJob.class);
// 1、添加任务调度的自定义注解 RSAnnotation , 值是 job 的 bean值
// 2、ScheduleParam 调度参数
// 3、ScheduleResult 调度结果
@RSAnnotation(value = "myJobHandler")
public ScheduleResult doTestJob(ScheduleParam scheduleParam) {
logger.info("myJobHandler:" + JSON.toJSONString(scheduleParam));
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
return new ScheduleResult(ScheduleResult.SUCCESS_CODE, "正常响应");
}
}
4、启动任务演示项目
展示了一个简单的任务调度类 DemoJob
,它包含一个可以被调度执行的方法 doTestJob
,该方法的注解是: myJobHandler , 与我们在 Admin 控制台创建的测试任务保持一致。
启动 DemoApplication ,本地执行结果:
如图,每隔 15 秒,我们的控制台会打印执行日志。
在 Admin 调度日志页面,查看日志:
我们也发现启动应用后,可以查看在线应用:
对于客户端来讲,任务执行是网关推送过来的,所以我们在家运行代码也可以执行。网关会保存客户端的出网 IP ,在线应用列表可以展示所有在线客户端。
4 你可以学到什么
1、任务调度专栏
第一期已经开发完成,包含 JDK 常用调度类、Quartz 、Crontab 、ElasticJob、自研 platform-schedule 。
第二期会重点突出 XXL-JOB 的基本原理以及性能瓶颈,以及新一代任务调度系统 PowerJob 的原理(重点 讲解 DAG )。
2、网络编程
我基于 RocketMQ remoting 模块做了些许微调,Admin 服务、Gateway 服务、Worker 服务都是通过 TCP remoting 协议交互。
相比 RocketMQ 复杂的代码,platform-schedule 会简单很多,中高级工程师可以轻松掌握 Netty 编程知识,开阔眼界。
3、组件封装
platform-schedule 每个模块都有不同的组件,比如 worker 模块非常核心的调度组件是依赖 Quartz ,那么如何封装 Quartz 实现灵活的调度呢 ?
同时,Admin 为什么可以知道 Worker 、Gateway 的路由地址,因为它本身是一个简易的注册中心。
如何封装任务调度的 SDK ,并平滑的接入 Spring 生态。