提交的 JDO 写入不适用于本地 GAE HRD,或可能重用的事务
Posted
技术标签:
【中文标题】提交的 JDO 写入不适用于本地 GAE HRD,或可能重用的事务【英文标题】:Committed JDO writes do not apply on local GAE HRD, or possibly reused transaction 【发布时间】:2012-11-14 17:08:47 【问题描述】:我在应用引擎上使用 JDO 2.3。我使用 Master/Slave 数据存储进行本地测试,最近切换到使用 HRD 数据存储进行本地测试,我的应用程序的某些部分正在中断(这是意料之中的)。应用程序的一个问题是它快速发送大量写入 - 这是因为 1 秒的限制,它会因并发修改异常而失败。
好的,这也是意料之中的,所以我让浏览器稍后在写入失败时重试写入(也许不是最好的 hack,但我只是想让它快速运行)。
但是奇怪的事情正在发生。即使提交阶段完成并且请求返回我的成功代码,一些应该成功的写入(那些没有得到并发修改异常的写入)也失败了。我可以从日志中看到重试请求工作正常,但我猜这些似乎在第一次尝试时提交的其他请求从未“应用”过。但是从我读到的关于应用阶段的内容来看,再次写入同一个实体应该会强制应用......但它不会。
代码如下。需要注意的几点:
-
我正在尝试使用automatic JDO caching。所以这就是 JDO 在幕后使用 memcache 的地方。除非您将所有内容包装在事务中,否则这实际上不起作用。
所有请求都是从实体中读取字符串,修改字符串的一部分,然后将该字符串保存回实体。如果这些请求不在事务中,您当然会遇到“脏读”问题。但是对于事务,隔离应该处于“可序列化”级别,所以我看不到这里发生了什么。
被修改的实体是根实体(不在组中)
我已启用跨组事务
相关代码(这是简化版):
PersistenceManager pm = PMF.getManager();
Transaction tx = pm.currentTransaction();
String responsetext = "";
try
tx.begin();
// I have extra calls to "makePersistent" because I found that relying
// on pm.close didn't always write the objects to cache, maybe that
// was only a DataNucleus 1.x issue though
Key userkey = obtainUserKeyFromCookie();
User u = pm.getObjectById(User.class, userkey);
pm.makePersistent(u); // to make sure it gets cached for next time
Key mapkey = obtainMapKeyFromQueryString();
// this is NOT a java.util.Map, just FYI
Map currentmap = pm.getObjectById(Map.class, mapkey);
Text mapData = currentmap.getMapData(); // mapData is JSON stored in the entity
Text newMapData = parseModifyAndReturn(mapData); // transform the map
currentmap.setMapData(newMapData); // mutate the Map object
pm.makePersistent(currentmap); // make sure to persist so there is a cache hit
tx.commit();
responsetext = "OK";
catch (JDOCanRetryException jdoe)
// log jdoe
responsetext = "RETRY";
catch (Exception e)
// log e
responsetext = "ERROR";
finally
if (tx.isActive())
tx.rollback();
pm.close();
resp.getWriter().println(responsetext);
更新:我很确定我知道为什么会发生这种情况,但我仍会将赏金奖励给任何能够确认的人。
基本上,我认为问题在于事务并没有真正在数据存储的本地版本中实现。参考资料:
https://groups.google.com/forum/?fromgroups=#!topic/google-appengine-java/gVMS1dFSpcU https://groups.google.com/forum/?fromgroups=#!topic/google-appengine-java/deGasFdIO-M https://groups.google.com/forum/?hl=en&fromgroups=#!msg/google-appengine-java/4YuNb6TVD6I/gSttMmHYwo0J
因为事务没有实现,所以回滚本质上是一个空操作。因此,当两个事务尝试同时修改记录时,我会得到脏读。换句话说,A读取数据,B同时读取数据。 A 尝试修改数据,B 尝试修改数据的不同部分。 A 写入数据存储,然后 B 写入,从而消除 A 的更改。然后 B 被应用引擎“回滚”,但由于在本地数据存储上运行时回滚是无操作的,所以 B 的更改会保留,而 A 不会。同时,由于 B 是抛出异常的线程,客户端重试 B,但不重试 A(因为 A 应该是成功的事务)。
【问题讨论】:
您是否考虑过重新设计您的数据存储以及如何使用它,以避免每秒多次保存到同一个实体组?或者,您是否尝试过将持久化到数据存储区交给排队任务,并安排事情以遵守 1/s 实体组写入频率限制? 我已经想到了。但在我这样做之前,我想了解为什么会发生这个特定的错误......我担心的是我根本不了解有关 HRD 或应用程序引擎/jdo 事务或其他内容的内容,或者我错过了一些内容文档,稍后它会咬我,因为我至少有 25 个其他服务需要添加事务(如果数据存储访问不在事务中,JDO 缓存将不起作用) FWIW,使用当前插件(GAE JDO v2.x),我认为 L2 缓存工作不需要在事务中进行访问;如果一个对象被读入,那么它是 L2 缓存的,如果不是,那么它应该被报告(显然不支持旧插件,所以只有在使用当前插件时才报告这样的事情)。 @DataNucleus 升级到新插件,行为相同。我不明白的是该代码执行了两次数据存储读取,然后是一次写入。启用缓存后,您会认为两次读取将来自缓存,因此唯一会进入 ds 的就是写入。但事实并非如此。相反,唯一计费的事务是一次数据存储读取,并且不会发生数据存储写入。为什么? @DataNucleus:澄清一下,当两次读取和一次写入不在事务中时,就没有缓存:所有这三个操作都进入数据存储区。 【参考方案1】:也许对你来说是个坏消息,我离开了 JDO,我正在使用 Objectify,在某些地方直接使用 datanucleus。我对我的持久性有一个完美的控制,这是一个性能和设计更好的选择(如果你从长远来看的话)。
由于 db 是 no-sql,因此针对 JPA、JDO 和标准假设存在结构变化:
使用本机 datanucleus API,您可以执行标准 JPA 甚至 Objectify 中都没有的事情:我使用的示例是动态创建列
事务在 GAE 中不存在,有些东西有时看起来像事务(实体组)。所以使用原生 API 可以避免你做这些不可察觉的体操。
尝试用操纵杆驾驶汽车是可行的,但肯定有新的东西要学。在我看来,值得学习原生方式
【讨论】:
齐德是对的。虽然 Google 提供 JPA 和 JDO 抽象层有点有趣,但我从未见过一个项目与它们配合得非常好。它通常以各种令人讨厌的黑客攻击告终,因为人们一直将支持数据存储视为 RDBMS,而事实并非如此。我强烈建议人们不要使用 JPA 或 JDO 库,而是使用原始的 Google API 或 Objectify。以上是关于提交的 JDO 写入不适用于本地 GAE HRD,或可能重用的事务的主要内容,如果未能解决你的问题,请参考以下文章
GWT+UIBinder+Gin+Guice+JDO+GAE 示例