2013-05-29 40 views
5

在我的應用程序中,我想要有一個實時聊天功能 - 多個人(可能有5個或更多)可以同時聊天。如何使用Google App Engine數據存儲(HRE)創建可靠的聊天?

我使用的是基於Java的Google App Engine - 這是我第一次嘗試使用GAE Datastore,我很習慣使用Oracle/MySQL,所以我認爲我的策略是錯誤的。

注:爲簡單起見,我省略任何驗證/安全檢查 在一些所謂的WriteMessage的servlet我有以下代碼

Entity entity = new Entity("ChatMessage"); 
entity.setProperty("userName", request.getParameter("userName")); 
entity.setProperty("message", request.getParameter("message")); 
entity.setProperty("time", new Date()); 
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 
datastore.put(entity); 

在一些其他的servlet叫ReadMessages我有下面的代碼

String id = request.getParameter("id"); 
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 
Query query = new Query("ChatMessage"); 
if (id != null) { 
    // Client requested only messages with id greater than this id 
    Filter idFilter = new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, 
     FilterOperator.GREATER_THAN, 
     KeyFactory.createKey("ChatMessage", Long.parseLong(id))); 
    query.setFilter(idFilter); 
} 
PreparedQuery pq = datastore.prepare(query); 
JsonArray messages = new JsonArray(); 
for (Entity result : pq.asIterable()) { 
    JsonObject jmsg = new JsonObject(); 

    // Client will use this id on the next request to read to poll only 
    // "new" messages 
    jmsg.addProperty("id", result.getKey().getId()); 
    jmsg.addProperty("userName", (String) result.getProperty("userName")); 
    jmsg.addProperty("message", (String) result.getProperty("message")); 
    jmsg.addProperty("time", ((Date) result.getProperty("time")).getTime()); 
    messages.add(jmsg); 
} 
PrintWriter out = response.getWriter(); 
out.print(messages.toString()); 

在JavaScript客戶端代碼 - WriteMessage servlet在用戶提交新消息時被調用 - 一個d ReadMessages每秒調用一次servlet來獲取新消息。

爲了優化,javascript會在後續請求中發送它收到的最後一條消息的標識(或者迄今爲止收到的最高標識)到ReadMessage,以便響應僅包含它所沒有的消息以前沒見過。

這一切似乎起初工作,但我想也許這個代碼有一些錯誤的東西。

這裏是什麼,我認爲是錯誤的:

  • 有些消息可能無法讀取,因爲我依靠ChatMessage重點篩選出消息的ID的JS客戶端已經見過 - 我不認爲這將是可靠的權利?

  • 有些寫入可能會失敗,因爲在同一時間可能有5個或6個傳入寫入 - 我的理解是,如果每秒寫入次數過多,則可能導致ConcurrentModificationException

  • 在實體上傳遞的日期是應用程序服務器上JRE的當前日期 - 也許我應該在SQL中使用類似「sysdate()」的東西?我不知道這是否是一個問題。

如何修復代碼,以便:

  1. 所有的聊天信息會被寫入 - 這純粹是最好有一個故障轉移,因此如果請求失敗JavaScript的只是重新嘗試,直到成功?

  2. 所有的聊天信息會被讀取(無例外)

  3. 清理舊消息,因此,只有1000左右的郵件存儲

+1

看一看:http://googcloudlabs.appspot.com/codelabexercise4.html –

回答

11

當有人在一個問題實際上是工作它還挺清爽在向SO發佈問題之前。

雖然您列出了一系列您遇到的有效問題,但我建議您最大的問題是成本。您正在爲每個聊天消息添加一個新實體,並且此實體需要進行索引。所以你正在討論發送每條消息的多個寫操作。您還必須爲您刪除的每個實體支付費用,因此您必須支付清理費用。

在設計的優勢方面,您沒有使用事務或祖先來創建您的實體,因此您不應該達到寫入perf限制。

在讀取方面,您每條消息讀取一個實體,因此成本也將累計在那裏。事實上,您查詢沒有事務或祖先查詢意味着您查詢時可能看不到最新的ChatMessage實體。

另外,與SQL不同,GAE數據存儲ID不是單調遞增,因此查詢ID GREATER_THAN不起作用。

現在的建議。我警告你,這將是很多工作。

  1. 最小化您使用的實體數量。不是每個消息添加一個新的實體,而是使用一個更大的實體,每個實體存儲多個消息。

  2. 而不是查詢消息實體,通過密鑰獲取它們。通過鍵獲取實體將爲您提供強烈一致的結果,而不是最終一致的結果。

    • 如果有多個寫道:如果你想確保所有最新的聊天消息被讀取(無例外)

這確實引入了兩個新的問題,你需要處理的這一點很重要要去同一個實體,你會遇到某種寫性能限制。

  • 由於您的實體可能會變得很大,您需要處理這種情況以確保它們不會超過1MB的限制。

  • 您需要兩個實體種類。您需要一個存儲多條消息的MessageLog Kind。您可能希望將消息作爲List存儲在MessageLog中。對於給定的聊天,您將需要多個MessageLog實體,主要用於寫入性能。 (有關更多信息,請搜索「Google App Engine Sharding」)。

    您需要一個聊天類,基本上存儲一個MessageLog鍵的列表。這允許多個聊天繼續。您的原始實施似乎只有一次全球聊天。或者如果你想要的話,只需使用一個聊天實例。

    這些都不需要索引,因爲您將通過Key獲取所有內容。這會降低成本。

    當你開始一個新的聊天時,你會根據你期望的perf來創建一些MessageLog實體。 1您希望每秒寫入一個實體。如果你有更多的人在聊天,我會創建更多的消息日誌。然後創建一個聊天實體並在其中存儲MessageLog密鑰列表。

    在留言寫上你會做到以下幾點: - 獲取由鍵適當的聊天的實體,您現在有MessageLogs 列表 - 選擇一個MessageLog來分配負載,因此所有的寫入不打同一個實體。可能有多種技術來挑選一個,但對於這個例子,隨機挑選一個。 - 格式化新消息並將其插入到MessageLog中。此時您也可以考慮在MessageLog中放棄舊消息。您還需要進行一些安全檢查,以確保MessageLog在1MB實體大小限制內。 - 寫入MessageLog。這應該只會產生1個寫操作,而不是寫入新實體的最少3個寫操作。 推薦:將消息追加到包含整個聊天記錄的給定聊天的memcache條目。

    在閱讀時,您會執行以下操作: 推薦:首先檢查給定聊天的memcache條目,如果存在,則返回該條目,完成。 - 通過密鑰獲取適當的聊天實體,您現在有一個消息日誌列表 - 通過密鑰獲取所有消息日誌。現在,您在聊天中收到所有消息,並且它們是最新的。 - 解析所有的MessageLogs,並重建整個聊天記錄。 推薦:將重建的消息日誌存儲在內存緩存中,因此您不必再次執行此操作。 - 返回重建的聊天記錄。

    考慮使用Channel API將消息發送給觀衆。觀衆可以以這種方式每秒更快地接收消息。我個人發現Channel API不是100%可靠的,所以我不會完全擺脫輪詢,但是您可以每隔30秒進行一次輪詢就可以作爲備份。

    想象一下與其中說100條消息的聊天。你原來的計劃將花費約101讀取操作讀取100條消息。在這個新方法中,你會得到5-10個MessageLog實體,所以成本將是6-11個讀操作。如果您獲得了內存緩存命中,則不需要任何讀取操作。但是您必須編寫代碼以從多個MessageLog對象重建聊天記錄。

    +0

    非常詳細的答覆!非常感謝,我會研究這個迴應。 – codefactor

    +0

    確實非常詳細的迴應。但我有一個問題。聊了一會兒,如果客戶端(瀏覽器)與服務器斷開連接,我們如何找到相同的聊天實例來添加消息到?如何存儲數據,以便我們可以執行一致的查詢並始終查找斷開連接之前使用的同一聊天實例? –

    +0

    就在我頭頂的時候,您希望按用戶和日期/時間爲您的聊天實例編制索引,並搜索最新的聊天實例。一致性可能是一個問題,我沒有真正想到如何解決這個問題。用戶可能不會太頻繁地連接,因此您可能能夠通過最終一致的查詢來生存下來。 – dragonx

    相關問題