Непоследовательная выборка из хранилища данных Google App Engine
У меня есть приложение, развернутое в Google App Engine. Я получаю противоречивые данные, когда я получаю сущность по идентификатору сразу после обновления этой сущности. Я использую JDO 3.0 для доступа к хранилищу данных ядра приложения.
У меня есть сотрудник Сотрудник
@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. Однако, когда я сразу пытаюсь получить ту же сущность сотрудника по идентификатору, я иногда получаю устаревшие данные, где зарплата и электронная почта равны нулю. Код, который я использую для создания и извлечения сущности сотрудника, приведен ниже.
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>
2 ответа
У меня есть предложение, однако вам это не понравится: используйте исключительно API низкого уровня и забудьте о JDO / JPA при использовании GAE.
Точно так же, как сказал @asp, предполагается, что get by ID является строго согласованным, однако плагин GAE JDO кажется мне ошибочным. К сожалению, миграция в JPA также не помогла в моем случае (подробнее здесь: транзакции JDO + много экземпляров GAE = переопределяющие данные). Кроме того, если я аннотирую какой-либо класс как @PersistenceAware, Eclipse сходит с ума и расширяет классы в бесконечном цикле. Кроме того, у меня было много проблем при использовании класса @PersistenceCapable со встроенным классом и кэшированием (без кэширования это работало нормально).
Ну, суть в том, что я думаю, что это будет намного быстрее с низкоуровневым API - вы точно знаете, что происходит, и кажется, что он работает как задумано. Вы можете обращаться с Entity как с картой, с небольшим количеством написанного самостоятельно оберточного кода, это кажется довольно интересной альтернативой. Я запускаю некоторые тесты и, используя API низкого уровня, прошёл их без проблем, в то время как пройти его с помощью JDO / JPA было невозможно. Я нахожусь в процессе миграции всего моего приложения из JDO в API низкого уровня. Это отнимает много времени, но меньше, чем бесконечно ждать какого-то волшебного решения или исправления от команды GAE.
Кроме того, во время написания GAE JDO я чувствовал себя... одиноким. Если у вас есть проблема с Java или даже Android, тысячи других людей уже имели эту проблему, спросили об этом на stackru и получили тонны правильных решений. Здесь вы все сами, поэтому используйте как можно более низкий уровень API, и вы будете уверены, что происходит. Несмотря на то, что миграция кажется ужасной и отнимает много времени, я думаю, что вы будете тратить меньше времени на переход на низкоуровневый API, чем на GAE JDO/JPA. Я не пишу это, чтобы ущипнуть команду, которая разрабатывает GAE JDO / JPA или оскорбить их, я уверен, что они делают все возможное. Но:
Существует не так много людей, использующих GAE по сравнению, скажем, с Android или Java в целом.
Использование GAE JDO / JPA с несколькими экземплярами сервера не так просто и понятно, как вы думаете. Такой разработчик, как я, хочет, чтобы его работа была выполнена как можно скорее, посмотрите какой-нибудь пример, прочитайте немного документации - не для того, чтобы изучать все это подробно, прочитайте краткое руководство, и у разработчика есть проблема, он хотел бы поделиться им в stackru и получить быструю помощь Легко получить помощь, если вы делаете что-то не так на 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 также позволяет явно устанавливать строгую политику согласованности, но этот параметр не будет иметь практического эффекта, поскольку запросы, не являющиеся предками, всегда в конечном итоге непротиворечивы независимо от политики.
https://cloud.google.com/appengine/docs/java/datastore/queries https://cloud.google.com/appengine/docs/java/datastore/jdo/overview-dn2
Пожалуйста, ознакомьтесь с документом о структурировании данных для строгой согласованности, предпочтительным подходом является слой кэширования для обслуживания данных.
Я заметил, что вы используете get by ID, не уверен, но "get by key" должен быть строго согласованным даже для хранилища данных HR ( ссылка), вы можете попробовать изменить его на запрос, основанный на ключе? Ключ строится с использованием идентификатора и вида сущности и происхождения.
Добавлять @Cacheable(value = "false")
в классе сущности. Эта проблема будет решена.
Выше проблема в основном из-за кеша JDO. Так что если мы отключим кеш, JDO будет читать данные из хранилища данных.
Или вы можете отключить кэш L2 в jdoconfig.xml.
Ссылка: http://www.datanucleus.org/products/accessplatform_3_0/jdo/cache.html