Java Google Appengine закрыл счетчики без транзакций - PullRequest
10 голосов
/ 24 сентября 2010

Я рассматриваю пример Sharded Counters на Java: http://code.google.com/appengine/articles/sharding_counters.html

У меня есть вопрос о реализации метода приращения.В python он явно оборачивает get () и увеличивает транзакцию.В примере с Java он просто получает и устанавливает его.Я не уверен, что полностью понимаю хранилище данных и транзакции, но кажется, что критический раздел обновления должен быть заключен в транзакцию хранилища данных.Я что-то пропустил?

Оригинальный код:

  public void increment() {
    PersistenceManager pm = PMF.get().getPersistenceManager();

    Random generator = new Random();
    int shardNum = generator.nextInt(NUM_SHARDS);

    try {
      Query shardQuery = pm.newQuery(SimpleCounterShard.class);
      shardQuery.setFilter("shardNumber == numParam");
      shardQuery.declareParameters("int numParam");

      List<SimpleCounterShard> shards =
          (List<SimpleCounterShard>) shardQuery.execute(shardNum);
      SimpleCounterShard shard;

      // If the shard with the passed shard number exists, increment its count
      // by 1. Otherwise, create a new shard object, set its count to 1, and
      // persist it.
      if (shards != null && !shards.isEmpty()) {
        shard = shards.get(0);
        shard.setCount(shard.getCount() + 1);
      } else {
        shard = new SimpleCounterShard();
        shard.setShardNumber(shardNum);
        shard.setCount(1);
      }

      pm.makePersistent(shard);
    } finally {
      pm.close();
    }
  }
}

Транзакционный код (я полагаю, вам нужно выполнить это в транзакции, чтобы гарантировать правильность при параллельных транзакциях?):

public void increment() { 
    PersistenceManager pm = PMF.get().getPersistenceManager(); 
    Random generator = new Random(); 
    int shardNum = generator.nextInt(NUM_SHARDS); 
    try { 
      Query shardQuery = pm.newQuery(SimpleCounterShard.class); 
      shardQuery.setFilter("shardNumber == numParam"); 
      shardQuery.declareParameters("int numParam"); 
      List<SimpleCounterShard> shards = 
          (List<SimpleCounterShard>) shardQuery.execute(shardNum); 
      SimpleCounterShard shard; 
      // If the shard with the passed shard number exists, increment its count 
      // by 1. Otherwise, create a new shard object, set its count to 1, and 
      // persist it. 
      if (shards != null && !shards.isEmpty()) { 
            Transaction tx = pm.currentTransaction(); 
        try { 
            tx.begin(); 
            //I believe in a transaction objects need to be loaded by ID (can't use the outside queried entity) 
             Key shardKey = KeyFactory.Builder(SimpleCounterShard.class.getSimpleName(), shards.get(0).getID()) 
            shard =  pm.getObjectById(SimpleCounterShard.class, shardKey); 
            shard.setCount(shard.getCount() + 1); 
            tx.commit(); 
        } finally { 
            if (tx.isActive()) { 
                tx.rollback(); 
            } 
        } 
      } else { 
        shard = new SimpleCounterShard(); 
        shard.setShardNumber(shardNum); 
        shard.setCount(1); 
      } 
      pm.makePersistent(shard); 
    } finally { 
      pm.close(); 
    } 
  } 

1 Ответ

6 голосов
/ 24 сентября 2010

Этот раздел прямо из документов показывает, что вы совершенно правы в отношении необходимости транзакции:

http://code.google.com/appengine/docs/java/datastore/transactions.html#Uses_For_Transactions

В этом примере демонстрируется одно использование транзакций: обновление сущностис новым значением свойства относительно его текущего значения.

    Key k = KeyFactory.createKey("Employee", "k12345");
    Employee e = pm.getObjectById(Employee.class, k);
    e.counter += 1;
    pm.makePersistent(e);

Это требует транзакции, так как значение может быть обновлено другим пользователем после того, как этот код выберетобъект, но перед сохранением измененного объекта. Без транзакции запрос пользователя будет использовать значение счетчика до обновления другого пользователя, а при сохранении будет перезаписано новое значение.При транзакции приложению сообщается об обновлении другого пользователя.Если объект обновляется во время транзакции, транзакция завершается с ошибкой.Приложение может повторить транзакцию, чтобы использовать новые данные.

Это очень близко к тому, что делает этот пример с использованием сегментированного кода, и, как и вы, я не смог найти причину, почему счетчики с сегментированным кодом будут другими.

...