如何依赖Google App Engine中的一致数据?

时间:2013-07-29 12:43:40

标签: database google-app-engine

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

客户端发布得分并从服务器获取高分列表,所有内容似乎都运行良好(我们已经测试了一个月没有任何问题,代码非常简单,只需要一个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 ))

它一直运行良好,但出于某种原因,几个小时后它返回旧数据(如天数据)。

2 个答案:

答案 0 :(得分:0)

如果您通过查询检索分数,那么您遇到了eventual consistency od HRD数据存储区。这是因为索引(用于查询)是异步构建的(=写入操作在构建索引之前返回)。

答案 1 :(得分:0)

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