从 Google App Engine 数据存储中获取不一致

Posted

技术标签:

【中文标题】从 Google App Engine 数据存储中获取不一致【英文标题】:Inconsistent Fetch From Google App Engine Datastore 【发布时间】:2014-12-10 05:21:18 【问题描述】:

我在 Google 应用引擎中部署了一个应用程序。在更新实体后立即按 id 获取实体时,我得到不一致的数据。我正在使用 JDO 3.0 访问应用引擎数据存储。

我有一个实体 Employee

@PersistenceCapable(detachable = "true")
public class Employee implements Serializable 

    /**
     * 
     */
    private static final long serialVersionUID = -8319851654750418424L;
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY, defaultFetchGroup = "true")
    @Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
    private String id;
    @Persistent(defaultFetchGroup = "true")
    private String name;
    @Persistent(defaultFetchGroup = "true")
    private String designation;    
    @Persistent(defaultFetchGroup = "true")
    private Date dateOfJoin;    
    @Persistent(defaultFetchGroup = "true")
    private String email;
    @Persistent(defaultFetchGroup = "true")
    private Integer age;
    @Persistent(defaultFetchGroup = "true")
    private Double salary;
    @Persistent(defaultFetchGroup = "true")
    private HashMap<String, String> experience;
    @Persistent(defaultFetchGroup = "true")
    private List<Address> address;


    /**
      * Setters and getters, toString() * */


最初,当我创建员工时,我没有设置薪水和电子邮件字段。

我稍后更新 Employee 实体以添加薪水和电子邮件。更新工作正常,数据被持久化到 appengine 数据存储中。但是,当我立即尝试通过 id 获取相同的员工实体时,有时会得到陈旧的数据,其中薪水和电子邮件为空。下面给出了我用来创建和获取员工实体的代码。

    public Employee create(Employee object) 
        Employee persObj = null;
        PersistenceManager pm = PMF.get().getPersistenceManager();
        Transaction tx = null;
        try 
            tx = pm.currentTransaction();
            tx.begin();

            persObj = pm.makePersistent(object);

            tx.commit();
         finally 

            if ((tx != null) && tx.isActive()) 
                tx.rollback();
            

            pm.close();
        

        return persObj;
    


    public Employee findById(Serializable id) 

        PersistenceManager pm = PMF.get().getPersistenceManager();

        try 
            Employee e = pm.getObjectById(Employee.class, id);

            System.out.println("INSIDE EMPLOYEE DAO : " + e.toString());
            return e;

         finally 

            pm.close();

        
    


    public void update(Employee object) 
        PersistenceManager pm = PMF.get().getPersistenceManager();
        Transaction tx = null;
        try 
            tx = pm.currentTransaction();
            tx.begin();
            Employee e = pm.getObjectById(object.getClass(), object.getId());
            e.setName(object.getName());
            e.setDesignation(object.getDesignation());
            e.setDateOfJoin(object.getDateOfJoin());
            e.setEmail(object.getEmail());
            e.setAge(object.getAge());
        e.setSalary(object.getSalary());
            tx.commit();
         finally 
            if (tx != null && tx.isActive()) 
                tx.rollback();
            

            pm.close();
        
    

我已将空闲实例的数量设置为 5,并且一次运行大约 8 个实例。当我检查各种实例的日志时,这就是我发现的。

为什么当请求由某些实例提供服务时,我会得到陈旧的数据。我可以保证,如果获取请求是由最初处理更新请求的实例处理的,我总是会得到更新的数据。但是当其他实例处理获取请求时,可能会返回陈旧的数据。我已在我的 jdoconfig.xml 中明确将数据存储读取一致性设置为强。

<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig_3_0.xsd"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig http://java.sun.com/xml/ns/jdo/jdoconfig_3_0.xsd">

   <persistence-manager-factory name="transactions-optional">
       <property name="javax.jdo.PersistenceManagerFactoryClass"
           value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>
       <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
       <property name="javax.jdo.option.NontransactionalRead" value="true"/>
       <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
       <property name="javax.jdo.option.RetainValues" value="true"/>
       <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
       <property name="datanucleus.appengine.singletonPMFForName" value="true"/>
       <property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true"/>
       <property name="datanucleus.query.jdoql.allowAll" value="true"/>      
       <property name="datanucleus.appengine.datastoreReadConsistency" value="STRONG" />

   </persistence-manager-factory>
</jdoconfig>

【问题讨论】:

***.com/search?q=%5Bgae%5D+eventual+consistency 研究“最终一致性”,因为这听起来像这样。 @PaulCollingwood 我已将数据存储读取一致性设置为 STRONG。 ***.com/questions/8112331/… 查看最后一条评论 @ichathan :我已经尝试过了。没用。 【参考方案1】:

在实体类中添加@Cacheable(value = "false")。此问题将得到解决。

以上问题主要是由于 JDO 缓存。因此,如果我们禁用缓存,JDO 将从数据存储中读取数据。

或者您可以在 jdoconfig.xml 中禁用 L2 缓存。

参考链接:http://www.datanucleus.org/products/accessplatform_3_0/jdo/cache.html

【讨论】:

我想要,但无法撤消我的反对票,但我认为这可能是一个正确的答案。【参考方案2】:

我有一个建议,但你不会喜欢这样:只使用低级 API,而在使用 GAE 时忘记 JDO/JPA。

就像@asp 所说,通过 ID 获取应该是强一致的,但是 GAE JDO 插件对我来说似乎是错误的。不幸的是,迁移到 JPA 对我来说也没有帮助(更多信息:JDO transactions + many GAE instances = overriding data)。此外,如果我将任何类注释为@PersistenceAware,Eclipse 会发疯,并在无限循环中增强类。此外,在使用带有嵌入式类和缓存的@PersistenceCapable 类时我遇到了很多问题(没有缓存它工作正常)。

嗯,重点是,我认为使用低级 API 会更快 - 您确切知道发生了什么并且它似乎按预期工作。你可以把 Entity 当作一个 Map,加上一点自写的包装代码,这似乎是一个非常有趣的替代方案。我运行了一些测试,并且使用低级 API 我通过了它们没有问题,而使用 JDO/JPA 传递它是不可能的。我正在将整个应用程序从 JDO 迁移到低级 API。这很耗时,但比无限期地等待 GAE 团队的一些神奇解决方案或错误修复要少。

另外,在撰写 GAE JDO 时,我感到……孤独。如果您对 java 甚至 android 有问题,那么成千上万的其他人已经遇到过这个问题,在 *** 上询问它并获得了大量有效的解决方案。在这里你自己一个人,所以尽可能使用低级别的 API,你会确定发生了什么。尽管迁移看起来非常可怕且耗时,但我认为与处理 GAE JDO/JPA 相比,迁移到低级 API 所浪费的时间更少。我写它不是为了扼杀开发 GAE JDO/JPA 的团队或冒犯他们,我相信他们会尽力而为。但是:

    与一般的 Android 或 Java 相比,使用 GAE 的人并不多

    将 GAE JDO/JPA 与多个服务器实例一起使用并不像您想象的那么简单和直接。像我这样的开发人员希望尽快完成他的工作,看一些例子,阅读一些文档 - 不要详细研究它,阅读一个简短的教程并且开发人员有问题,他想在 *** 上分享它和获得快速帮助。如果您在 Android 上做错了什么,无论是复杂的还是简单的错误,都可以轻松获得帮助。使用 GAE JDO/JPA 并不容易。我花在 GAE JDO 文章、教程和文档上的时间比我想的要多得多,尽管它看起来很基础,但我没有做我想做的事。如果我只是使用低级 API 而不是尝试使用 JDO 走捷径(是的,我认为 JDO 会节省我的时间),那会快得多。

    Google 对 Python GAE 的关注远超过 Java。在许多面向所有受众的文章中,有专门的 Python 代码和提示,快速示例在这里:http://googlecloudplatform.blogspot.com/2013/12/best-practices-for-app-engine-memcache.html 或这里:https://cloud.google.com/developers/articles/balancing-strong-and-eventual-consistency-with-google-cloud-datastore/。我甚至在开始开发之前就注意到了这一点,但我想与我的 Android 客户端共享一些代码,所以我选择了 Java。尽管我有扎实的 Java 背景,即使我现在确实分享了一些代码,但如果我能回到过去再选择一次,我现在会选择 Python。

这就是为什么我认为最好只使用最基本的方法来访问和操作数据。

祝你好运,祝你一切顺利。

【讨论】:

我尝试使用低级 API 获取操作。它工作正常。其余操作仍使用 JDO。不确定这是否会导致进一步的问题。【参考方案3】:

如果您使用的是 High Replication 数据存储,则设置读取策略并不能确保所有读取都具有高度一致性,这些仅适用于祖先查询。来自文档;

API 还允许您显式设置强一致性策略,但此设置不会产生实际效果,因为无论策略如何,非祖先查询始终最终一致。

https://cloud.google.com/appengine/docs/java/datastore/queries#Java_Data_consistency https://cloud.google.com/appengine/docs/java/datastore/jdo/overview-dn2#Setting_the_Datastore_Read_Policy_and_Call_Deadline

请查看有关Structuring Data for Strong Consistency 的文档,首选方法是缓存层来提供数据。

我注意到您正在使用按 ID 获取,但不确定,但即使对于 HR 数据存储 (reference),“按密钥获取”也应该是高度一致的,您可以尝试将其更改为基于密钥的查询吗?密钥是使用 id 和实体种类和祖先构建的。

【讨论】:

我遇到了同样的问题,我按照您的建议进行了“按键获取”。所以我用 query.execute(KeyFactory.createKey(...)) 替换了 pm.getObjectById(...)。到目前为止,我无法重现该问题。有其他人尝试过这种改变吗?

以上是关于从 Google App Engine 数据存储中获取不一致的主要内容,如果未能解决你的问题,请参考以下文章

从 Google App Engine 中的数据存储区获取实体以在 iOS 应用中使用

从 Google App Engine 数据存储区获取随机记录?

如何将数据从 Android 移动设备发送到 Google App Engine 数据存储区?

连接到 Google App Engine 数据存储

在 Google App Engine 数据存储中存储分层数据?

Google App Engine - 哪些工具可以在本地编辑数据存储? [关闭]