Java Google Appengine分片计数器没有事务

时间:2010-09-23 20:29:03

标签: java google-app-engine transactions concurrency

我正在阅读Java中的Sharded Counters示例: 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 个答案:

答案 0 :(得分:6)

直接从文档中删除此部分表明您完全正确需要进行交易:

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);
  

这需要一个事务,因为在此代码获取对象之后,但在保存修改后的对象之前,其他用户可能会更新该值。 没有事务,用户的请求将在其他用户更新之前使用计数器的值,并且保存将覆盖新值。通过事务,应用程序将被告知其他用户的更新。如果在事务期间更新实体,则事务将失败并发生异常。应用程序可以重复事务以使用新数据。

它非常接近于该分片示例正在做的事情,并且像您一样,我无法找到任何理由为什么分片计数器会有所不同。