Java Appengine后端任务调度程序:更经济的版本

Java Appengine后端任务调度程序:更经济的版本,java,multithreading,performance,google-app-engine,scheduled-tasks,Java,Multithreading,Performance,Google App Engine,Scheduled Tasks,我正在为appengine开发一个作业调度器,默认的调度器总是启动3-4个实例来完成所有的工作,一些溢出实例可能需要数千个任务,或者只有几个,然后坐在那里烧CPU什么也不做 我的任务涉及处理许多不同大小的域的作业,有时吞吐量很大,有时一个用户需要更新10000个模型;如果我将正常的appengine任务调度器打开,它会以两种方式失败:1)后端从不关闭,当内存达到上限时,java gc会对实例进行重击,表现得像一个僵尸,但从未关闭{并且仍然接受/保留作业},2)许多域都有一个用户,处理该用户的时间

我正在为appengine开发一个作业调度器,默认的调度器总是启动3-4个实例来完成所有的工作,一些溢出实例可能需要数千个任务,或者只有几个,然后坐在那里烧CPU什么也不做

我的任务涉及处理许多不同大小的域的作业,有时吞吐量很大,有时一个用户需要更新10000个模型;如果我将正常的appengine任务调度器打开,它会以两种方式失败:1)后端从不关闭,当内存达到上限时,java gc会对实例进行重击,表现得像一个僵尸,但从未关闭{并且仍然接受/保留作业},2)许多域都有一个用户,处理该用户的时间远远超过其他域,这使得后端在域的其余部分完成后长时间保持活动状态

这些任务必须全天运行,并且需要多个后端来处理扇出,因此我不能将它们全部转储到B8上,并称之为一天。因此,我们需要一个调度程序来管理如何将任务分配给后端

现在,我不想为了节省几分钟的cpu时间而支付每个任务的数据存储操作,所以我的攻击计划{请批评}是在RAM中使用一个静态ConcurrentHashMap,在一次尝试中启动每个run(),让每个延迟的任务在启动时放入[hashcode,startTime],在最后一次测试中删除(hashcode)。每个正在运行作业的后端实例将有一个这样的映射,包装在一个方法BackendCounter.addToLiveMap(this)中;它的.size()用作该后端上活动的作业总数{使用时间戳检测运行时间>10分钟的僵尸作业}。作业调度器可以为每个实例触发一个工作线程,以监视该实例中运行的作业(不包括其本身)数量,并在memcache中保留一个排序列表,列出哪些实例有多少任务处于活动状态。如果一个实例低于X个live tasks的阈值,请选择一个要延迟的溢出实例,然后让方法BackendCounter.addToLiveMap(this)抛出一个我可以捕获的异常,告诉作业只将自己调度到一个新实例{ChangeInstanceException#getNewTarget()}。通过这种方式,我可以防止很少使用的实例获得新的工作,这样它们就有机会关闭,只需支付一些memcache操作,而fanout只需支付对静态映射的写入和删除

这就解决了第二个问题,即实例时间杀手。至于问题1,即如何防止一个实例{通常是实例0和实例1}达到峰值内存并开始转向黑暗面,我在两个选项之间左右为难

一方面,我可以使用对BackendCounter.addToLiveMap(此)的预期调用引发ChangeInstanceException,只需检查内存:


if(((float)Runtime.getRuntime().freemory()/Runtime.getRuntime().totalMemory())为什么不简单地使用拉式队列,让后端在需要更多工作时从队列中抓取任务?您可以使用任何逻辑来决定何时应该这样做。或者,停止使用后端,改为使用任务队列,它将为您处理实例启动、关闭和向实例分配任务。我们的frontend配置为低资源分配,因为它们永远不允许做任何繁重的工作。这使得面向客户端的响应快速而代价高昂,因此我们必须使用后端来获得必要的资源。我将研究如何使用拉队列,并让后端实例在达到内存限制之前停止工作,以及然后停止拖拽,返回,等待死亡。否则就用LifecycleManager.getInstance().BeginShutton(30)杀死实例;