0

我们正在制作一款以应用引擎为后端的 iOS 赛车游戏。然而在 6 月下旬发生了一些奇怪的事情(我刚刚从假期回来,因此我现在发布)。

客户端发布分数并从服务器获取高分列表,一切似乎都运行良好(我们已经对其进行了一个月的测试,没有任何问题,代码非常简单,只需要一个 put/get)。但在 6 月下旬,几个小时后,旧数据被返回给了客户。它发生了一段时间,但随后数据自行修复。

然而,这仍然给我们带来了麻烦,因为在提交分数时,我们检查服务器每个玩家只有一个高分,但是这个应用程序引擎错误(?)导致服务器有一些玩家的多个分数。

所以发生的事情是:玩家 A 提交分数,玩家 B 提交分数,数据恢复到只有玩家 A 存在时,玩家 B 提交新分数(它被存储为服务器看不到玩家 B),服务器修复数据问题并现在我们需要玩家 B。

如果您希望能够依赖应用程序引擎的后端,您应该怎么做?这可能会破坏交易。

更具体(根据评论中的要求)

我只是简单地说明我们正在做的事情。但基本上是一样的。所以我们不仅存储高分,还存储玩家的幽灵数据。这是代码(但删除了一些现在不相关的额外字段)。

这是模型(去掉了一些不重要的字段):

class Highscore(db.Model):  
    player = db.StringProperty()  
    track = db.IntegerProperty()  
    track_time = db.FloatProperty()  
    ghost_data = blobstore.BlobReferenceProperty()  

对于存储,我首先执行以下操作:

class GhostPrepareHandler(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'application/json'
        self.response.out.write(json.dumps({ 'add_highscore_url' : blobstore.create_upload_url('/api/highscore') }))

接着

class HighscoreUploadHandler(blobstore_handlers.BlobstoreUploadHandler):
    def post(self):
       # Check any previous highscore
       track = self.request.get('track')
       player = self.request.get('player')

       hs = Highscore.all().filter('track =', track).filter('player =', player).get()

       # Check if a previous ghost exists with a worse time, if so remove it,
       # else if previous time is better, do not store this highscore
       if hs is not None:
            if hs.track_time < float( self.request.get('track_time') ):
                self.response.headers['Content-Type'] = 'application/json'
                self.response.out.write(json.dumps({ 'success' : True }))
                return

            hs.delete()

        # Store highscore
        hs = Highscore()
        hs.player = self.request.get('player')
        hs.track = int( self.request.get('track') )
        hs.track_time = float( self.request.get('track_time') )

        upload = self.get_uploads()[0]
        hs.ghost_data = upload.key()

        # Store
        hs.put()

        self.response.headers['Content-Type'] = 'application/json'
        self.response.out.write(json.dumps({ 'success' : True }))

然后我们得到了阅读部分

def get(self):
    player = self.request.get('player')
    track = self.request.get('track')

    highscores = HighScore.all().filter('track =', track).order('track_time').fetch(limit=4)

    highscores_json = []

    hs_count = 0
    for hs in highscores:
        # Filter out player or last ghost
        if hs.player == player or hs_count > 2:
            continue

        hs_obj = {
            'player' : hs.player,
            'track_time' : hs.track_time,
            'ghost_data_url' : 'http://' + host + '/api/highscore/download?ghost_key=' + str( hs.ghost_data.key() )
        }

        highscores_json.append( hs_obj )
        hs_count += 1

    self.response.headers['Content-Type'] = 'application/json'
    self.response.out.write(json.dumps( highscores_json ))

它一直运行良好,但由于某种原因,它在几个小时内返回了旧数据(比如几天前的数据)。

4

2 回答 2

0

您是否正在使用查询来获取一个玩家的更新记录?抱歉,只是有时间快速阅读,但似乎是这种情况。我认为可以肯定地说您永远不应该对此类更新使用查询。您将遇到最终一致性问题,并且每个 get() 都比必要的效率低(延迟)和高成本(读/写操作)。为什么不使用根据他/她的特定信息形成的唯一 id 来记录每个玩家的高分记录?然后 get_by_id() 和 put()。您可以保证一致性,唯一的限制是每条记录每秒写入一次的限制,在这种情况下这不应该是一个问题。此外,我会非常谨慎地依赖 delete() 像你一样。如果您使用 get_by_id() 过程,您只需使用每个 put() 用新的 hs 覆盖旧的 hs。再次,

于 2013-07-30T15:33:15.230 回答
0

如果您通过查询检索分数,那么您遇到了HRD 数据存储的最终一致性。发生这种情况是因为索引(用于查询)是异步构建的(= 写入操作在索引构建之前返回)。

于 2013-07-29T15:03:06.827 回答