【问题标题】:Google App Engine atomic section?Google App Engine 原子部分?
【发布时间】:2011-02-03 03:21:32
【问题描述】:
  • 假设您从数据存储区检索一组记录(例如:select * from MyClass where reserved='false')。

  • 如何确保其他用户未设置保留仍为假?

我查看了 Transaction 文档,对 google 的解决方案感到震惊,即捕获异常并在循环中重试。

我缺少的任何解决方案 - 很难相信在这种环境中没有办法进行原子操作。

(顺便说一句 - 我可以在 servlet 中使用“syncronize”,但我认为它无效,因为无法确保 servlet 对象只有一个实例,不是吗?同样适用于静态变量解决方案)

你知道怎么解决吗?

(这是谷歌解决方案:

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

看看:

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

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

可怕的解决方案,不是吗?

【问题讨论】:

  • 不,这并不可怕。一般原则称为“乐观锁定”,在争用相当罕见的情况下,它比互斥(“悲观锁定”)更有效。它对大规模分布式实现也更加友好。
  • 如果您有许多客户试图同时预订一个房间,并且在每次预订之前您需要执行一个 select 语句,那就太可怕了!
  • 更不用说在选择之后你持有所有未预订的房间,所以当时任何其他客户请求都会导致异常......锁定机制不是我的发明......它是微不足道的需要...
  • 还不错。在互斥的情况下,除了一个之外,所有这些客户都将无所事事,而在 Google 的解决方案中,除了一个之外,所有客户都处于一个循环中,抛出异常并再次尝试。 Somebody 无论哪种方式都成功,因为只有在有人写了一些东西(即成功预订房间)时才会抛出异常。这一种锁定机制,它只是你不习惯的那种。在节点之间有损通信的分布式系统上实现互斥绝非易事。我只能建议你顺其自然。
  • 不,锁也不是更好,因为它们总是施加开销,即使在绝大多数没有冲突的情况下也是如此。乐观并发仅在检测到实际冲突时会导致额外开销。

标签: google-app-engine servlets


【解决方案1】:

您是正确的,您不能使用同步或静态变量。

您认为在 App Engine 环境中不可能有原子操作是不正确的。 (看看原子是什么意思here)当你做一个事务时,它是原子的——要么一切都发生,要么什么都没有发生。听起来您想要的是某种全局锁定机制。在 RDBMS 世界中,这可能类似于“选择更新”或将事务隔离级别设置为序列化事务。这些类型的选项中的任何一种都不是非常可扩展的。或者正如您所说,它们都是可怕的解决方案:)

如果你真的想要应用引擎中的全局锁定,你可以这样做,但它会很丑陋并且严重损害可扩展性。您需要做的就是创建某种 CurrentUser 实体,在其中存储具有全局锁定的当前用户的用户名。在让用户执行任何操作之前,您需要先检查是否没有用户已列为 CurrentUser,然后将该用户的密钥写入 CurrentUser 实体。检查和写入必须在事务中。这样,只有一个用户是“当前”用户,因此拥有全局锁定。

【讨论】:

  • 希望很明显,这个操作(在事务中获取 CurrentUser)将比首先在事务中执行原始更新操作更糟糕......如果不是,在阅读后评论,我希望它会是:)
  • 感谢您的回复。 - GAE 中不能使用 synchronize 是在哪里写的? - 我不是在寻找 DB 世界意义上的“原子”,而是在编程语言意义上 - 我实际上并不需要超过 1 个 JVM 来运行我的应用程序,因为它是一个本地应用程序。
  • 在所有客户端中使用 while 循环,在该循环中调用数据存储会占用我所有的 DB/CPU qouata,不是吗?
  • 不,因为只要有冲突,循环才会迭代。在冲突导致大量开销之前,App Engine 尤其可以每秒为每个实体组处理 5 到 10 次更新。
  • @Peter:bach 在另一个问题中提供了一个例子。假设您有一些机器人不断地用更新淹没数据库,其速率高于完成事务所需的时间。然后互斥会阻止机器人足够长的时间来完成一些工作。您可以争论是否首先应该让机器人以该频率运行,以及事务是否应该花费那么长时间,但实际上阻塞锁会补偿它。你的建议,或者一个实体,意思是“从我的创建时间开始 X 秒内不要更新”。
【解决方案2】:

你的意思是这样的:

   public void func(Data data2) {

        String query = "select from " + objectA.class.getName()
                + " where reserved == false";
        List<objectA> Table = (List<objectA>) pm.newQuery(
                query).execute();

        for (objectA row : Table)
        {
            Data data1 = row.getData1();
            row.setWeight(JUtils.CalcWeight(data1, data2));
        }

        Collections.sort(Table, new objectA.SortByWeight());

        int retries = 0;
        int NUM_RETRIES = 10;
        for (int i = 0; i < Table.size() ; i++)
        {   
            retries++;      
            pm.currentTransaction().begin(); //    <---- BEGIN
            ObjectA obj = pm.getObjectById(Table.get(i).class, Table.get(i).getKey());
            if (obj .getReserved() == false)   // <--- CHECK if still reserved
                obj.setReserved(true);
            else
                break;      

            try
            {
                pm.currentTransaction().commit();
                break;
            } 
            catch (JDOCanRetryException ex)
            {
                if (j == (NUM_RETRIES - 1))
                {
                    throw ex;
                }
                i--; //so we retry again on the same object
            }
        }
        }

【讨论】:

    猜你喜欢
    • 2015-02-19
    • 1970-01-01
    • 2015-11-13
    • 2016-07-13
    • 1970-01-01
    • 2012-10-25
    • 1970-01-01
    • 2023-03-02
    • 2012-02-11
    相关资源
    最近更新 更多