【问题标题】:atomic counter increment in App-EngineApp-Engine 中的原子计数器增量
【发布时间】:2026-02-02 13:35:02
【问题描述】:

我对事务还是有点困惑,不管是使用 DatastoreService 还是 Objectify。 (是的,我读过What is the correct way to atomically increment a counter in App Engine?)。我需要原子地增加一个计数器。我怎么做?应用引擎文档中的示例在其 finally 块中有一个回滚。但我不想要回滚,我希望系统继续尝试。另一方面,objectify 文档说它的事务模型与低级 api 的不同。所以我正在编写这两个代码,我只需要帮助纠正它们或确认它们。

DatastoreService 版本

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService()
Transaction txn = datastore.beginTransaction();
try {
    Key commentKey = KeyFactory.createKey(“Comment”, id);
    Entity comment = datastore.get(commentKey);
    int views = (Integer)comment.getProperty(“views”);
    views++;//increment step
    comment.setProperty(“views”, views);

    datastore.put(comment);

    txn.commit();
} finally {
    if (txn.isActive()) {
        txn.rollback();
    }
}

对象化版本

ofy().transact(new VoidWork() {

      @Override
      public void vrun() {
        Comment comment = ofy().load().type(Comment.class).id(commentId).now();
        long views = 1+ comment.getViews();
        comment.setViews(views);
        ofy().save().entity(comment).now();
      }
    });

重要的一点是,我希望系统继续无限尝试。当然,我希望客户端调用在所有这些异步发生时返回。感谢您的帮助

【问题讨论】:

  • 如果您将“永远尝试”作为其复杂的需要来自任务队列的消息传递包含在内,则问题的范围太广
  • 永远,我的意思是我真的希望计数器非常准确,而在 DatastoreService 版本中有回滚,我不确定它尝试了多少次。
  • appengine 前端有 1 分钟的限制,所以此时它很复杂,因为您需要处理很多情况
  • 你应该看看如何与具有强大原子指令的 memcached 结合使用。但过于宽泛,无法放在这里。其他答案提到了这一点。
  • @ZigMandel 我的两个答案不正确吗?他们缺少什么?我对objectify特别感兴趣,尽力而为是否正确?

标签: google-app-engine google-cloud-endpoints google-cloud-datastore objectify


【解决方案1】:

如果发生并发冲突,Objectify 版本将重试。您可以修改 DatastoreService 版本以在 ConcurrentModificationException 上循环,您将有效地获得相同的逻辑。

但是,这并不能为您提供极其准确的计数器(尽管对于大多数用途而言,它通常足够接近)。但是,您不会希望进行这样的银行交易。

问题(存在于所有分布式事务处理系统中)是事务期间可能出现问题,例如抛出 DatastoreException。这会使您的计数器处于不确定状态 - 提交是否成功?你不知道。

如果您想要准确(并且您确实说非常准确),您需要执行一些变体:

  1. 在开始交易之前使用唯一键创建一个 txn 记录
  2. 开始交易
  3. 检查记录是否存在;如果没有,你已经完成了
  4. 删除记录,增加计数器,然后提交
  5. 如果出错,从 2 开始重复直到成功

您需要某种查询来清理完全失败的交易的剩余 txn 记录。

一种变体是在事务之前只创建 id 并在事务内部创建 txn 记录,使用记录的正存在表示成功。如果您长期保留该记录,它基本上就是交易历史记录。

这种级别的事务确定性会花费额外的写入操作并增加明显的延迟,因此您可能只想在真正需要准确性时使用它。

【讨论】:

  • 哇。这听起来真的很复杂。我拥有的 objectify sn-p 怎么样,它是否能达到某种程度的准确度?
  • @KatedralPillon 你想要(或需要)它有多精确?您是否在经营一家证券交易所或显示大量页面浏览量?您的事务是幂等的(应该是),除非您有非常激烈的争用,否则应该“几乎总是”在第一次运行或前几次重试时成功。
  • 我正在计算选票。我的应用程序有一个投票系统,允许用户通过投票提供反馈。问题是我不想使用分片,所以事务是我的重点。 (实际上我还不够专业,无法理解交易和分片在 value 方面的区别)。
  • 计票(假设您想要合理的准确度)实际上是更容易做的事情之一,因为您通常具有“一人一票”的不变量。所以上面提到的“txn 记录”实际上是一个永久存在的 Vote 记录,以 {person, topic} 为键。请注意,如果您想要吞吐量,则需要对计数器进行分片或对投票进行批量求和,并使用延迟的总计。