考虑一个允许用户评论歌曲的 GAE (python) 应用程序。预计用户数量为1,000,000+。预计歌曲数量为 5,000 首。

该应用程序必须能够:

  • 给出用户评论的歌曲数量
  • 给出对歌曲发表评论的用户数量

计数器管理必须是事务性的,以便它们始终反映底层数据。

看来 GAE 应用程序必须始终保持这些类型的计数计算,因为在请求时查询它们效率很低。

我的数据模型

class Song(BaseModel):
    name = db.StringProperty()
    # Number of users commenting on the song
    user_count = db.IntegerProperty('user count', default=0, required=True)
    date_added = db.DateTimeProperty('date added', False, True)
    date_updated = db.DateTimeProperty('date updated', True, False)

class User(BaseModel):
    email = db.StringProperty()
    # Number of songs commented on by the user
    song_count = db.IntegerProperty('song count', default=0, required=True)
    date_added = db.DateTimeProperty('date added', False, True)
    date_updated = db.DateTimeProperty('date updated', True, False)

class SongUser(BaseModel):
    # Will be child of User
    song = db.ReferenceProperty(Song, required=True, collection_name='songs')
    comment = db.StringProperty('comment', required=True)
    date_added = db.DateTimeProperty('date added', False, True)
    date_updated = db.DateTimeProperty('date updated', True, False)

代码
这以事务方式处理用户的歌曲计数,但不处理歌曲的用户计数。

s = Song(name='Hey Jude')
s.put()

u = User(email='me@example.com')
u.put()

def add_mapping(song_key, song_comment, user_key):
    u = User.get(user_key)

    su = SongUser(parent=u, song=song_key, song_comment=song_comment, user=u);
    u.song_count += 1

    u.put()
    su.put()

# Transactionally add mapping and increase user's song count
db.run_in_transaction(add_mapping, s.key(), 'Awesome', u.key())

# Increase song's user count (non-transactional)
s.user_count += 1
s.put()

问题是: 我如何以事务方式管理两个柜台?

根据我的理解,这是不可能的,因为 User、Song 和 SongUser 必须是同一个的一部分 实体组. 。它们不能位于一个实体组中,因为这样我的所有数据都将位于一组中,并且无法由用户分发。

有帮助吗?

解决方案

您确实不必担心处理用户在交易中评论的歌曲数量,因为用户似乎不太可能一次评论多于一首歌曲,对吗?

现在,肯定会有许多用户同时评论同一首歌曲,因此您必须担心确保数据不会因竞争条件而变得无效。

但是,如果您在“歌曲”实体中保留对歌曲发表评论的用户数量,并通过事务锁定该实体,那么您将对该实体产生非常高的争用,并且数据存储超时将使您的应用程序出现问题。很多问题。

这个问题的答案是 分片计数器.

为了确保您可以创建新的 SongUser 实体并更新相关歌曲的分片计数器,您应该考虑让 SongUser 实体将相关歌曲作为父实体。这会将它们放在同一个实体组中,您可以在同一事务中创建 SongUser 并更新分片计数器。SongUser 与创建它的用户的关系可以保存在 ReferenceProperty 中。

关于您对两个更新(事务更新和用户更新)并非都成功的担忧,这始终是一种可能性,但考虑到任一更新都可能失败,您将需要进行适当的异常处理以确保两者都成功。这是很重要的一点:不保证事务中更新成功。如果事务因任何原因无法完成,您可能会收到 TransactionfailedError 异常。

因此,如果您的事务完成后没有引发异常,请在事务中运行对用户的更新。如果发生某些错误,这将使您自动重试更新用户。除非用户实体上可能存在我不理解的争用,否则它最终不会成功的可能性是 极其小. 。如果这是一个不可接受的风险,那么我认为 AppEngine 没有为您提供解决此问题的完美解决方案。

首先问自己: 如果某人评论的歌曲数量少了一首,真的那么糟糕吗?这与更新银行账户余额或完成股票销售一样重要吗?

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top