需求来源
有一套数据计算服务平台,基本架构是分布式文件系统 hdfs 存储原始数据,mysql 数据库存储计算模型路径 以及 任务记录。需要通过 hadoop 、spark等进行分布式任务计算。通过web 进行计算模型组织、任务投递以及结果查询。
解决问题
当任务投递到分布式计算系统之后,web 如何知道任务何时完成,如何获取任务计算结果(可能成功,也可能失败)
基本思想
实事上,分布式计算系统进行任务计算完成后,很难做到通知 web 已经完成任务。当然,有以下几种思路可以完成:
- 有本事的可以通过修改 hadoop 或者 spark 源码定制一套消息通知 RESTAPI ,这样看起来很合理,但是难度可想而知
- 在分布式计算系统中执行默认后台服务,定时捕捉计算任务完成,并通知webserver。这是很好的策略,但是意味着,除了部署分布式分布系统,还要根据 特定的 webserver 部署位置进行后台任务诸多配置。
- 在分布式任务提交的同时,按照特定模板组织一个回调。将回调设置为任务进程退出时执行(不论是正常退出还是异常退出均执行),可以将任务计算结果写回webserver
- 由webserver 执行定时任务,通过并发线程池投递向远程计算集群查询任务状态的任务,将查询到的结果写到数据库
web server 的定时任务
实际上,多数情况下使用后两种情况应该是比较好的。因为整个任务提交、监控都能由web server完成,计算集群只需要完成计算任务即可,而不用负责消息传递等。其中,又以 web server 的定时任务实现起来最为简单。
这里,有一个实现的例子,Spring Boot 的service 服务可以将任务投递到分布式集群、并将job 信息写入数据库,这里实现的是定时任务轮询数据库查询未完成的job ,然后向分布式计算系统发送请求判断任务是否真的完成,代码如下:
package com.IntelligenceInsight.Myth.util;
import com.IntelligenceInsight.Myth.mapper.JobMapper;
import com.IntelligenceInsight.Myth.model.Job;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Component
class TimerApplicationRunner implements ApplicationRunner {
@Autowired
private JobMapper mapper;
// TODO: Should minimize access data
// Stores a job without DONE in the cache and removes it from the cache when it detects completion
// corresponding:
// 1) When the web service starts, we need to access the database once to get the job without the done.
// 2) Each time you submit a job, in addition to writing to the database, we need to write to the cache.
@Override
public void run(ApplicationArguments args) throws Exception {
ThreadPool tp = new ThreadPool(128);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
List<Job> jobs = mapper.JobListNotDone();
for(Job job : jobs) {
Task task = new Task(job.getUuid());
tp.IssueOperation(task);
}
}
},1000,3000);
}
}
class Task implements Runnable {
private String uuid;
public Task(String uuid) {
this.uuid = uuid;
}
public void run() {
// 根据 uuid 去检查 job 的状态
// 主要是去检查任务执行结果的写入内容,并将状态值更新到数据库
// job 状态有四种 SUBMIT RUNNING FINISH FAIL
System.out.println(uuid);
}
}
public class ThreadPool {
private ExecutorService es;
public ThreadPool(int workers) {
ExecutorService es = Executors.newFixedThreadPool(workers);
this.es = es;
}
public void IssueOperation(Runnable task) {
this.es.submit(task);
}
}
原理详解
解释一下实现,以及web server 所用架构:
后台框架是 Spring Boot 2 + Mybatis 。
这里实现的是:
- 当web server 启动的时候 通过 ApplicationRunner 启动一个同步进程,
- 同步进程主要做的就是,声明一个线程池,然后通过 new Timer().schedule() 启动一个定时任务,
- 定时任务通过查询数据库找到所有没有完成的任务,
- 将任务所持有的uuid 构建一个task,并定义task 需要做的事情
- task 需要的做的事情:向计算平台发一个查询文件操作,并将查询的结果写回到数据库
- 将 task 投递到并发线程池
小结
web server 的轮询操作,虽然基本实现需求。但并不能保证线程安全。
在不修改hadoop 等源码的前提下,最好的方式应该是想办法增加回调,既能保证任务状态的即时性,又能避免不断轮询计算集群造成的资源占用。
321



被折叠的 条评论
为什么被折叠?



