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

Java 提交的JDO写入不适用于本地GAE HRD,也可能不适用于重用的事务,java,google-app-engine,memcached,jdo,Java,Google App Engine,Memcached,Jdo,我在AppEngine上使用JDO2.3。我使用主/从数据存储进行本地测试,最近切换到使用HRD数据存储进行本地测试,我的应用程序的一部分正在崩溃(这是意料之中的)。该应用程序的一个突破之处是它可以快速发送大量的写操作——这是因为1秒的限制,它失败了,同时出现了一个修改异常 好的,这也是意料之中的,所以我让浏览器在写入失败后再次重试写入(可能不是最好的黑客,但我只是想让它快速工作) 但一件奇怪的事情正在发生。一些应该成功的写入(那些没有得到并发修改异常的写入)也失败了,即使提交阶段完成,请求返回

我在AppEngine上使用JDO2.3。我使用主/从数据存储进行本地测试,最近切换到使用HRD数据存储进行本地测试,我的应用程序的一部分正在崩溃(这是意料之中的)。该应用程序的一个突破之处是它可以快速发送大量的写操作——这是因为1秒的限制,它失败了,同时出现了一个修改异常

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

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

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

  • 我正在尝试使用。这就是JDO在幕后使用memcache的地方。除非将所有内容都打包到事务中,否则这实际上不起作用
  • 所有请求都是从实体中读取字符串,修改字符串的一部分,然后将该字符串保存回实体。如果这些请求不在事务中,您当然会遇到“脏读”问题。但是对于事务,隔离应该处于“可序列化”的级别,所以我看不出这里发生了什么
  • 正在修改的实体是根实体(不在组中)
  • 我已启用跨组事务
  • 相关代码(这是一个简化版本):

    更新:我很确定我知道为什么会发生这种情况,但我仍然会将奖金奖励给任何能够证实这一点的人

    基本上,我认为问题在于事务并没有真正在数据存储的本地版本中实现。参考资料:


    因为没有实现事务,回滚本质上是一个禁止操作。因此,当两个事务试图同时修改记录时,我会得到一个脏读。换句话说,A读取数据,B同时读取数据。A尝试修改数据,B尝试修改数据的不同部分。A写入数据存储,然后B写入,删除A的更改。然后B被appengine“回滚”,但由于回滚在本地数据存储上运行时是不可操作的,因此B的更改保留,而a的更改不保留。同时,由于B是引发异常的线程,客户端重试B,但不重试A(因为A被认为是成功的事务)。

    也许对您来说是个坏消息,我离开了JDO,使用Objectify,在某些地方直接使用datanucleus。我完全可以控制我的持久性,这是一个性能和设计更好的选择(如果你考虑长远的话)

    由于db不是sql,因此针对JPA、JDO和标准假设存在结构性变化:

    使用本机datanucleus API,您可以执行标准JPA甚至Objectify中都没有的操作:我使用的示例是动态创建列

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


    试着用操纵杆驾驶一辆汽车是可行的,但肯定有新的东西需要学习。在我看来,学习原生方式是值得的

    您是否想过重新设计您的数据存储以及如何使用它,以避免每秒多次持久化到同一实体组?或者,您是否尝试过将持久化转移到数据存储以使任务排队,并按照1/s实体组写入频率限制进行安排?我已经想到了这一点。但在我这么做之前,我想了解为什么会发生这种特殊的错误。。。我担心的是,我根本不了解有关HRD或app engine/jdo事务或其他方面的内容,或者我在文档中遗漏了一些内容,这会影响我以后的工作,因为我至少还有25个其他服务需要添加事务(如果数据存储访问不在事务中,JDO缓存将不起作用)FWIW,使用当前的插件(GAE JDO v2.x),我认为二级缓存不需要在事务中访问;如果读取了一个对象,那么它就是二级缓存,如果没有,那么应该报告它(显然,旧插件不受支持,因此只有在使用当前插件时才报告此情况)@DataNucleus升级到新插件后,得到了相同的行为。我不明白的是,代码执行两次数据存储读取,然后执行一次写入。当启用缓存时,您可能会认为这两次读取来自缓存,因此唯一会进入ds的是写入。但事实并非如此。相反,我所做的唯一计费事务是只有一个数据存储读取,而没有数据存储写入。为什么?@DataNucleus:为了澄清这一点,当两个读取和一个写入不在一个事务中时,就没有缓存:所有这三个操作都进入数据存储。Zied是对的。虽然谷歌提供JPA和JDO抽象层有点有趣,但我从来没有这样做过r看到一个项目进行得非常顺利。它通常以各种恶劣的黑客行为结束,因为人们一直认为备份数据存储是RDBMS,而事实上它肯定不是。我强烈建议人们不要使用JPA或JDO库,使用原始的Google API或Objectify。
    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);