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