提交的 JDO 写入不适用于本地 GAE HRD,或者可能重用的事务

2022-09-01 16:18:10

我在应用引擎上使用 JDO 2.3。我正在使用主/从数据存储进行本地测试,最近切换到使用HRD数据存储进行本地测试,并且我的应用程序的某些部分正在中断(这是意料之中的)。该应用程序中断的一部分是它快速发送大量写入 - 这是因为1秒的限制,它因并发修改异常而失败。

好吧,这也是意料之中的,所以我让浏览器在以后失败时再次重试写入(也许不是最好的黑客,但我只是想让它快速工作)。

但一件奇怪的事情正在发生。一些应该成功的写入(那些没有获得并发修改异常的写入)也失败了,即使提交阶段完成并且请求返回我的成功代码也是如此。我从日志中可以看出,重试的请求工作正常,但是这些似乎在第一次尝试时提交的其他请求从未“应用”。但是从我读到的关于应用阶段的内容来看,再次写入同一实体应该强制应用......但事实并非如此。

代码如下。需要注意的一些事项:

  1. 我正在尝试使用自动JDO缓存。所以这就是JDO在盖子下使用memcache的地方。除非您将所有内容包装在事务中,否则这实际上不起作用。
  2. 所有请求所做的就是从实体中读取字符串,修改字符串的一部分,然后将该字符串保存回实体。如果这些请求不在事务中,您当然会遇到“脏读”问题。但是对于事务,隔离应该处于“可序列化”的水平,所以我不明白这里发生了什么。
  3. 被修改的实体是根实体(不在组中)
  4. 我启用了跨组交易

相关代码(这是一个简化版本):

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

也许对你来说是个坏消息,我离开了JDO,我正在使用Objectify,在某些地方直接使用datanucleus。我对自己的坚持有一个完美的控制,这是一个性能和设计更好的选择(如果你从长远来看的话)。

由于 db 是 no-sql,因此存在针对 JPA、JDO 和标准假设的结构更改:

使用本机datanucleus API,你可以做一些标准JPA甚至不在Objectify中的事情:我使用的例子是动态创建列。

GAE 中不存在事务,有时看起来像事务(实体组)。因此,使用本机API将避免您进行此类不可察觉的体操。

尝试用操纵杆驾驶汽车可能会起作用,但肯定有新的东西需要学习。在我看来,值得学习本地方式


答案 2

推荐