2012-04-15 60 views
6

請幫我找到我的誤解。應用程序引擎,交易和冪等性

我正在App Engine上撰寫RPG。玩家採取的某些行動消耗某些屬性。如果統計數字達到零,玩家可以不採取任何行動。然而,我開始擔心作弊的球員,如果一名球員非常快速地發出兩個動作,那麼彼此相鄰呢?如果減少屬性的代碼不在交易中,那麼玩家有可能執行兩次動作。所以,我應該包裝在交易中減少stat的代碼,對吧?到現在爲止還挺好。

在GAE的Python,雖然,我們有這樣的documentation

注意:如果提交事務,當你的應用程序接收到一個例外,它並不總是 意味着該交易失敗。如果事務已提交併且最終將成功應用 ,則可能會收到Timeout,TransactionFailedError或InternalError異常。只要有可能,使您的數據存儲交易冪等 ,如果您重複交易,最終結果將是相同的。

哎呦。這意味着,我是跑的功能如下:


def decrement(player_key, value=5): 
    player = Player.get(player_key) 
    player.stat -= value 
    player.put() 

嗯,這不是要去工作,因爲事情是不是冪,對不對?如果我在它周圍放置一個重試循環(是否需要使用Python?我讀過,我不需要這麼做,但是我無法在文檔中找到它),它可能會增加兩次值,對?由於我的代碼可以捕獲異常,但數據存儲區仍然提交數據......呵?我該如何解決?這是我需要distributed transactions的情況嗎?我真的嗎?

+1

嗯,是的,這是一個很好的觀點......但在我用一堆難以診斷的,重現錯誤我想知道我應該在這裏做什麼模式。 – 2012-04-15 04:55:10

+0

您的模式正處於正確的軌道上,但GAE有很多令人沮喪的細微差別,使得這種難以精確的手術操作成爲可能。根據我對GAE的經驗,有時候這是值得的,有時不值得。 – 2012-04-15 05:07:56

+1

@TravisWebb不同意。交易安全性不是「過早優化」,交易衝突也不是特別不可能的。 – 2012-04-15 10:24:48

回答

13

首先,尼克的回答是不正確的。 DHayes的交易不是冪等的,所以如果它運行多次(即第一次嘗試被認爲是失敗的時候重試),那麼這個值將被多次遞減。尼克說,「數據存儲檢查實體是否被修改,因爲它們被提取」,但是這並不能阻止這個問題,因爲兩個事務有單獨的提取,第二次提取是在第一個事務完成後。

要解決此問題,您可以通過創建「事務密鑰」並將該密鑰記錄在新實體中作爲事務的一部分來使交易冪等。第二個事務可以檢查該事務密鑰,如果找到,將不會執行任何操作。一旦您完成交易或您放棄重試,交易密鑰可以被刪除。

我想知道AppEngine(百萬分之一,還是十億分之一)的「極其罕見」意味着什麼?但我的建議是,冪等交易對財務事務是必需的,但不是遊戲成績,甚至不是「生命」;-)

1

您不應該嘗試在Memcache中存儲這種信息,這比Datastore快得多(如果此屬性在您的應用程序中經常使用,您將需要這些信息)。 Memcache爲您提供了一個很好的功能:decr其中:

原子地遞減鍵的值。在內部,該值是一個無符號的64位整數。 Memcache不檢查64位溢出。該值如果太大,則將環繞。

搜索decrhere。然後,您應該每x秒或在滿足特定條件時使用任務將此密鑰中的值保存到數據存儲區。

+0

感謝您的回答,但我認爲不會奏效。如果這是我能夠承受的模糊值的某種全球性計數器,那將是完美的,但我可以想象如果由於memcache驅逐造成的值變得混亂,玩家會很生氣。 – 2012-04-15 05:29:13

+0

請注意,Memcache decr()函數還會「封閉在零之下遞減到零」[[[1 \]](https://cloud.google.com/appengine/docs/python/refdocs/google.appengine.api)。內存緩存#google.appengine.api.memcache.Client.decr)。 – Lee 2015-09-10 13:03:27

1

如果您仔細考慮您所描述的內容,可能並不是一個問題。想想這樣:

你的球員有一個統計點左。然後他會立即惡意發送兩個動作(A1和A2),每個動作需要消耗該點。 A1和A2都是事務性的。

以下是可能發生的情況:

A1成功。然後A2將中止。都好。

A1合法失敗(不更改數據)。重試計劃。 A2然後嘗試,成功。當A1再次嘗試時,它會中止。

A1成功但報告錯誤。重試計劃。下一次A1或A2嘗試時,它們會中止。

爲了這個工作,你需要跟蹤A1和A2是否已經完成 - 也許給他們一個任務UUID並存儲完成任務列表?甚至只是使用任務隊列。

+0

謝謝,但我不知道這是行不通的,因爲存儲ID本身可能會遇到這個問題......但我認爲尼克的答案涵蓋了我的情況。 – 2012-04-15 16:37:30

4

編輯:這是不正確的 - 請參閱評論。

你的代碼沒問題。文檔所指的冪等性是關於副作用的。正如文檔解釋的那樣,您的交易功能可能會運行多次;在這種情況下,如果功能有任何副作用,它們將被多次應用。由於你的交易功能不這樣做,它會沒事的。

與關於冪等性有問題的功能的一個例子是這樣的:

def do_something(self): 
    def _tx(): 
    # Do something transactional 
    self.counter += 1 
    db.run_in_transaction(_tx) 

在這種情況下,self.counter可被1遞增,或可能大於1。這可以通過這樣做可避免交易外的副作用:

def do_something(self): 
    def _tx(): 
    # Do something transactional 
    return 1 
    self.counter += db.run_in_transaction(_tx) 
+0

謝謝,尼克。你說我在一個事務中做的任何數據存儲操作只會發生一次,即使我的代碼得到一個異常並重試它?如果我的'decrement'事務由於重試而被調用兩次,它只會減少一次?在ndb-land中,是因爲我的事務被分配了一些ID,數據存儲庫知道它已經提交了? – 2012-04-15 16:36:13

+0

(以及「我的代碼...重試」我的意思是「ndb爲我重試」) – 2012-04-15 16:42:19

+1

@ D.Hayes他們會發生多次,但只有其中一人會被提交回數據存儲。數據存儲使用樂觀併發性,所以當它試圖提交事務時,數據存儲會檢查實體是否在被提取後被修改,並且只有在事務沒有被執行時才接受。 – 2012-04-15 23:59:59

相關問題