找到缺陷!使用任务队列可靠地执行长任务

时间:2011-02-24 23:00:43

标签: java google-app-engine transactions google-cloud-datastore distributed-transactions

我正在谷歌应用引擎上制作成绩簿。我会跟踪每个学生每个评分期的成绩。评分期可以重叠。由于我可以一次显示数百个等级,因此我会预先计算服务器上的等级。因此,对于任何一个学生,我可能有很多计算成绩 - 每个评分期一个。

现在,老师从测验中输入一个新分数。该分数可能影响许多计算的分数,因为它可能属于许多分级期。我需要重新计算所有受影响的成绩。这可能需要很长时间,因为对于每个评分时段,我需要获取所有相关分数并在这些分数上执行复杂的例程。我认为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之前,可以再次将那些现有的Grara标记为干净?如果是这样,我怎样才能确保不会发生这种情况(所有等级的交易都已经结束)?
  • persistenceManager.flush()是否足以保存部分完成的计算,即使pm未关闭?

这一定是一个常见的问题。我很欣赏教程的任何链接,特别是那些用于appengine的教程。非常感谢你阅读!

1 个答案:

答案 0 :(得分:2)

如果您担心竞争条件,请不要使用布尔脏标志 - 而是使用一对时间戳。如果要将记录标记为脏,请更新“脏”时间戳。

当您开始计算成绩时,请记下“脏”时间戳的内容。

计算完成绩后,将“干净”时间戳更新为等于您开始时读取的“脏”时间戳的值,表示您已将该等级与该时间戳的新数据同步

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

相关问题