2012-04-12 60 views
0

我正在開發一個應用程序,該應用程序具有必須對所有實例可用的對象,但也具有對該對象內某些方法的同步訪問權限。可能在Google App Engine中擁有同步訪問的全局對象?

比如我有這樣的對象:

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

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

    private long id; 
} 

這是一個簡單的對象創建串聯長(ID)。這個對象每次都必須生成一個唯一的ID。目前,我有一個靜態同步方法,用於處理數據存儲訪問和存儲以及MemCache訪問和存儲。它適用於這種特定的方法,但我已經可以看到更復雜對象的問題,這些對象要求用戶能夠訪問非同步變量以及同步變量。

是否有某種方法可以使對象成爲全局對象,並且在訪問這些同步對象時允許同步方法和非同步方法以及對象存儲?

編輯:我認爲人們過多關注我給他們的例子,而不是關於讓全局變量可以被所有實例訪問並且允許同步訪問特定方法同時允許異步訪問他人的更大問題。

這是一個更好的例子,希望它能讓事情變得更清晰。

Ex。

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

}

+0

使用交易? – dragonx 2012-04-12 05:32:27

+0

如果從memcache訪問對象,事務是否會阻止同一個ID被多次賦予?另外,交易是否阻止了對某個對象的訪問? – 2012-04-12 05:51:45

回答

0

正如評論指出的那樣 - 你可以用交易來實現這一目標:

  1. 開始事務
  2. 創建或indexsomeText性能更新SomeObject實體。
  3. 提交交易。如果兩個實例同時嘗試這樣做,那麼將會得到一個異常,並且需要重試(重新獲取數據,增量,放入所有事務)。

編輯:

(除去上分片計數器部分,因爲它們不保證)

注意,上述溶液在約1寫入/ s的寫瓶頸。如果你需要更高性能的解決方案,你可以考慮使用後端實例。

+1

分片計數器將無法用於分配唯一的ID。 – 2012-04-12 09:31:37

+0

看着他的代碼,我假設他基本上想要一個計數器:'getTick()'和'index ++'。 – 2012-04-12 10:04:21

+0

看來我的例子是令人困惑的人,並沒有安靜地解決問題,所以我正在更新一個更復雜的對象,我想實際使用它們,但屬於相同的要求。 – 2012-04-12 11:59:26

0

除了事務之外的其他方法是使用單個後端實例來保持您的全局對象,並具有對同步對象的所有訪問權限。所有其他實例都需要使用URLFetch訪問此後端實例以獲取對象的狀態。

這是一個可怕的性能瓶頸,但如果您的應用程序想要順利擴展,請不要使用它,我只是指出了替代方法。實際上,如果可能的話,請首先避免在分佈式應用程序上需要同步全局對象。

+0

不幸的是我看不到一種方法來避免這種情況。我需要每次都生成一個唯一的ID,並且會依次選擇。我的問題沒有使用最好的例子。我將至少有兩個其他的全球對象,其中一個是允許購買虛擬商品的市場(當然,虛擬貨幣),我需要防止雙重購買,但也允許訪問列表。 – 2012-04-12 11:52:09

+0

我還應該補充說,如果它變得太過瓶頸,我會在每次更新後刪除數據存儲區的寫入,並且每隔x分鐘用一個cron保存對象以提高性能。 – 2012-04-12 11:56:09

+0

@AjjandroHuerta:首先,防止雙重購買可以採用其他方法,比採用同步方法更容易。您可以使用重試設置爲1的TaskQueue,以便重新嘗試失敗的購買。其次,你爲什麼需要ID是按順序?如果它是用於排序的目的,那麼在這種類型的其中一個值中使用可排序的值。第三,對您的情況進行排序有多關鍵?時間戳通常在實例服務器之間的幾秒鐘內失步,如果偶然出現錯誤的情況是可以接受的,請使用時間戳記作爲可排序值。 – 2012-04-13 07:31:03

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(); 
    } 
} 
+1

不幸的是,在對我的問題的評論中,提問者似乎更喜歡ID順序排列,這帶來了一大堆令人頭痛的問題。 – 2012-04-13 07:23:20

1

使用GAE,Java語言不會隱藏您的所有數據存儲區抽象。

停止思考全局變量和方法。這些是Java語言結構。開始考慮數據存儲結構 - 實體,數據存儲訪問和事務。

在GAE,你的代碼將在多臺服務器同時運行,它們將不會共享全局變量,「共享數據」是在數據存儲區(或內存緩存)

實體是在數據存儲中的對象。您可以從代碼中的任何位置獲取數據存儲區,以便它們可以替換您的全局變量。您可以在方法中定義事務來同步數據存儲區訪問,並確保事務只發生一次。您可以在某些方法中使用事務,並且在不需要同步時不使用事務。

你不應該需要你的全球礦物ArrayList。處理購買時,基本上需要一個事務,您可以從數據存儲中獲取列表,或者在數據存儲不存在時創建它,更新用戶並將其寫回數據存儲。在繼續之前,您可能需要閱讀數據存儲區。

+0

我有從數據存儲中獲取的PlanetID對象,然後從管理對PlanetID的訪問的靜態方法中放入到memcache中。然後,靜態方法將更新後的對象序列化到實體中的數據存儲中,然後再存回內存緩存中。這是我目前保持同步的黑客方式。需要生成唯一的ID需要一個全局對象來維護。不斷地從一個實體讀取和寫入,我有一個被緩存的對象。 – 2012-04-12 15:02:17

+0

我應該補充一點,我明白你的意思是用全局變量來思考,我只是用語言來表達一個想法。我使用Memcache使我的對象成爲全局對象,因爲memcache被所有實例共享。 – 2012-04-12 15:09:06

+0

你有多個行星嗎?如果是的話,每個星球都應該在數據存儲中擁有自己的實體。您可以將memcache用作緩存而不是點擊數據存儲,但不能用它來替換數據存儲 - memcache數據可能隨時消失。 找出你將如何在數據存儲中存儲各種數據類型(行星,礦物,列表),而不是Java類。當你這樣看時,解決方案應該更清晰。將數據存儲看作一堆全局變量並不能幫助理解。 – dragonx 2012-04-12 15:50:09

0

雖然技術上可行,但您應該聽取其他解答的建議並使用Google提供的服務,例如數據存儲和內存緩存。

但是,您可以使用包含數據的單個後端,然後使用您最喜歡的RPC方法將數據讀寫到共享對象中。您需要意識到,儘管不會經常發生,但後端不保證不會隨機死亡 - 因此您可能會丟失此對象中的所有數據。

相關問題