1

我正在制作Google应用程序引擎的成绩簿。我跟踪每个学生的评分等级。评分时段可以重叠。由于我可能一次显示数百个这样的等级,因此我会预先计算服务器上的等级。所以,对于任何一个学生,我可能有很多计算成绩 - 每个成绩阶段一个。找到缺陷!使用任务队列可靠地执行长期任务

现在,老师从测验中输入新分数。该分数可能会影响许多计算成绩,因为它可能会落入许多分数阶段。我需要重新计算所有受影响的成绩。这可能需要很长时间,因为在每个评分阶段,我需要获取所有相关分数,并对这些分数执行复杂的例程。我认为30秒是不够的 - 特别是如果数据存储今天感觉缓慢。此外,失败不是一种选择。更新某些等级并使其他人无声无息地过期是不可接受的。

因此,我认为对于自己来说,了解任务队列是多么美妙的时刻!

我不是在数据库结构或任何方面的专家,但这里是什么,我希望做一个概要:

public ReturnCode addNewScore(Float score, Date date, Long studentId) 
{ 
    List<CalculatedGrade> existingGrades = getAllRelevantGradesForStudent(studentId, date); 

    for (CalculatedGrade grade : existingGrades) 
    { 
     grade.markDirty(); //leaves a record that this grade is no longer up to date 
    } 

    persistenceManager.makePersistentAll(existingGrades); 
    //DANGER ZONE? 
    persistenceManager.makePersistent(new IndividualScore(score, date, studentId)); 

    tellTheTaskQueueToStartCalculating(); 

    return OMG_IT_WORKED; 
} 

这似乎是一个快速的方法,以纪念所有相关等级脏的。如果中途失败,则返回失败,客户将知道再次尝试。如果客户稍后尝试获取脏分,我们可以在那里返回错误。

然后,任务队列的代码会是这个样子:

public void calculateThemGrades() 
{ 
    List<CalculatedGrade> dirtyGrades = getAllDirtyGrades(); 

    try 
    { 
     for (CalculatedGrade grade : dirtyGrades) 
     { 
      List<Score> relevantScores = getAllRelevantScores(); 
      Float cleanGrade = calculateGrade(relevantScores); 
      grade.setGrade(cleanGrade); 
      grade.markClean(); 

      persistenceManager.flush(); 
     } 
    } 
    catch(Throwable anything) 
    { 
     //if there was any problem, like we ran out of time or the datastore is down or whatever, just try again 
     tellTheTaskQueueToStartCalculating() 
    } 
} 

我的问题是:这是否保证绝不会有被标记干净的新成绩已经加入后,计算成绩?

关注的具体领域:

  • 将在existingGrades始终在第一个片段的新IndividualScore之前坚持着,周围的危险区?
  • 是否有可能另一个线程将在危险区域启动任务队列代码,以便在真正进入新的IndividualScore之前,那些现有的Graded可能会被标记为干净?如果是这样,我怎样才能确保不会发生(所有成绩的交易都没有)?
  • 是否persistenceManager.flush()足以保存部分完成的计算,即使pm未关闭?

这一定是常见的问题。我会很感激任何教程链接,尤其是那些appengine。感谢您阅读这么多!

+0

我有一个产品做心理测试。用户通过测试输入原始数据,并在构建报告时进行复杂计算循环。尽管成千上万的方程与您所做的类型非常相似,但即使在同时支持多个用户的情况下,也几乎没有任何滞后。如果这只是你使用这个,你可能会有点矫枉过正.... – bpeterson76 2011-02-24 23:09:57

+0

感谢您的观点。我希望每次计算都需要获取大约1000个实体,包括分数和其他一些东西,然后平均返回10个实体。可能这可能会在单个请求中运行,但我需要永远不会将数据保留在不一致状态的内容。它正在处理我开始担心时间不够的错误 - 我偶尔会在其他一些请求中耗尽时间。 – 2011-02-24 23:14:35

+0

嗯...但是,我想我可以做更多的计算时,实际要求的成绩,如果失败返回相同的错误信息,否则我... – 2011-02-24 23:33:45

回答

2

如果您担心竞争条件,请勿使用布尔型脏标志 - 而应使用一对时间戳。如果您想标记脏记录,请更新“脏”时间戳。

当你开始计算等级时,记下'脏'时间戳是什么。

当您完成计算分数时,请将'clean'时间戳更新为与您在开始时读取的'dirty'时间戳的值相等,表示您已将该等级与新数据同步时间戳。

“脏”时间戳大于“干净”时间戳的任何记录都是脏的。任何两场比赛都是干净的记录。简单而有效。如果另一个请求添加了可能会影响给定等级的新数据,而您的任务队列任务已经处于计算等级的中间,则“脏”时间戳将与更新的“干净”时间戳不匹配,因此任务队列会考虑该记录仍然肮脏,并再次处理。

+0

不幸的是,我不认为我可以做一个查询通过比较时间戳记录 - 无法在appengine数据存储上说'select * from dirty where clean> clean'。如果我正在重写一个“差异”字段,我似乎可能会以危险的方式覆盖,我可能会用减法来做一些事情。 – 2011-02-24 23:23:56

+0

我可以制作一个特殊的'RecalculateRequest'实体,带有时间戳,用于我想标记脏的每个年级。然后我可以搜索'RecalculateRequest's,在'Grade'实体中设置一个'cleanAsOf'时间戳,并且只有当时间戳记追上时才删除'RecalculateRequest' ... – 2011-02-24 23:59:44

+0

是的,这可以工作(或者只是允许多个重新计算任何给定等级的请求,并且总是删除比cleanAsOf时间戳更早的那些)。 – Amber 2011-02-25 01:22:28