我正在为appengine工作调度员,默认调度程序总是会启动3-4个实例完成所有工作,一些溢出实例可能需要数千个任务,或者只有几个然后坐着燃烧的cpus无所事事。
我的任务涉及处理许多不同大小的域的作业,有时会产生巨大的吞吐量,有时候是一个用户需要更新10,000个模型;如果我将正常的appengine任务调度程序松开,它会以两种方式失败:1)后端从不关闭,当内存达到上限时,java gc使实例捶打并表现得像它几乎是一个僵尸但从未关闭{仍然获取/保留作业},以及2)许多域只有一个用户,其处理时间远远超过所有其他域,并且在域的其余部分完成后,这会使后端保持很长时间。
这些任务必须全天运行,并且需要多个后端来处理扇出,所以我不能将它们全部转储到B8并将其称为一天。所以我们需要一个调度员来管理如何分配任务后端。
现在,我不想为每个任务支付数据存储操作只是为了节省几分钟的CPU时间,所以我的攻击计划{please critique}是在RAM中使用静态ConcurrentHashMap,开始每次运行()在一次尝试中,让每个延迟任务在启动时将其[hashcode,startTime]置于最后并在最终中删除(hashcode)。每个后端实例都会有一个这样的映射,它正在运行作业,包含在一个方法中,BackendCounter.addToLiveMap(this);它的.size()用作该后端有多少作业的运行总计{用时间戳来检测运行> 10分钟的僵尸作业}。作业调度程序可以为每个实例触发一个工作线程,以监视该实例中正在运行的作业(不包括自身)的数量,并在memcache中保留一个排序列表,其中包含哪些实例有多少个活动。如果一个实例低于X live tasks的阈值,选择一个溢出实例来推迟,然后让方法BackendCounter.addToLiveMap(this)抛出一个异常我可以捕获告诉作业只是将自己安排到一个新实例{ChangeInstanceException# getNewTarget()}。这样我就可以防止几乎没用的实例获得新的工作,这样他们就有机会关闭,只支付一些memcache操作,并且扇出只需支付写入和删除静态地图。
它解决了问题二,即实例小时杀手。至于问题一,即如何防止一个实例{通常是实例0和1}达到峰值记忆并开始转向黑暗面,我在两个选项之间徘徊。
一方面,我可以使用对BackendCounter.addToLiveMap的预期调用(this)抛出ChangeInstanceException并简单地检查内存:
if(((float)Runtime.getRuntime()。freeMemory()/ Runtime.getRuntime()。totalMemory())< 0.9)抛出新的ChangeInstanceException(getOverflowInstance()); 这种天真的方法只会告诉任何接近其内存限制的实例,将所有新作品发送到其他地方。
另一方面,我可以保留实例0和1以处理溢出{并在两者中的哪一个之间切换以获得新的作业以使其有机会关闭},然后将扇出发送到实例2+,这将只会直到他们说并行,10或15个工作。扇出非常一致,只需要几分钟,因此实例2,3和最多4个将需要打开,并且有时间关闭,而另一个实例会受到更多负载的影响。 我唯一害怕的是,如果作业开始从一个实例跳转到另一个实例,可以通过重定向头限制来跳过抛出ChangeInstanceException。
非常感谢任何想法或建议。