使用ThreadPools在Java中处理K个线程(N * M>> K)上的N个用户的M个任务

时间:2014-12-01 11:20:20

标签: java multithreading threadpoolexecutor graceful-degradation qos

我在tomcat服务器上运行RESTful样式的RPC(远程过程调用)API,该服务器在K个线程上处理具有M个任务的N个用户的数据。大多数情况下,一个用户有大约20到500个任务(但M可能在1到5000之间)。一项任务需要大约10到20秒才能完成,但可以在1秒到20分钟之间。目前,大多数系统有一个用户,有时最多三个,但在不久的将来它会同时增加到大约10个用户。我们的服务器有10个内核,因此我想使用10个线程。目前,每个用户都有5个线程进行处理,效果很好。但是a)大多数时候机器仅使用50%(导致针在“30分钟”范围内等待),有时服务器负载高达150%。

解决方案的要求:

  1. 服务器始终使用100%(如果有任务)
  2. 所有用户在线程执行方面都是一样的(与其他每个用户完成的线程数相同)
  3. 新用户不必等到早期用户的所有任务都完成(特别是在user1有5000个任务且user2有1个这很重要的情况下)
  4. 想到的解决方案:

    1. 只使用10个线程的FixedThreadPoolExecutor,违反条件3

    2. 使用PriorityBlockingQueue并在我的任务中实现compareTo方法 - >不能使用threadpoolExecutors提交方法(因此我不知道提交的任务何时结束)

    3. 实现像阻塞队列一样的“循环”,其中K个线程(在我们的例子中为10)以循环方式从N个内部队列中获取新任务 - >为了能够将任务放入正确的队列,我需要一个“提交”方法,该方法需要多个参数(我也需要实现一个ThreadPoolExecutor)

    4. 我试图用循环法来说明我的意思,比如阻止队列(如果没有帮助,可以随意编辑它):

        --                       --
        --        --        --   --             queue task load, 
        --   --   --   --   --   --        --   one task denoted by --
        --   --   --   --   --   --   --   -- 
      | Q1 | Q2 | Q3 | Q4 | Q5 | Q6 | Q7 | QN |
      |                      *   ^            |
      |                  last|   |next        |
      |                           -------------
      \                          /
       \    |    |    |    |    |
       | T1 | T2 | T3 | T4 | TK |
      

      是否有一个优雅的解决方案,主要使用Java标准API(或任何其他广泛的Java API)来实现这种处理行为(可能是我提出的解决方案之一或任何其他解决方案)?或者您对如何解决这个问题有任何其他提示?

3 个答案:

答案 0 :(得分:0)

如果您同意最小化整体任务延迟是对要求2和3的良好替代,并且您有足够好的任务运行时估计,那么我可能会得到答案。

您可以将任务提交时间存储在每个任务中,以便以后可以随时计算其估计的延迟。 然后,您可以构建PriorityBlockingQueue,在插入新任务时,始终将其插入到提供一定公平性的队列位置,并尝试最小化总体延迟。首先,这将使长期运行的任务处于不利地位。我自己没有尝试过,但我会根据您的估计运行时间分配任务优先级,并使用estimatedRuntime-waitingTime作为优先级(首先采用最低级别的prreeity工作)。在等待足够的负面优先权之后,这将给重型任务带来机会。在那之前,轻量级任务将有更好的机会成为第一,即使它们刚刚被提交。但是,这种安排只会如您的估计所允许的那样公平。

至于循环要求:如果这非常重要,您也可以在队列中处理此问题。基本上,当您使用线程池时,您可以根据在队列中插入新作业的位置来实现您的调度策略。如果您可以估算作业延迟,则可以通过插入正确的位置来平衡用户。

答案 1 :(得分:0)

满足您的要求:

1)最大化线程使用:任何ThreadPoolExecutor都会处理这个问题 2)所有用户都被视为相同:基本上需要循环设置 3)避免新用户按FIFO顺序等待:与#2相同。

你也提到了提交和获得结果的能力。

您可以考虑使用包装器对象的独立PriorityBlockingQueue<Job>,例如:

class Job implements Comparable<Job> {
    private int priority;
    private YourCallable task;

    public Job(int priority, YourCallable task) {
        this.priority = priority;
        this.task = task;
    }

    @Override
    public int compareTo(Job job) {
        // use whatever order you prefer, based on the priority int
    }
}

您的制作人向PriorityBlockingQueue提供作业,并指定优先级(基于您的循环规则或其他),以及实现Callable的任务。然后,您的消费者会为作业执行queue.poll。

掌握完毕后,您可以获取该Job对象中包含的任务,并将其发送给您选择的ThreadPoolExecutor进行处理。

答案 2 :(得分:0)

我一直致力于类似于循环设置的解决方案。它变得非常复杂,但我想我想出了一个不错的实现。它可能不是一个非常优雅的解决方案,但有单元测试显示一些功能。不幸的是,TaskQ尚未处于“1.0”阶段。

它涵盖了您的第1至3点:

  • 您可以指定要使用的线程数量,如果有足够的任务,则使用所有线程。
  • 每个用户都会在线程可用时转弯
  • 如果一个用户A在队列中有500个任务而另一个用户B有1个任务,那么一旦线程可用,用户B的任务就会被执行。

目前还没有手册/文档,希望您能抽出时间进行调查。显示某些用法的单元测试是here,扩展/使用的主要类是RunnableTaskQWithQos