承诺的JDO写入不适用于本地GAE HRD或可能重用的事务

时间:2012-11-27 05:52:55

标签: java google-app-engine memcached jdo

我在app引擎上使用JDO 2.3。我使用Master / Slave数据存储区进行本地测试,最近切换到使用HRD数据存储区进行本地测试,我的部分应用程序正在破坏(这是预期的)。正在破坏的应用程序的一部分是它快速发送大量写入的地方 - 这是因为1秒限制的事情,它失败了并发修改异常。

好的,这也是预料之中的,所以我让浏览器在失败时再次重试写入(可能不是最好的黑客,但我只是想让它快速运行)。

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

代码如下。有些事情需要注意:

  1. 我正在尝试使用automatic JDO caching。所以这就是JDO使用memcache的地方。除非您在事务中包装所有内容,否则这实际上不起作用。
  2. 所有请求正在执行的是从实体读取字符串,修改字符串的一部分,并将该字符串保存回实体。如果这些请求不在事务中,那么您当然会遇到“脏读”问题。但是对于事务,隔离应该处于“可序列化”的水平,所以我不知道这里发生了什么。
  3. 被修改的实体是根实体(不在组中)
  4. 我启用了跨群组交易
  5. 相关代码(这是简化版):

    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同时读取数据。尝试修改数据,B尝试修改数据的不同部分。写入数据存储区,然后B写入,消除A的更改。然后B由应用引擎“回滚”,但由于在本地数据存储上运行时回滚是无操作,因此B的更改保持不变,而A则不然。同时,由于B是抛出异常的线程,客户端重试B,但不重试A(因为A应该是成功的事务)。

1 个答案:

答案 0 :(得分:1)

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

因为db是no-sql,所以针对JPA,JDO和标准假设存在结构性变化:

使用原生datanucleus API,你可以做一些不在标准JPA中甚至是在Objectify中的东西:我使用的例子是动态创建列

交易在GAE中不存在,有时候看起来像交易(实体组)。因此,使用原生API将避免您进行这种不可思议的体操。

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