这个方案应该不是最优的,庆幸解决了目前的问题,果然是SQL语句大法好。
业务介绍:(请无视表的命名)
- 运行服务器表(service_register)【服务器id(id)】
- 作业表(file_manage)【作业id(id),调度状态(status)】
- 运行表(t_service_job)【服务器id(service_id),作业id(job_id),工作状态(work_status)】<每次调度作业将它们对应关系存入该表,运行结束时移除>
- 运行日志表(t_service_job_log)【服务器id(service_id),作业id(job_id),运行结果(result)】<每次运行成功或者失败需要将运行结果存入日志>
业务要求:
- 定时扫描调度状态为0的作业,然后把它们分别分配给空闲的服务器,将作业id和服务器id存入运行表,定时刷新运行表的工作状态,当工作状态为-1失败或者1成功时,将该条数据移除运行表,结果存入日志,作业状态更新为1。
- 如果A作业在服务器1上运行失败,那它将不再运行在服务器1上,而是给它分配其它服务器运行,直到运行成功
- 如果作业在所有服务器上运行失败,判定作业损坏,提示用户。
处理思路:
- 定时器处理:springboot自带了定时器设置,将方法进行封装即可。(非重点)
- 业务1扫描空闲的服务器:已知上线的服务器需要绑定作业在运行表中,所以只需要找到没有在运行表中的服务器即可
- 业务1其它业务为简单的增删改查无视。
- 业务2因为无论成功还是失败,运行记录会存在日志中,所以只有已经运行过但失败了的作业才需要查询日志表中该作业与服务器的运行情况。然后只需要在日志表中查询对应作业id下的服务器id即可知道哪些服务器运行过该作业,将它们排除即可得出结果。
- 业务3当业务2中查询结果与服务器列表重合,即可判定作业损坏。
处理代码-SQL:
定时器代码(别的博客应该有了我就再贴一下)
<!-- spring quartz 定时器-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>1.8.6</version>
</dependency>
@Component
@Configuration
@EnableScheduling
@Controller
public class AutoScheduler {
//1000是1000毫秒
@Scheduled(fixedRate = 1000*60)
public void execute(){
}
}
如果要配置多任务,可以参考api文档,需要写自定义配置类
业务1扫描空闲的服务器列表
SELECT
service_register.id,
service_register.IP,
service_register.NAME,
service_register.details
FROM
service_register LEFT JOIN t_service_job
ON
service_register.id = t_service_job.service_id
WHERE t_service_job.service_id IS NULL
我在其它博客上发现一张妙图,讲的是join的关系(不知道侵权不,不放了)
业务2查找没有运行过该作业的服务器列表(重点!Sql很长)
SELECT
s1.service_id
FROM
(
SELECT
sr.id AS service_id
FROM
service_register AS sr
LEFT JOIN t_service_job AS sj ON sr.id = sj.service_id
WHERE
sj.service_id IS NULL
) AS s1
LEFT JOIN (
SELECT
jl.service_id AS service_id
FROM
t_service_job_log AS jl
WHERE
jl.job_id = #{jobId}
) AS s2 ON s1.service_id = s2.service_id
WHERE
s2.service_id IS NULL
前半部分还是查询空闲服务器,左连接了一个子查询(查找特定作业id的工作记录)
切记不能将·作业id·作为条件写在外围查询的where里面,这样什么都查不到的。
如果不想重复写sql,mybatis有循环的标签可以用。
业务3判断该作业是否损坏(写博客的好处来了,写到一半我才发现这个逻辑有点问题,立刻回去补了这条sql进去)
SELECT
service_register.id
FROM
service_register
LEFT JOIN (
SELECT
t_service_job_log.service_id
FROM
t_service_job_log
WHERE
t_service_job_log.job_id = #{jobId}
) AS jl ON service_register.id = jl.service_id
WHERE
jl.service_id IS NULL
查询当前作业是否损坏
逻辑问题出现在,我之前直接判断如果没有可以用的服务器则为作业损坏,没有考虑到那台可以用的服务器正在被使用的情况。所以当没有服务器可以用时,需要判断作业是否损坏,没有损坏再判断分配任务。
处理代码-java
框架跑的是SpringBoot,就不多解释,都懂。
调度作业核心代码:
private void scanServiceJob(){
System.out.println("执行第"+times+"次调度作业中...");
//查找当前是否有未上线作业
List<FileManage> fms = fManageService.findByStatus(0);
if(fms != null && fms.size() > 0){
//查找当前是否有空闲的服务器
List<ServiceRegister> srs = sRegisterService.findLeisureServices();
if(srs != null && srs.size() > 0){
for(FileManage fm : fms){
//查找该作业对应的空闲机器id(下面代码)
int id = sJobLogService.findCanRunService(fm.getId());
if(id == 0){
System.out.println("没有空闲的服务器,等待调度中...");
}else if(id == -1){
System.out.println("文件可能受到损坏,请检查!");
fManageService.updateStatusByid(-1,fm.getId());
}else {
ServiceJob serviceJob = new ServiceJob();
serviceJob.setServiceId(id);
serviceJob.setJobId(fm.getId());
//添加到服务器与作业的对应关系表中
sJobService.add(serviceJob);
//修改该作业上线状态
fManageService.updateStatusByid(1,fm.getId());
}
}
}else {
System.out.println("没有空闲的服务器,等待调度中...");
}
}
}
查看作业的服务器信息:
public int findCanRunService(int jobId) {
//判断作业是否损坏
List<Integer> sr = sJobLogMapper.findNoRunService(jobId);
if(sr != null && sr.size() >0){
//判断是否有服务器可以用
sr = sJobLogMapper.findNotRunedService(jobId);
if(sr != null && sr.size() > 0) {
return sr.get(0);
}
return 0;
}
return -1;
}
这个命名有点蠢,请见谅。
其中第一个列表对应的是业务3,第二个列表对应的是业务2
检查运行结果录入日志:
private void scanServiceJobStatus(){
System.out.println("执行第"+times+"次检查运行情况中...");
//查找所有非运行的工作数据
List<ServiceJob> sjs = sJobService.findNotWorkStatus(0);
if (sjs != null && sjs.size() > 0){
for(int i = 0;i < sjs.size();i ++){
//将结果移到日志表中
ServiceJobLog sjl = new ServiceJobLog();
sjl.setJobId(sjs.get(i).getJobId());
sjl.setServiceId(sjs.get(i).getServiceId());
//运行成功
if(sjs.get(i).getWorkStatus() == 1){
sjl.setResult(1);
} else{
sjl.setResult(0);
//更新作业状态,重新丢回去
fManageService.updateStatusByid(0,sjs.get(i).getJobId());
}
sJobLogService.add(sjl);
//将该记录从运行表中移除
sJobService.removeJobById(sjs.get(i).getId());
}
}
}
总结
- 这个业务算是我目前来说遇到的最麻烦的业务了,分批调度的问题,也没有什么例子可以给我参考参考,以前也是没有写过的,算是自己研究出来的吧。
- 从最开始用的一些比较暴力的写法:
private void scanServiceJob(){
System.out.println("执行第"+times+"次调度作业中...");
//查找当前是否有未上线作业
List<FileManage> fms = fManageService.findByStatus(0);
if(fms != null && fms.size() > 0){
//查找当前是否有空闲的服务器
List<ServiceRegister> srs = sRegisterService.findLeisureServices();
if(srs != null && srs.size() > 0){
//将作业分配到空闲服务器上
for(int i = 0;i < srs.size();i ++){
if(fms.size() == 0){
break;
}
ServiceJob serviceJob = new ServiceJob();
serviceJob.setServiceId(srs.get(i).getId());
serviceJob.setJobId(fms.get(0).getId());
//修改该作业上线状态
fManageService.updateStatusByid(1,fms.get(0).getId());
fms.remove(0);
//添加到服务器与作业的对应关系表中
sJobService.add(serviceJob);
}
}
}
}
- 直接用List做调度,虽然完成了调度作业的要求,但是无法对作业运行的细节做处理,所以之后改成了上面的那种写法。
- 逻辑编程真是令人着迷啊。希望有大佬指点下正确的写法,据说有插件?我好像找不到。