7

私の誤解を見つけるのを手伝ってください。

AppEngineでRPGを書いています。プレイヤーが行う特定のアクションは、特定の統計を消費します。統計がゼロに達すると、プレイヤーはそれ以上アクションを実行できなくなります。しかし、私はプレイヤーをだますことについて心配し始めました-プレイヤーが2つのアクションを非常に速く、隣り合って送信した場合はどうなりますか?統計をデクリメントするコードがトランザクションにない場合、プレーヤーはアクションを2回実行する可能性があります。だから、トランザクションで統計を減らすコードをラップする必要がありますよね?ここまでは順調ですね。

ただし、GAE Pythonでは、ドキュメントに次のように記載されています。

:トランザクションの送信時にアプリが例外を受け取った場合、それは必ずしもトランザクションが失敗したことを意味するわけではありません。トランザクションがコミットされ、最終的に正常に適用される場合は、Timeout、TransactionFailedError、またはInternalError例外を受け取る可能性があります。可能な限り、データストアトランザクションをべき等にして、トランザクションを繰り返しても最終結果が同じになるようにします。

おっと。これは、私が実行していた関数が次のようになっていることを意味します。


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

まあ、べき等ではないので、それはうまくいきませんよね?再試行ループをその周りに配置すると(Pythonで実行する必要がありますか?SOで実行する必要がないことを読みました...しかし、ドキュメントで見つけることができません)、値が2回インクリメントされる可能性があります。右?私のコードは例外をキャッチできますが、データストアはまだデータをコミットしているので...え?これを修正するにはどうすればよいですか?これは分散トランザクションが必要な場合ですか?私は本当にですか?

4

4 に答える 4

13

まず、ニックの答えは正しくありません。DHayesのトランザクションはべき等ではないため、複数回実行された場合(つまり、最初の試行が失敗したと考えられたときの再試行、失敗しなかったときの再試行)、値は複数回デクリメントされます。Nickは、「データストアは、エンティティがフェッチされてから変更されているかどうかをチェックします」と述べていますが、2つのトランザクションには別々のフェッチがあり、2番目のフェッチは最初のトランザクションが完了した後であるため、問題を防ぐことはできません。

この問題を解決するには、「トランザクションキー」を作成し、そのキーをトランザクションの一部として新しいエンティティに記録することで、トランザクションをべき等にすることができます。2番目のトランザクションはそのトランザクションキーをチェックでき、見つかった場合は何もしません。トランザクションが完了したことを確認した後、または再試行を断念した場合は、トランザクションキーを削除できます。

AppEngineにとって「非常にまれ」とはどういう意味か知りたいのですが(100万分の1、それとも10億分の1?)、財務上の問題にはべき等のトランザクションが必要ですが、ゲームのスコア、または「生きている」;-)

于 2012-11-15T05:27:43.547 に答える
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)
于 2012-04-15T10:28:50.043 に答える
1

この種の情報をMemcacheに保存しようとしないでください。これは、データストアよりもはるかに高速です(この統計がアプリケーションで頻繁に使用される場合に必要になるものです)。Memcacheはあなたに素晴らしい関数を提供します:decrこれは:

キーの値を原子的にデクリメントします。内部的には、値は符号なし64ビット整数です。Memcacheは64ビットオーバーフローをチェックしません。値が大きすぎると、折り返されます。

ここで検索しdecr ます。次に、タスクを使用して、x秒ごと、または特定の条件が満たされたときに、このキーの値をデータストアに保存する必要があります。

于 2012-04-15T05:19:22.660 に答える
1

あなたが何を説明しているのかを注意深く考えれば、それは実際には問題ではないかもしれません。このように考えてください:

プレイヤーのステータスポイントが1つ残っています。次に、悪意を持って2つのアクション(A1とA2)を瞬時に送信し、それぞれがそのポイントを消費する必要があります。A1とA2はどちらもトランザクションです。

発生する可能性のあることは次のとおりです。

A1は成功します。その後、A2は中止されます。すべて良い。

A1は合法的に失敗します(データを変更せずに)。再試行がスケジュールされています。その後、A2は試行し、成功します。A1が再試行すると、中止されます。

A1は成功しますが、エラーを報告します。再試行がスケジュールされています。次にA1またはA2のいずれかが試行すると、それらは中止されます。

これが機能するためには、A1とA2が完了したかどうかを追跡する必要があります-おそらくそれらにタスクUUIDを与え、完了したタスクのリストを保存しますか?または、タスクキューを使用することもできます。

于 2012-04-15T05:41:34.797 に答える