之前只在单点服务器上部署过任务调度平台,最近因为项目结题时计算速率的指标要求过高,计划采用多台服务器并发调度+各服务器上多线程并发相结合的形式提升效率,于是进行了任务调度平台的集群部署,最后计算效率提升了100倍。这里把搭建过程和相关的源码学习过程记录下来,便于其他同类需求项目的复现。
服务器集群上的分布式任务调度平台搭建
我这里的服务器集群为10台机架式服务器,有1个主控节点,9个计算节点,10台服务器的ip配置分别为
192.168.1.101
-192.168.1.110
,任务调度平台基础框架沿袭之前项目使用的XXL-JOB
,相关学习笔记可以参考作者之前的博文:
分布式任务调度平台XXL-JOB的Oracle版本搭建与学习笔记:
https://blog.csdn.net/nannan7777/article/details/107337464?spm=1001.2014.3001.5501
分布式任务调度平台XXL-JOB源码解析笔记
https://blog.csdn.net/nannan7777/article/details/117706213?spm=1001.2014.3001.5501
1 任务调度平台服务器集群搭建过程
首先XXL-JOB官网上给出的集群部署策略有两种,调度中心集群部署
,以及执行器集群部署
,具体要求如下:
(1) 调度中心集群部署
调度中心支持集群部署,提升调度系统容灾和可用性。
调度中心集群部署时,几点要求和建议:
1)DB
配置保持一致;
2)集群机器时钟保持一致(单机集群忽视);
3)推荐通过Nginx
反向代理为调度中心集群做负载均衡,分配域名。调度中心访问、执行器回调配置、调用API服务等操作均通过该域名进行。
(2) 执行器集群部署
执行器支持集群部署,提升调度系统可用性,同时提升任务处理能力。
执行器集群部署时,几点要求和建议:
1)执行器回调地址(xxl.job.admin.addresses
)需要保持一致:执行器根据该配置进行执行器自动注册等操作;
2)同一个执行器集群内AppName
(xxl.job.executor.appname
)需要保持一致:调度中心根据该配置动态发现不同集群的在线执行器列表。
结合我自己工程项目的需求,这里采用执行器集群部署策略,将任务调度中心(xxl-job-admin
)部署在主控节点的服务器上,并在全部服务器上部署执行器(xxl-job-executor
),分别配置执行器地址为10台服务器的ip,同时这10个执行器的AppName
保持一致,过程如下所示:
1)创建一个执行器集群:
(如果有多种类型的计算需求,可以创建多个执行器组,将计算任务按照类别分开,我这里仅有一类计算任务,因此只创建了一个执行器集群)
执行器集群的属性说明如下:
AppName:
是每个执行器集群的唯一标示,执行器会周期性以AppName为对象进行自动注册。可通过该配置自动发现注册成功的执行器,供任务调度时使用。
名称:
执行器的名称,因为AppName限制字母数字等组成,可读性不强,名称为了提高执行器的可读性。
排序:
执行器的排序,系统中需要执行器的地方,如任务新增,将会按照该排序读取可用的执行器列表。
注册方式:
调度中心获取执行器地址的方式:
自动注册:
执行器自动进行执行器注册,调度中心通过底层注册表可以动态发现执行器机器地址。
手动录入:
人工手动录入执行器的地址信息,多地址逗号分隔,供调度中心使用。
机器地址:
"注册方式"为"手动录入"时有效,支持人工维护执行器的地址信息。
2)为各个执行器创建对应的服务器计算节点,并在各台服务器上部署执行器jar包,将全部执行器运行起来后计算节点状态会变为在线:
3)之后我修改了执行器集群的路由策略,代码里默认选择的是 RANDOM
随机策略,这里为均衡利用各个服务器的资源,我选择了ROUND
轮询策略:
- 路由策略:
FIRST
(第一个):固定选择第一个机器;
LAST
(最后一个):固定选择最后一个机器;
ROUND
(轮询):顺序选择每一个机器;
RANDOM
(随机):随机选择在线的机器;
CONSISTENT_HASH
(一致性HASH):每个任务按照Hash算法固定选择某一台机器,且所有任务均匀散列在不同机器上。
LEAST_FREQUENTLY_USED
(最不经常使用):使用频率最低的机器优先被选举;
LEAST_RECENTLY_USED
(最近最久未使用):最久未使用的机器优先被选举;
FAILOVER
(故障转移):按照顺序依次进行心跳检测,第一个心跳检测成功的机器选定为目标执行器并发起调度;
BUSYOVER
(忙碌转移):按照顺序依次进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度;
SHARDING_BROADCAST
(分片广播):广播触发对应集群中所有机器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务;- 子任务:每个任务都拥有一个唯一的任务ID(任务ID可以从任务列表获取),当本任务执行结束并且执行成功时,将会触发子任务ID所对应的任务的一次主动调度。
- 调度过期策略:
- 忽略:调度过期后,忽略过期的任务,从当前时间开始重新计算下次触发时间;
- 立即执行一次:调度过期后,立即执行一次,并从当前时间开始重新计算下次触发时间;
- 阻塞处理策略:调度过于密集执行器来不及处理时的处理策略;
单机串行(默认):调度请求进入单机执行器后,调度请求进入FIFO队列并以串行方式运行;
丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败;
覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务; - 任务超时时间:支持自定义任务超时时间,任务运行超时将会主动中断任务;
- 失败重试次数;支持自定义任务失败重试次数,当任务失败时将会按照预设的失败重试次数主动进行重试。
代码修改的部分如下所示:
private boolean saveJob(DagNode node, TaskDefinition definition) {
Job jobInfo = new Job();
jobInfo.setId(node.getId());
jobInfo.setJobCron("* * * * * ?");
jobInfo.setExecutorId(definition.getExecutor());
jobInfo.setExecutorHandler(definition.getJobHandler());
jobInfo.setGlueType(GlueTypeEnum.BEAN.name());
jobInfo.setGlueRemark("NONE");
jobInfo.setGlueUpdatetime(new Date());
jobInfo.setExecutorFailRetryCount(ConstantUtils.EXECUTOR_FAIL_RETRY_COUNT);
jobInfo.setExecutorTimeout(ConstantUtils.EXECUTOR_TIMEOUT);
jobInfo.setAuthor("System Scheduler");
jobInfo.setExecutorParam(node.getCommand());
jobInfo.setJobDesc(node.getId());
jobInfo.setAddTime(new Date());
jobInfo.setUpdateTime(new Date());
jobInfo.setExecutorRouteStrategy(ExecutorRouteStrategyEnum.ROUND.name());
jobInfo.setExecutorBlockStrategy(ExecutorBlockStrategyEnum.SERIAL_EXECUTION.name());
int res = ServerConfig.getInstance().getJobDao().save(jobInfo);
if