异构任务集的动态优先级

时间:2018-04-18 21:55:20

标签: java scheduler job-scheduling

我有一堆重复的任务要安排。他们查询数据库以找出要执行的操作,然后执行某些操作,如统计信息更新,发送电子邮件,获取文件和导入文件。目前,可能有十个,这个数字预计会增长很多。我没有给出任何时间限制,实际上,选择算法是我的工作,以便没有人抱怨。 :d

目前,我正在使用线程的临时组合和定期安排的任务,例如

  • 对于最重要的任务,有一个自己的线程在空闲时会回到短暂的睡眠状态(当新的重要工作到来时,它可以被唤醒)。
  • 另一项重要任务是在其自己的主题中每小时安排一次
  • 中等重要性任务定期安排“填补漏洞”,因此可能只有其中一个在任何时刻运行
  • 最不重要的任务全部由一个专用线程处理

目前似乎运作良好,但它不是面向未来的,并且出于以下原因感觉不对:

  • 由于最不重要的任务的队列可能会增长很多,这些任务可能会无限期地延迟。
  • 填充孔可能会出错,并且可能会同时运行许多任务。
  • 在任何给定时刻运行的任务数量应取决于服务器负载。 (*)

(*)它主要是一个Web服务器,服务请求实际上是最高优先级。获得单独的服务器无济于事,因为瓶颈通常是数据库。目前,它运行良好,但我正在寻找一个更好的解决方案,因为我们希望负载在一两年内增长100倍。

我的想法是提高工作的优先级,当它被推迟太多时。例如,有统计数据每小时运行一次并将它们延迟几个小时并不是什么大不了的事,但它不应该是一整天而且不应该是整整一周。

我很乐意通过以下方式替换我的所有AbstractExecutionThreadServiceAbstractScheduledService

  • 无论如何,立即启动优先级最高的任务。
  • 仅当总负载为“小”时才启动中优先级任务。
  • 仅当系统“大部分空闲”时才启动优先级最低的任务。
  • 使用提供的公式增加延迟任务的优先级。

这肯定听起来很模糊,让它更精确是我要问的一部分。我的竞争目标是

  • 永远不要拖延重要的任务。
  • 永远不要让太多同时运行的任务太慢地使服务器变慢。

没有硬性截止日期,也没有必要尽量减少使用的线程数。我并不坚持完全按照我所描述的方式解决问题,我不是在寻找一个库(我也没有坚持重新发明轮子)。我不认为类似cron的调度程序是正确的解决方案。

2 个答案:

答案 0 :(得分:1)

使用ExecutorService模型,重新排序执行程序任务的经典解决方案是创建一个ThreadPoolExecutorPriorityBlockingQueue为其提供任务 - 如here所述。< / p>

然而,需要安排任务也会对其产生影响。 ScheduledThreadPoolExecutor使用内部自定义BlockingQueue在计划准备就绪时提供任务,但我认为您很清楚,它不容易进行进一步的自定义。

乍一看,DelayQueue看起来完全符合条款 - 它可以优先考虑下一个Delayed元素或任务。这会处理Delayed.getDelay()关于它是否准备就绪的迟到决定。

当你试图将DelayQueue<DelayedRunnable>之类的东西传递给ThreadPoolExecutor的构造函数时,美国人用这个计划软膏。这只会接受BlockingQueue<Runnable>,而不是BlockingQueue<? extends Runnable>

解决此问题的一种方法是创建BlockingQueue<Runnable>的最小实施,该实施委派给BlockingQueue。基础知识在这里:

public class BlockingDelayQueue extends AbstractQueue<Runnable>
        implements BlockingQueue<Runnable> {

    private final DelayQueue<DelayedRunnable> delayQueue;

    public BlockingDelayQueue(DelayQueue<DelayedRunnable> delayQueue) {
        this.delayQueue = delayQueue;
    }

    @Override
    public boolean isEmpty() {
        return delayQueue.isEmpty();
    }

    @Override
    public Runnable poll(long timeout, TimeUnit unit)
            throws InterruptedException {
        DelayedRunnable delayedRunnable = delayQueue.poll(timeout, unit);

        if (delayedRunnable == null)
            return null;
        return delayedRunnable.getCommand();
    }
    ...
}

DelayedRunnable的实验版用于证明其中使用简单的Priority枚举来检查执行者的“繁忙程度”:

LOW {
    boolean isReady(ThreadPoolExecutor executor) {
        return executor.getActiveCount() == 0;
    }
},
MEDIUM {
    boolean isReady(ThreadPoolExecutor executor) {
        return executor.getActiveCount() <= 1;
    }
},
HIGH {
    boolean isReady(ThreadPoolExecutor executor) {
        return true;
    }
};

然后可以检查DelayedRunnable.getDelay()

@Override
public long getDelay(TimeUnit unit) {
    long millis;
    if (!priority.isReady(executor))
        millis = 1000;
    else
        millis = time - System.currentTimeMillis();
    return unit.convert(millis, TimeUnit.MILLISECONDS);
}

- 只要<= 0尚未就绪,它就不会返回priority

这似乎运作良好,例如在这里启动标准的2s睡眠任务......

    DelayedScheduler scheduler = new DelayedScheduler();
    scheduler.schedule(task("Low 1"), 1, TimeUnit.SECONDS, Priority.LOW);
    scheduler.schedule(task("Low 2"), 2, TimeUnit.SECONDS, Priority.LOW);
    scheduler.schedule(task("Low 3"), 3, TimeUnit.SECONDS, Priority.LOW);
    scheduler.schedule(task("Medium 1"), 1, TimeUnit.SECONDS, Priority.MEDIUM);
    scheduler.schedule(task("Medium 2"), 2, TimeUnit.SECONDS, Priority.MEDIUM);
    scheduler.schedule(task("Medium 3"), 3, TimeUnit.SECONDS, Priority.MEDIUM);
    scheduler.schedule(task("High 1"), 1, TimeUnit.SECONDS, Priority.HIGH);
    scheduler.schedule(task("High 2"), 2, TimeUnit.SECONDS, Priority.HIGH);
    scheduler.schedule(task("High 3"), 3, TimeUnit.SECONDS, Priority.HIGH);

......产生了正确的结果:

High 1 started at 1087ms
Medium 1 started at 1087ms
High 2 started at 2087ms
  Medium 1 ended at 3087ms
  High 1 ended at 3087ms
High 3 started at 3087ms
  High 2 ended at 4088ms
Medium 2 started at 4088ms
  High 3 ended at 5088ms
Medium 3 started at 5088ms
  Medium 2 ended at 6088ms
  Medium 3 ended at 7089ms
Low 1 started at 7089ms
  Low 1 ended at 9089ms
Low 2 started at 9089ms
  Low 2 ended at 11089ms
Low 3 started at 11089ms
  Low 3 ended at 13089ms

- 允许中等优先级任务,而只有一个高优先级任务正在运行,低,而没有其他任何事情发生。

DelayedSchedulerGitHub上的其他未见位。)

答案 1 :(得分:0)

我认为你非常接近你想要的东西,也许只需要一点鼓励/批准/约束

我的想法是&#34;如果我知道我可以运行的最大并发线程数,那么我将如何与3个线程队列分享#34;。

一旦我知道这一点,我就可以设置3个队列,每个队列都有不同的可用线程份额。   - 优先级1(最高)获得50%的工作   - 优先级2获得35%的工作   - 优先级3(最低)获得15%的工作