【问题标题】:App Engine JDO Transaction on multiple many-to-one多个多对一上的 App Engine JDO 事务
【发布时间】:2010-09-17 21:31:47
【问题描述】:

我有一个简单的域模型如下

驱动程序 - 键(字符串)、运行计数、唯一轨道计数

Track - key(string)、run-count、unique-driver-count、best-time

运行 - 键(?)、驱动键、轨道键、时间、布尔驱动更新、布尔轨道更新

我需要能够在同一事务中更新 Run 和 Driver;以及同一事务中的运行和跟踪(显然是为了确保我不会两次更新统计信息,或者错过增量计数器)

现在我已经尝试分配为运行键,一个由驱动键/轨道键/运行键(字符串)组成的键

这将让我在一个事务中更新 Run 实体和 Driver 实体。

但如果我尝试同时更新 Run 和 Track 实体,它会抱怨它不能跨多个组进行事务处理。它说它在交易中同时拥有 Driver 和 Truck 并且不能在两者上操作......

tx.begin(); 运行 = pmf.getObjectById(Run.class, runKey); track = pmf.getObjectById(Track.class, trackKey); //这是失败的地方; 增量计数器(); updateUpdatedFlags(); tx.commit();

奇怪的是,当我做类似的事情来更新 Run and Driver 时,它工作正常。

关于如何映射我的域模型以实现相同功能的任何建议?

【问题讨论】:

  • 我已准备好处理争用,我不需要立即更新统计字段,这就是为什么我在运行本身上有“更新”标志......因为我可以在系统运行时运行更新不那么忙...我不能牺牲的是精度...
  • 作为更多的上下文,我通常会有很多赛道和很多车手;驱动程序实体不会有太多争用,它一次只能由一个用户访问,但轨道实体一次会被许多用户读取,并针对它提交运行。
  • 更新——我终于完成了这个开发。结果在 tRacePerfect.com 。 “赛道”是“拼图”,“车手”是“玩家”。如您所见,维护了各种统计信息。我正在使用分片和任务技术的组合。

标签: java google-app-engine google-cloud-datastore jdo


【解决方案1】:

使用 Google App Engine,all of the datastore operations must be on entities in the same entity group。这是因为您的数据通常存储在多个表中,而 Google App Engine 无法跨多个表进行事务处理。

拥有一对一和一对多关系are automatically in the same entity group的实体。因此,如果一个实体包含对另一个实体或实体集合的引用,您可以在同一事务中读取或写入两者。对于没有所有者关系的实体,您可以创建具有显式实体组父级的实体。

您可以将所有对象放在同一个实体组中,但如果太多用户同时尝试修改实体组中的对象,您可能会遇到一些争用。如果每个对象都在其自己的实体组中,则您无法进行任何有意义的事务。你想在这两者之间做点什么。

一种解决方案是在同一个实体组中进行跟踪和运行。您可以通过让 Track 包含一个运行列表来做到这一点(如果您这样做,那么 Track 可能不需要运行计数、唯一驱动程序计数和最佳时间;可以在需要时计算它们)。如果您不希望 Track 具有运行列表,则可以使用 unowned one-to-many relationship 并将运行的实体组父级指定为其 Track(请参阅 this page 上的“使用实体组创建实体”)。无论哪种方式,如果 Run 与其轨道位于同一实体组中,您可以执行涉及 Run 及其部分/全部 Track 的事务。

对于许多大型系统,不是使用事务来实现一致性,而是通过进行idempotent 的操作来完成更改。例如,如果 Driver 和 Run 不在同一个实体组中,您可以通过首先执行查询以获取过去某个日期之前所有运行的计数来更新 Driver 的运行计数,然后在事务中,使用新计数和上次计算的日期更新驱动程序。

请记住,使用日期时机器可能会有某种时钟漂移,这就是我建议使用过去日期的原因。

【讨论】:

  • 感谢您的反馈。我确实理解实体/组/交易约束..我不太希望让赛道(和司机)拥有所有跑步的完整集合......因为我希望特别是赛道有很多跑步,并且每当我加载曲目时,我都会经常需要这些计数统计信息,但我不想加载所有运行只是为了一直计算统计信息......当然必须有一个简单的方法,即使我牺牲了一些注意我可以处理....我如何强制“所有对象都在同一个实体组中”?
  • 统计数据一定要实时更新吗?如果没有,我可以建议您离线更新它们的方法。要查看如何为实体选择实体组父级,请单击我帖子中的链接之一并查看“使用实体组创建实体”
  • Namshub,再次感谢您的反馈。我知道我必须找到介于两者之间的方法。统计数据不必绝对实时。但理想情况下。这就是为什么我想我会尝试实时更新它们,如果它失败了,我会稍后再试(只要我可以存储它失败的事实)。我遇到的另一个并发症是 Run 本质上有两个独立的父级(Track 和 Driver)......这实际上是并发症的根本原因。因为据我所知,我不能让它有两个父母。
  • 一个实体只能有一个实体组父级。听起来幂等操作可以满足您的需求。
  • Namshub,再次感谢您的贡献。我提出的答案无需幂等操作即可解决我的问题。但这显然是对我的真实模型的简单化视图。所以当我的问题变得更复杂时,我肯定会考虑基于时间的幂等操作。再次感谢。
【解决方案2】:

我想我找到了一个横向但仍然干净的解决方案,在我的领域模型中仍然有意义。

领域模型略有变化如下:

驱动程序 - 键(字符串 ID),驱动程序统计信息 - 例如。 id="迈克尔",运行=17

Track - key(string-id), track-stats - ex。 id="蒙扎", bestTime=157

RunData - 键(字符串 ID),统计数据 - 例如。 id="Michael-Monza-20101010", 时间=148

TrackRun - 键(Track/string-id),track-stats-updated - 例如。 id="Monza/Michael-Monza-20101010", track-stats-updated=false

DriverRun - 键(驱动程序/字符串 ID),驱动程序统计更新 - 例如。 id="Michael/Michael-Monza-20101010", driver-stats-updated=true

我现在可以立即或在我自己的时间使用 Run 中的统计数据自动(即精确)更新 Track 的统计数据。 (与驱动程序/运行统计数据相同)。

所以基本上我必须以非常规的关系方式扩展我对问题建模的方式。你怎么看?

【讨论】:

  • 我个人不喜欢为了交易而复制数据的想法。理想情况下,您的数据模型应该映射到您的域模型。如果您不需要实时统计数据,您可以使用幂等操作离线更新它们。
  • 嗯,我想这就是你看到模型的方式......我实际上可以提取两个运行实体类型......我可以有一个 TrackRun 和一个 DriverRun。这些只是存储关联和“parentUpdated”标志。然后可以将 RunData 本身存储在一个单独的实体中,因此没有重复。 RunData 只会被创建并且永远不会被更新。这些实体永远不能被创建两次,因为它们的 id 是由用户定义的。这仍然使它对真实的商业模式有效。我确实需要尽可能实时的统计数据(但对失败有弹性)。更新我的答案...
  • TrackRun写入成功,DriverRun写入失败会怎样?
  • 当用户第一次创建运行时,在这种情况下可能会自动发生几次重试。如果仍然没有成功,则会通知用户,并且他可以根据需要再试多次。 TrackRun(或 DriverRun 或 DriverData)没有被创建两次的风险,因为它的隐含字符串 ID(如果不存在则创建)。这是创建。
  • 那么,stat-update 阶段可以随时发生。系统将在创建后立即尝试。但是,如果任一更新失败,用户仍然会被告知成功......并且统计信息将在稍后系统不那么繁忙/再次启动时自动更新。
【解决方案3】:

意识到这已经晚了,但是..

你见过这种银行账户转账的方法吗? http://blog.notdot.net/2009/9/Distributed-Transactions-on-App-Engine

在我看来,您可以通过将增量计数器分成两个步骤作为 IncrementEntity 并处理它来做类似的事情,如果事务失败等稍后再拾取碎片。

来自博客:

  1. 在交易中,扣除所需的 来自支付账户的金额,以及 创建一个 Transfer 子实体到 记录这一点,指定接收 “目标”字段中的帐户,以及 将“其他”字段留空 现在。
  2. 在第二个事务中,添加 收款所需金额 帐户,并创建一个转移子 实体记录这一点,指定 “目标”字段中的付款帐户, 以及创建于 在“其他”字段中的第 1 步。
  3. 最后, 更新创建于 第 1 步,将“其他”字段设置为 我们在第 2 步中创建的转移。

博客中有 Python 代码示例,但应该很容易适应

【讨论】:

  • 谢谢史蒂文。是的,这种方法实际上也类似于我解决问题的方式。很好的阅读和解释。
【解决方案4】:

关于这个主题有一个有趣的 google io 会话http://www.google.com/events/io/2010/sessions/high-throughput-data-pipelines-appengine.html

我猜你可以更新 Run stats 然后触发两个任务来分别更新 Driver 和 Track。

【讨论】:

  • 嗨,吉古兹。感谢您的反馈意见。是的,这与我最终实现的非常相似。
猜你喜欢
  • 2012-06-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-04
  • 2013-04-29
  • 2010-11-27
  • 2011-12-07
相关资源
最近更新 更多