我们需要异步处理不同种类的对象。每种类型/类型的对象都使用API密钥进行处理。
每个API密钥在并发使用上都有自己的限制(例如,一个API密钥的并行会话数不得超过5个)。
我们对工作线程数有全局限制(CPU限制)。
我们希望在工作线程限制内进行尽可能多的API调用。
可能的解决方案:
2 tasks with KEY1 (max 2 session) -\ total 3 workers
5 tasks with KEY2 (max 3 session) -/
是:
1. worker1: KEY2, worker2: KEY2, worker3: KEY2 (in queue: 2x KEY1, 2x KEY2)
2. worker1: KEY1, worker2: KEY2, worker3: KEY2 (in queue: 1x KEY1, 3x KEY2)
3. worker1: KEY1, worker2: KEY1, worker3: KEY2 (in queue: 4x KEY2)
可能的解决方案:
3 tasks with KEY1 (max 1 session) & 3 workers
是:
1. worker1: KEY1, worker2: IDLE, worker3: IDLE, (in queue 2x KEY1)
执行顺序无关紧要(但我们希望像政策一样先进先出),最大吞吐量是最重要的。
不清楚选择哪种实施策略。
ThreadExecutor
具有任何队列是不够的,因为您需要知道ThreadExecutor
当前正在使用哪些API密钥。
答案 0 :(得分:1)
我不确定问题是否正确,但是您需要为每个API密钥准备一个Semaphore
。
Semaphore key1Semaphore = new Semaphore(2);
Semaphore key2Semaphore = new Semaphore(3);
您可以通过致电key1Semaphore
来检查key1Semaphore.tryAcquire()
是否获得许可,并获得许可。这是非阻塞的,因此如果失败并返回false,则可以尝试从另一个API密钥获取信号并从中提交任务。
重要的是,在使用API密钥之一完成任务后,必须释放信号量许可。
您可能需要一个额外的对象来与wait()
和notify()
进行同步,以便当任务完成时,它通知正在分派任务的主线程再次检查信号量。
因此,从本质上讲,您所获得的是任务调度程序将向5个工作人员中的ExecutorService
提交5个任务,然后在释放其中一个信号量许可之前,它将无法再提交任何内容。 / p>
当任务完成并且释放了许可时,调度员会收到通知,因此可以取消等待,然后再次检查信号量,然后将任务提交给ExecutorService
。
该解决方案偏向于第一个API密钥,但是您可以通过检查每个密钥的任务长度并更公平地分配它们,来进一步完善它。您甚至可以旋转索引,以便在每个循环中将索引递增1,这样第一次是从API KEY 1开始,第二次是从API KEY 2等。
答案 1 :(得分:1)
我可能会创建一个维护服务
Queue
,其中包含由任务和相应的键组成的条目,Map
)的Map<String,AtomicInteger>
和ThreadPoolExecutor
。如果全局线程计数已满,并且已提交任务,则将其放入队列的末尾。
如果全局线程计数未满,则检查与请求密钥对应的映射值以获取密钥线程限制;如果已达到,则将任务放回队列,否则提交给执行者服务。
“提交给执行者服务”将不会直接提交任务,而是增加密钥线程数,并将任务包装到Runnable
中,这将另外1.减少映射中的密钥线程数和2触发队列的重新评估,以便在适用时提交新任务。
也有可能在BlockingQueue
中创建“每个键的活动计数”逻辑,该逻辑将作为first()
返回包含未达到最大计数的键任务的下一个元素,并将其作为管理队列传递给ThreadPoolExecutor
构造函数;但我确信这会破坏队列合同,并且使用起来并不完全安全。