需求的产生和分析
假如有两个项目组,考试组有批量的离线文档要生成,题库组则经常有批量的题目进行排重和根据条件批量修改题目的内容。
架构组通过对实际的上线产品进行用户调查,发现这些功能在实际使用时, 用户都反应速度很慢,而且提交任务后,不知道任务的进行情况,做没做?做到 哪一步了?有哪些成功?哪些失败了?都一概不知道。
架构组和实际的开发人员沟通,都说因为前端提交任务到 Web 后台 以后,是一次要处理多个文档和题目,所以速度快不起来。提示用多线程进行改 进,实际的开发人员表示多线程没有用过,不知道如何使用,也担心用不好。综合以上情况,架构组决定在公司的基础构件库中提供一个并发任务执行框架,以解决上述用户和业务开发人员的痛点:
- 对批量型任务提供统一的开发接口
- 在使用上尽可能的对业务开发人员友好
- 要求可以查询批量任务的执行进度
需要做什么
要实现这么一个批量任务并发执行的框架,我们来分析一下我们要做些什么?
- 批量任务,为提高性能
必然的我们要使用 java 里的多线程,为了在使用上尽可能的对业务开发人 员友好和简单,需要屏蔽一些底层 java 并发编程中的细节,让他们不需要去了 解并发容器,阻塞队列,异步任务,线程安全等等方面的知识,只要专心于自己 的业务处理即可。 - 每个批量任务拥有自己的上下文环境
因为一个项目组里同时要处理的批量任务可能有多个,比如考试组,可能就 会有不同的学校的批量的离线文档生成,而题库组则会不同的学科都会有老师同 时进行工作,因此需要一个并发安全的容器保存每个任务的属性信息 - 自动清除已完成和过期任务
因为要提供进度查询,系统需要在内存中维护每个任务的进度信息以供查询, 但是这种查询又是有时间限制的,一个任务完成一段时间后,就不再提供进度查 询了,则就需要我们自动清除已完成和过期任务,用定时轮询吗?
具体实现
可查询进度的并发任务执行框架
- 用户业务方法的结果?
一个方法执行的结果有几种可能?三种,成功:按预想的流程出了结果;失败:按按预想的流程没出结果;异常:没按预想的流程抛出了预料之外的错误。 因此我们定义了一个枚举,表示这三种情况,
/**
*类说明:方法本身运行是否正确的结果类型
*/
public enum TaskResultType {
Success,/*方法执行完成,业务结果也正确*/
Failure,/*方法执行完成,业务结果错误*/
Exception/*方法执行抛出了异常*/
}
对于方法的业务执行结果,返回值有很多种可能,基本类型,系统定义的对 象类型,用户自定义的对象类型都是存在的,我们需要用泛型来说表示这个结果。 同时方法执行失败了,我们还需要告诉用户或者业务开发人员,失败的原因,我 们再定义了一个任务的结果类。
/**
*
*类说明:任务处理后返回的结果实体类
*/
public class TaskResult<R> {
private final TaskResultType resultType;/*方法执行结果*/
private final R returnValue;/*方法执行后的结果数据*/
private final String reason;/*如果方法失败,这里可以填充原因*/
}
- 如何执行用户的业务方法?
我们是个框架,用户的业务各种各样,都要放到我们框架里执行,怎么办? 当然是定义个接口,我们的框架就只执行这个方法,而使用我们框架的业务方都 应该来实现这个接口,当然因为用户业务的数据多样性,意味着我们这个方法的 参数也应该用泛型。
/**
*
*类说明:要求框架使用者实现的任务接口,因为任务的性质在调用时才知道,
*所以传入的参数和方法的返回值均使用泛型
*/
public interface ITaskProcesser<T, R> {
TaskResult<R> taskExecute(T data);
}
- 用户如何提交他的工作和查询任务进度?
用户在前端提交了工作(JOB)到后台,我们需要提供一种封装机制,让业 务开发人员可以将任务的相关信息提交给这个封装机制,用户的需要查询进度的 时候,也从这个封装机制中取得,同时我们的封装机制内部也要负责清除已完成任务。
在这个封装机制里我们定义了一个类 JobInfo,抽象了对用户工作的封装, 一个工作可以包含多个子任务(TASK),这个 JobInfo 中就包括了这个工作的相 关信息,比如工作名,用以区分框架中唯一的工作,也可以避免重复提交,也方 便查询时快速定位工作,除了工作名以外,工作中任务的列表,工作中任务的处 理器都在其中定义。
/**
*类说明:提交给框架执行的工作实体类,
* 工作:表示本批次需要处理的同性质任务(Task)的一个集合
*/
public class JobInfo<R> {
private final String jobName;/*工作名,用以区分框架中唯一的工作*/
private final int jobLength;/*工作中任务的长度*/
private final ITaskProcesser<?, ?> taskProcesser;/*处理工作中任务的处理器*/
private AtomicInteger successCount;/*任务的成功次数*/
private AtomicInteger taskProcessCount;/*工作中任务目前已经处理的次数*/
/*存放每个任务的处理结果,供查询用*/
private LinkedBlockingDeque<TaskResult<R>> taskDetailQueues;
private final long expireTime;/*保留的工作的结果信息供查询的时长*/
}
同时 JobInfo 还有相当多的关于这个工作的方法,比如查询工作进度,查询 每个任务的处理结果,记录每个任务的处理结果等等
负责清除已完成任务,我们则交给 CheckJobProcesser 类来完成,定时轮询的 机制不够优雅,因此我们选用了 DelayQueue 来实现这个功能
/**
*类说明:任务完成后,在一定的时间供查询结果,
* 之后为释放资源节约内存,需要定期处理过期的任务
*/
public class CheckJobProcesser {
/*存放任务的队列*/
private static DelayQueue<ItemVo<String>> queue
= new DelayQueue<ItemVo<String>>();
并且在其中定义了清除已完成任务的 Runnable 和相关的工作线程。
private static class FetchJob implements Runnable{
private static DelayQueue<ItemVo<String>> queue
= CheckJobProcesser.queue;
//缓存的工作信息
private static Map<String,JobInfo<?>> jobInfoMap
= PendingJobPool.getMap();
@Override
public void run() {
while(true){
try{
ItemVo<String> item = queue.take();
String jobName = (String)item.getData();
jobInfoMap.remove(jobName);
System.out.println(jobName+" 过期了,从缓存中清除");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/*任务完成后,放入队列,经过expireTime时间后,会从整个框架中移除*/
public void putJob(String jobName,long expireTime){
ItemVo<String> item = new ItemVo<>(expireTime,jobName);
queue.offer(item);
System.out.println(jobName+"已经放入过期检查缓存,时长:"+expireTime);
}
static{
Thread thread = new Thread(new FetchJob());
thread.setDaemon(true);
thread.start();
System.out.println("开启过期检查的守护线程......");
}
- 框架的主体类
主体类则是 PendingJobPool,这也是业务开发人员主要使用的类。这个类主 要负责调度,例如工作(JOB)和任务(TASK)的提交,任务(TASK)的保存, 任务(TASK)的并发执行,工作进度的查询接口和任务执行情况的查询等等。
/**
*类说明:框架的主体类,也是调用者主要使用的类
*/
public class PendingJobPool {
/*框架运行时的线程数,与机器的CPU数相同*/
private static final int THREAD_COUNTS
= Runtime.getRuntime().availableProcessors();
/*队列,线程池使用,用以存放待处理的任务*/
private static BlockingQueue<Runnable> taskQueue
= new ArrayBlockingQueue<Runnable>(5000);
/*线程池,固定大小,有界队列*/
private static ExecutorService taskExecutor
= new ThreadPoolExecutor(THREAD_COUNTS, THREAD_COUNTS,
60, TimeUnit.SECONDS, taskQueue);
/*工作信息的存放容器*/
private static ConcurrentHashMap<String,JobInfo<?>> jobInfoMap
= new ConcurrentHashMap<String, JobInfo<?>>();
以下贴出单机程序的全部源码以及集成spring使用
/**
*
*类说明:模拟一个应用程序,提交工作和任务,并查询任务进度
*程序入口
*/
public class AppTest {
private final static String JOB_NAME = "计算数值";
private final static int JOB_LENGTH = 1000;
//查询任务进度的线程
private static class QueryResult implements Runnable{
private PendingJobPool pool;
public QueryResult(PendingJobPool pool) {
super();
this.pool = pool;
}
@Override
public void run() {
int i=0;
while(i<350) {
List<TaskResult<String>> taskDetail = pool.getTaskDetail(JOB_NAME);
if(!taskDetail.isEmpty()) {
System.out.println(pool.getTaskProgess(JOB_NAME));
System.out.println(taskDetail);
}
SleepTools.ms(100);
i++;
}
}
}
public static void main(String[] args) {
MyTask myTask = new MyTask();
PendingJobPool pool = PendingJobPool.getInstance();
pool.registerJob(JOB_NAME, JOB_LENGTH, myTask,5);
Random r = new Random();
for(int i=0;i<JOB_LENGTH;i++) {
pool.putTask(JOB_NAME, r.nextInt(1000));
}
Thread t = new Thread(new QueryResult(pool));
t.start();
}
}
/**
*类说明:一个实际任务类,将数值加上一个随机数,并休眠随机时间
*/
public class MyTask implements ITaskProcesser<Integer,Integer> {
@Override
public TaskResult<Integer> taskExecute(Integer data) {
Random r = new Random();
int flag = r.nextInt(500);
SleepTools.ms(flag);
if(flag<=300) {//正常处理的情况
Integer returnValue = data.intValue()+flag;
return new TaskResult<Integer>(TaskResultType.Success,returnValue);
}else if(flag>301&&flag<=400) {//处理失败的情况
return new TaskResult<Integer>(TaskResultType.Failure,-1,"Failure");
}else {//发生异常的情况
try {
throw new RuntimeException("异常发生了!!");
} catch (Exception e) {
return new TaskResult<Integer>(TaskResultType.Exception,
-1,e.getMessage());
}
}
}
}
/**
*
*类说明:要求框架使用者实现的任务接口,因为任务的性质在调用时才知道,
*所以传入的参数和方法的返回值均使用泛型
*/
public interface ITaskProcesser<T, R> {
TaskResult<R> taskExecute(T data);
}
/**
*类说明:提交给框架执行的工作实体类,
* 工作:表示本批次需要处理的同性质任务(Task)的一个集合
*/
public class JobInfo<R> {
private final String jobName;/*工作名,用以区分框架中唯一的工作*/
private final int jobLength;/*工作中任务的长度*/
private final ITaskProcesser<?, ?> taskProcesser;/*处理工作中任务的处理器*/
private AtomicInteger successCount;/*任务的成功次数*/
private AtomicInteger taskProcessCount;/*工作中任务目前已经处理的次数*/
/*存放每个任务的处理结果,供查询用*/
private LinkedBlockingDeque<TaskResult<R>> taskDetailQueues;
private final long expireTime;/*保留的工作的结果信息供查询的时长*/
private static CheckJobProcesser checkJob = CheckJobProcesser.getInstance();
public JobInfo(String jobName,int jobLength,
ITaskProcesser<?, ?> taskProcesser,
long expireTime) {
this.jobName = jobName;
this.jobLength = jobLength;
successCount = new AtomicInteger(0);
taskProcessCount = new AtomicInteger(0);
this.taskProcesser = taskProcesser;
taskDetailQueues = new LinkedBlockingDeque<TaskResult<R>>(jobLength);
this.expireTime = expireTime;
}
public int getSuccCount() {
return successCount.get();
}
public int getTaskProcessCount() {
return taskProcessCount.get();
}
//提供工作中失败的次数
public int getFailCount() {
return taskProcessCount.get() - successCount.get();
}
public ITaskProcesser<?, ?> getTaskProcesser() {
return taskProcesser;
}
public int getJobLength() {
return jobLength;
}
/*提供工作的整体进度信息*/
public String getTotalProcess() {
return "Success["+successCount.get()+"]/Current["+taskProcessCount.get()
+"] Total["+jobLength+"]";
}
/*提供工作中每个任务的处理结果*/
public List<TaskResult<R>> getTaskDetail(){
List<TaskResult<R>> taskResultList = new LinkedList<>();
TaskResult<R> taskResult;
while((taskResult=taskDetailQueues.pollFirst())!=null){
taskResultList.add(taskResult);
}
return taskResultList;
}
/*每个任务处理完成后,记录任务的处理结果,因为从业务应用的角度来说,
对查询任务进度数据的一致性要不高
我们保证最终一致性即可,无需对整个方法加锁*/
public void addTaskResult(TaskResult<R> taskResult){
if(TaskResultType.Success.equals(taskResult.getResultType())){
successCount.incrementAndGet();
}
taskProcessCount.incrementAndGet();
taskDetailQueues.addLast(taskResult);
if(taskProcessCount.get()==jobLength){
checkJob.putJob(jobName,expireTime);
}
}
}
/**
*
*类说明:任务处理后返回的结果实体类
*/
public class TaskResult<R> {
private final TaskResultType resultType;/*方法执行结果*/
private final R returnValue;/*方法执行后的结果数据*/
private final String reason;/*如果方法失败,这里可以填充原因*/
public TaskResult(TaskResultType resultType, R returnValue, String reason) {
super();
this.resultType = resultType;
this.returnValue = returnValue;
this.reason = reason;
}
public TaskResult(TaskResultType resultType, R returnValue) {
super();
this.resultType = resultType;
this.returnValue = returnValue;
this.reason = "Success";
}
public TaskResultType getResultType() {
return resultType;
}
public String getReason() {
return reason;
}
public R getReturnValue() {
return returnValue;
}
@Override
public String toString() {
return "TaskResult [resultType=" + resultType
+ ", returnValue=" + returnValue
+ ", reason=" + reason + "]";
}
}
/**
*类说明:方法本身运行是否正确的结果类型
*/
public enum TaskResultType {
Success,/*方法执行完成,业务结果也正确*/
Failure,/*方法执行完成,业务结果错误*/
Exception/*方法执行抛出了异常*/
}
/**
*类说明:任务完成后,在一定的时间供查询结果,
* 之后为释放资源节约内存,需要定期处理过期的任务
*/
public class CheckJobProcesser {
/*存放任务的队列*/
private static DelayQueue<ItemVo<String>> queue
= new DelayQueue<ItemVo<String>>();
/*单例化*/
private static class ProcesserHolder{
public static CheckJobProcesser processer = new CheckJobProcesser();
}
public static CheckJobProcesser getInstance() {
return ProcesserHolder.processer;
}
/*处理队列中到期任务*/
private static class FetchJob implements Runnable{
private static DelayQueue<ItemVo<String>> queue
= CheckJobProcesser.queue;
//缓存的工作信息
private static Map<String,JobInfo<?>> jobInfoMap
= PendingJobPool.getMap();
@Override
public void run() {
while(true){
try{
ItemVo<String> item = queue.take();
String jobName = (String)item.getData();
jobInfoMap.remove(jobName);
System.out.println(jobName+" 过期了,从缓存中清除");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/*任务完成后,放入队列,经过expireTime时间后,会从整个框架中移除*/
public void putJob(String jobName,long expireTime){
ItemVo<String> item = new ItemVo<>(expireTime,jobName);
queue.offer(item);
System.out.println(jobName+"已经放入过期检查缓存,时长:"+expireTime);
}
static{
Thread thread = new Thread(new FetchJob());
thread.setDaemon(true);
thread.start();
System.out.println("开启过期检查的守护线程......");
}
}
/**
* 类说明:框架的主体类,也是调用者主要使用的类
*/
public class PendingJobPool {
/*框架运行时的线程数,与机器的CPU数相同*/
private static final int THREAD_COUNTS
= Runtime.getRuntime().availableProcessors();
/*队列,线程池使用,用以存放待处理的任务*/
private static BlockingQueue<Runnable> taskQueue
= new ArrayBlockingQueue<Runnable>(5000);
/*线程池,固定大小,有界队列*/
private static ExecutorService taskExecutor
= new ThreadPoolExecutor(THREAD_COUNTS, THREAD_COUNTS,
60, TimeUnit.SECONDS, taskQueue);
/*工作信息的存放容器*/
private static ConcurrentHashMap<String, JobInfo<?>> jobInfoMap
= new ConcurrentHashMap<String, JobInfo<?>>();
// /*检查过期工作的处理器*/
// private static CheckJobProcesser checkJob
// = CheckJobProcesser.getInstance();
public static Map<String, JobInfo<?>> getMap() {
return jobInfoMap;
}
/*以单例模式启动*/
private PendingJobPool() {
}
private static class JobPoolHolder {
public static PendingJobPool pool = new PendingJobPool();
}
public static PendingJobPool getInstance() {
return JobPoolHolder.pool;
}
/*对工作中的任务进行包装,提交给线程池使用,
并将处理任务的结果,写入缓存以供查询*/
private static class PendingTask<T, R> implements Runnable {
private JobInfo<R> jobInfo;
private T processData;
public PendingTask(JobInfo<R> jobInfo, T processData) {
this.jobInfo = jobInfo;
this.processData = processData;
}
@Override
public void run() {
R r = null;
ITaskProcesser<T, R> taskProcesser
= (ITaskProcesser<T, R>) jobInfo.getTaskProcesser();
TaskResult<R> result = null;
try {
result = taskProcesser.taskExecute(processData);
if (result == null) {
result = new TaskResult<R>(TaskResultType.Exception, r
, "result is null");
}
if (result.getResultType() == null) {
if (result.getReason() == null) {
result = new TaskResult<R>(TaskResultType.Exception, r
, "result is null");
} else {
result = new TaskResult<R>(TaskResultType.Exception, r
, "result is null,reason:"
+ result.getReason());
}
}
} catch (Exception e) {
e.printStackTrace();
result = new TaskResult<R>(TaskResultType.Exception, r
, e.getMessage());
} finally {
jobInfo.addTaskResult(result);
}
}
}
//调用者提交工作中的任务
public <T, R> void putTask(String jobName, T t) {
JobInfo<R> jobInfo = getJob(jobName);
PendingTask<T, R> task = new PendingTask<>(jobInfo, t);
taskExecutor.execute(task);
}
//调用者注册工作,如工作名,任务的处理器等等
public <R> void registerJob(String jobName, int jobLength,
ITaskProcesser<?, ?> taskProcesser, long expireTime) {
JobInfo<R> jobInfo =
new JobInfo<R>(jobName, jobLength, taskProcesser, expireTime);
if (jobInfoMap.putIfAbsent(jobName, jobInfo) != null) {
throw new RuntimeException(jobName + "已经注册!");
}
}
/*根据工作名称检索工作*/
@SuppressWarnings("unchecked")
private <R> JobInfo<R> getJob(String jobName) {
JobInfo<R> jobInfo = (JobInfo<R>) jobInfoMap.get(jobName);
if (null == jobInfo) {
throw new RuntimeException(jobName + "是非法任务!");
}
return jobInfo;
}
/*获得工作的整体处理进度*/
public <R> String getTaskProgess(String jobName) {
JobInfo<R> jobInfo = getJob(jobName);
return jobInfo.getTotalProcess();
}
/*获得每个任务的处理详情*/
public <R> List<TaskResult<R>> getTaskDetail(String jobName) {
JobInfo<R> jobInfo = getJob(jobName);
return jobInfo.getTaskDetail();
}
}