我正在应用JDO 2.3在App Engine上进行开发。之前我用了主/从数据存储来进行本地测试,最近我转而使用HRD数据存储进行本地测试,这导致我的应用程序中的某些部分出现问题(这是可以预料的)。其中一个出现问题的部分是当它快速发送大量写入时,由于1秒限制,会出现并发修改异常。
好的,这也是可以预料的,所以我让浏览器稍后重试写操作(也许不是最好的方法,但我只是想快速让它工作)。
但是奇怪的事情发生了。有些应该成功的写请求(那些没有出现并发修改异常的请求)也会失败,尽管提交阶段已完成且请求返回成功代码。我可以从日志中看到重新尝试的请求正常工作,但是这些其他请求似乎在第一次尝试提交时已经“提交”了,但是我猜可能从未“应用”。但是根据我所读到的关于应用阶段的内容,再次对该实体进行写操作应该会强制执行应用…但它没有这样做。
以下是相关代码。请注意以下几点:
好的,这也是可以预料的,所以我让浏览器稍后重试写操作(也许不是最好的方法,但我只是想快速让它工作)。
但是奇怪的事情发生了。有些应该成功的写请求(那些没有出现并发修改异常的请求)也会失败,尽管提交阶段已完成且请求返回成功代码。我可以从日志中看到重新尝试的请求正常工作,但是这些其他请求似乎在第一次尝试提交时已经“提交”了,但是我猜可能从未“应用”。但是根据我所读到的关于应用阶段的内容,再次对该实体进行写操作应该会强制执行应用…但它没有这样做。
以下是相关代码。请注意以下几点:
- 我正在尝试使用自动JDO缓存。因此,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的更改。然后app引擎"回滚" B,但由于在本地数据存储上运行时回滚是一个无操作,因此B的更改保留,而A的更改则不保留。同时,由于B是抛出异常的线程,客户端重新尝试B,但不会重试A(因为A据说是成功的事务)。