可以在Google App Engine中拥有具有同步访问权限的全局对象吗?

时间:2012-04-12 04:35:37

标签: java google-app-engine

我正在开发一个应用程序,该应用程序的对象必须对所有实例都可用,但对对象中的某些方法也具有同步访问权限。

例如我有这个对象:

public class PlanetID implements Serializable {
    public PlanetID() {
        id = 0;
    }

    public long generateID() {
        id++;
        return id;
    }

    private long id;
}

这是一个简单的对象,可以在系列中创建一个长(id)。这个对象每次都必须生成一个唯一的id。目前我有一个静态同步方法,它处理数据存储访问和存储以及MemCache访问和存储。它适用于这种特定方法,但我已经可以看到更复杂对象的问题,这些对象要求用户能够访问非同步变量以及同步变量。

是否有某种方法可以使对象全局化并允许同步方法和非同步方法以及访问这些同步对象时对象的存储?

编辑:我认为人们过分关注我给出的例子而不是更大的问题,即拥有一个全局变量,可以被所有实例访问,并且允许对特定方法进行同步访问,同时允许异步访问其他实例。 / p>

这是一个更好的例子,希望它能让事情变得更加清晰。

实施例

public class Market implements Serializable {
public Market() {
    mineral1 = new ArrayList<Listing>();
    mineral2 = new ArrayList<Listing>();
    mineral3 = new ArrayList<Listing>();
    mineral4 = new ArrayList<Listing>();
}

public void addListing(int mineral, String userID, int price, long amount) { //Doesn't require synchronized access
    switch (mineral) {
    case MINERAL1:
        mineral1.add(new Listing(userID, price, amount));
        break;
    case MINERAL2:
        mineral2.add(new Listing(userID, price, amount));
        break;
    case MINERAL3:
        mineral3.add(new Listing(userID, price, amount));
        break;
    case MINERAL4:
        mineral4.add(new Listing(userID, price, amount));
        break;
    }
}

public void purchased(int mineral, String userID, long amount) { //Requires synchronized access
    ArrayList<Listing> mineralList = null;

    switch (mineral) {
    case MINERAL1:
        mineralList = mineral1;
        break;
    case MINERAL2:
        mineralList = mineral2;
        break;
    case MINERAL3:
        mineralList = mineral3;
        break;
    case MINERAL4:
        mineralList = mineral4;
        break;
    }       

    Listing remove = null;
    for (Listing listing : mineralList)
        if (listing.userID == userID)
            if (listing.amount > amount) {
                listing.amount -= amount;
                return;
            } else{
                remove = listing;
                break;
            }

    mineralList.remove(remove);
            Collections.sort(mineralList);
}

public JSONObject toJSON(int mineral) { //Does not require synchronized access
    JSONObject jsonObject = new JSONObject();

    try {
        switch (mineral) {
        case MINERAL1:
            for (Listing listing : mineral1)
                jsonObject.accumulate(Player.MINERAL1, listing.toJSON());
            break;
        case MINERAL2:
            for (Listing listing : mineral2)
                jsonObject.accumulate(Player.MINERAL2, listing.toJSON());
            break;
        case MINERAL3:
            for (Listing listing : mineral3)
                jsonObject.accumulate(Player.MINERAL3, listing.toJSON());
            break;
        case MINERAL4:
            for (Listing listing : mineral4)
                jsonObject.accumulate(Player.MINERAL4, listing.toJSON());
            break;
        }
    } catch (JSONException e) {

    }

    return jsonObject;
}

public static final int MINERAL1 = 0;
public static final int MINERAL2 = 1;
public static final int MINERAL3 = 2;
public static final int MINERAL4 = 3;

private ArrayList<Listing> mineral1;
private ArrayList<Listing> mineral2;
private ArrayList<Listing> mineral3;
private ArrayList<Listing> mineral4;

private class Listing implements Serializable, Comparable<Listing> {
    public Listing(String userID, int price, long amount) {
        this.userID = userID;
        this.price = price;
        this.amount = amount;
    }

    public JSONObject toJSON() {
        JSONObject jsonObject = new JSONObject();

        try {
            jsonObject.put("UserID", userID);
            jsonObject.put("Price", price);
            jsonObject.put("Amount", amount);
        } catch (JSONException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return jsonObject;
    }

    @Override
    public int compareTo(Listing listing) {
        return (price < listing.price ? -1 : (price == listing.price ? 0 : 1));
    }

    public String userID;
    public int price;
    public long amount;
}

}

5 个答案:

答案 0 :(得分:1)

使用GAE,Java语言不会为您隐藏所有数据存储抽象。

不要考虑全局变量和方法。这些是Java语言结构。开始考虑数据存储区构造 - 实体,数据存储区访问和事务。

在GAE上,您的代码将同时在许多服务器上运行,它们不会共享全局变量,“共享数据”位于数据存储区(或内存缓存区)

实体是数据存储区中的对象。您可以从代码中的任何位置进行数据存储提取,以便它们可以替换全局变量。您可以在方法中定义事务以同步数据存储区访问并确保事务只发生一次。您可以在某些方法中使用事务,并且在不需要同步时不使用事务。

你不应该需要你的全球矿物数据列表。处理购买时,您实际上需要一个从数据存储区获取列表的事务,或者如果它不存在则创建它,更新用户并将其写回数据存储区。您可能希望在继续之前阅读数据存储区。

答案 1 :(得分:0)

如评论中所述 - 您可以使用交易来实现此目的:

  1. 开始交易
  2. 使用SomeObjectindex属性创建或更新someText实体。
  3. 提交交易。如果两个实例同时尝试执行此操作,则会出现异常并需要重试(再次获取数据,递增,将所有内容放入事务中)。
  4. 编辑:

    (已删除分片计数器部分,因为他们不保证)

    请注意,上述解决方案的写入瓶颈约为1写/秒。如果您需要更高性能的解决方案,可以考虑使用后端实例。

答案 2 :(得分:0)

除了事务之外的其他方法是使用单个后端实例来保留您的全局对象,并且对该对象的所有访问都是同步的。所有其他实例都需要使用URLFetch访问此后端实例以获取对象的状态。

这是一个可怕的性能瓶颈,但如果你的应用程序想要顺利扩展,请不要使用它,我只是指出了替代方法。实际上,如果可能的话,请首先避免在分布式应用程序上使用同步的全局对象。

答案 3 :(得分:0)

查看DatastoreService.allocateIds - 无论您是否实际使用生成的ID来编写数据存储区实体,您都可以以有效的方式从中获取唯一的长数字。

但是请注意,它们不能保证按顺序排列,它们只能保证是唯一的 - 问题并不表示是顺序的要求。

public class PlanetID implements Serializable
{
    private DatastoreService ds;

    public PlanetID()
    {
        ds = DatastoreServiceFactory.getDatastoreService();
    }

    public long generateID()
    {
        return ds.allocateIds("Kind_That_Will_Never_Be_Used", 1L).getStart().getId();
    }
}

答案 4 :(得分:0)

虽然技术上可行,但您应该注意其他答案的建议并使用Google提供的服务,例如数据存储和内存缓存。

但是,您可以使用包含数据的单个后端,然后使用您喜欢的RPC方法读取和写入共享对象中的数据。您需要注意的是,虽然它不经常发生,但后端并不能保证不会随机死亡 - 因此您可能会丢失此对象中的所有数据。