java:定时任务以及线程池任务投递

需求来源

有一套数据计算服务平台,基本架构是分布式文件系统 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 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 等源码的前提下,最好的方式应该是想办法增加回调,既能保证任务状态的即时性,又能避免不断轮询计算集群造成的资源占用。

你可能感兴趣的:(Java,Web,bigdata)