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

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

我正在 Google 应用引擎上制作成绩簿。我跟踪每个评分期每个学生的成绩。评分期可以重叠。由于我可能一次显示数百个这样的成绩,因此我在服务器上预先计算了成绩。因此,对于任何一名学生,我可能会计算出许多成绩 - 每个评分周期都有一个成绩。

现在,老师输入测验的新分数。该分数可能会影响许多计算的成绩,因为它可能属于许多评分期。我需要重新计算所有受影响的成绩。这可能需要很长时间,因为对于每个评分周期,我都需要获取所有相关分数并对这些分数执行复杂的例程。我认为 30 秒是不够的 - 特别是如果数据存储今天感觉很慢的话。此外,失败不是一种选择。某些成绩更新而另一些成绩却悄然过时,这是 Not Acceptable 。

所以我心里想,这是学习任务队列的美好时光!

我不是数据库结构或其他方面的专家,但这里是我想做的事情的概述:

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 之前,那些现有的成绩可能会再次被标记为干净?如果是这样,我如何确保不会发生这种情况(所有年级的交易都已结束)?
  • 即使 pm 未关闭,persistenceManager.flush() 是否足以保存部分完成的计算?

这一定是一个常见的问题。我很感激任何教程的链接,特别是那些关于 appengine 的链接。感谢您阅读这么多!

最佳答案

如果您担心竞争条件,请不要使用 boolean 脏标志 - 相反,请使用一对时间戳。当您想要将记录标记为脏时,请更新“脏”时间戳。

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

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

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

关于java - 找出缺陷!使用任务队列可靠地执行长任务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5111630/

相关文章:

java - 如何在java中获取当前时间(以纳秒为单位)?

java - 如何在 Swing GUI 中实现延迟?

google-app-engine - 哪些后端正在消耗Google Container Engine Kubernetes配额

java - 在 Tomcat 中使用 Spring + Hibernate 事务缓存?

sql-server - 在单个更新语句上使用事务

java - 方法完成后立即提交事务

java - 如何检测光标是否位于按钮的文本字段上,然后将其移动到下一个?

java - redis 批量增量更新

eclipse - GWT 重置清除 *.cache.html 文件

java - 应用引擎 : Having a web module and a endpoint module