2013-05-29 1 views
5

В моем приложении я хочу иметь функцию чата - в которой несколько человек (возможно, 5 или более) могут одновременно общаться вместе.Как создать надежный чат с помощью Google App Engine Data Store (HRE)?

Я использую Java-приложение на основе Java - это действительно первый раз, когда я пытался использовать хранилище данных GAE, я так привык использовать Oracle/MySQL, поэтому считаю, что моя стратегия неверна.

Примечание: Для простоты я опуская любые проверки проверки/безопасности В некоторых сервлета называется WriteMessage я следующий код

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

В какой-то другой сервлет называется 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 вызывается в любое время, когда пользователь отправляет новое сообщение - d ReadMessages Сервлет вызывается каждую секунду для получения новых сообщений.

Для того, чтобы оптимизировать, javascript отправит идентификатор последнего сообщения, которое он получил (или, возможно, самый высокий идентификатор, полученный до сих пор), на последующие запросы до ReadMessage, так что ответ содержит только сообщения, Раньше я видел.

Это, кажется, работает сначала, но я думаю, что, возможно, в этом коде есть пара ошибок.

Вот что я думаю, что это не так:

  • Некоторые сообщения не могут быть истолкованы, потому что я полагаться на идентификатор ключа ChatMessage, чтобы отфильтровать сообщения о том, что клиент JS уже видел раньше - я не думайте, что будет надежным?

  • Некоторые записи могут потерпеть неудачу, так как в то же время может быть 5 или 6 входящих записей - и я понимаю, что это может привести к ConcurrentModificationException, если слишком много записей в секунду.

  • Дата, переданная сущностью, является текущей датой JRE на сервере приложений - может быть, я должен использовать что-то вроде «sysdate()» в SQL? Я не знаю, действительно ли это проблема или нет.

Как я могу исправить код так, что:

  1. Все сообщения чата будет написано - что он просто лучше иметь отказоустойчивость, так что если запрос не на JavaScript будет просто повторить попытку до тех пор, не

  2. Все сообщения чата будут прочитаны (без исключения)

  3. Чистые старые сообщения, так что только 1000 или около того сообщения сохраняются

+1

Посмотрите на: http://googcloudlabs.appspot.com/codelabexercise4.html –

ответ

11

Это своего рода освежающий, когда кто-то на самом деле работал над проблемой перед отправкой вопроса в SO.

Пока вы описали кучу актуальных проблем, с которыми вы сталкиваетесь с вашим подходом, я бы предложил, чтобы ваша самая большая проблема была бы стоить. Вы добавляете новый объект с каждым сообщением чата, и, кроме того, этот объект должен быть проиндексирован. Таким образом, вы говорите о нескольких операциях записи для каждого отправленного сообщения. Вы также должны платить за каждый объект, который вы удаляете, поэтому вам придется заплатить, чтобы очистить.

С другой стороны, вы не используете транзакции или предки для создания своих сущностей, поэтому вы не должны нажимать на лимит записи.

На стороне чтения вы читаете по одному объекту на сообщение, поэтому затраты также будут складываться там. Тот факт, что вы запрашиваете без транзакций или запросов предков, означает, что вы не можете видеть последние объекты ChatMessage при запросе.

Кроме того, в отличие от SQL, идентификаторы хранилища GAE не монотонно увеличиваются, поэтому запрос с идентификатором GREATER_THAN не будет работать.

Теперь для предложения. Я предупреждаю вас, это будет много работы.

  1. Минимизировать количество объектов, которые вы используете. Вместо добавления нового сущности для каждого сообщения используйте более крупный объект, который хранит несколько сообщений для каждого объекта.

  2. Вместо запроса для объектов сообщения выберите их ключ. Получение объектов по ключевым словам даст вам сильные согласованные результаты, а не в конечном итоге последовательные результаты. Это важно, если вы хотите, чтобы убедиться, что все последние сообщения чата считываемые (нет исключений)

Это действительно вводит две новые проблемы, которые нужно решать:

  • В случае нескольких записей идут к одному и тому же объекту, вы попадете в какой-то лимит производительности записи.

  • Поскольку ваши сущности могут расти большими, вам необходимо обработать дело, чтобы убедиться, что они не превышают ограничение 1 МБ.

Вам понадобятся две сущности. Вам понадобится MessageLog Kind, который хранит несколько сообщений. Вероятно, вы захотите сохранить сообщения в виде списка в MessageLog. Для данного чата вам понадобятся несколько объектов MessageLog, в первую очередь для производительности записи. (для получения дополнительной информации см. «Осколок Google App Engine»).

Вам понадобится Чат, который по существу хранит список ключей MessageLog. Это позволяет использовать несколько чатов. У вашей первоначальной реализации, казалось, был только один глобальный чат. Или, если вы этого хотите, просто используйте один экземпляр чата.

Ни один из них действительно не нужно индексировать, так как вы будете получать все с помощью ключа. Это уменьшит затраты.

Когда вы начинаете новый чат, вы создадите несколько объектов MessageLog на основе того, что вы ожидаете от вас. 1 Суть за каждую запись, которую вы ожидаете. Если в чате больше людей, я бы создал больше MessageLogs. Затем создайте объект чата и сохраните в нем список ключей MessageLog.

В сообщении напишите вы могли бы сделать следующее: - Fetch соответствующего объекта чата по ключу, теперь у вас есть список MessageLogs - Выберите один MessageLog распределить нагрузку так, чтобы все записи не поражая ту же сущность , Может быть несколько методов для их выбора, но для этого примера выберите один случайный случай. - Отформатируйте новое сообщение и вставьте его в MessageLog. Вы также можете рассмотреть возможность удаления старых сообщений в MessageLog в этот момент. Вы также хотите выполнить некоторую проверку безопасности, чтобы гарантировать, что MessageLog находится в пределах ограничения размера сущности 1MB. - Напишите MessageLog. Это должно повлечь за собой только 1 запись op вместо минимальной записи 3 записи для записи нового объекта. РЕКОМЕНДОВАНО: добавьте сообщение в запись memcache для данного чата, которая содержит весь журнал чата.

При чтении вы должны сделать следующее: РЕКОМЕНДУЕТСЯ: сначала проверьте запись memcache для данного чата, если она существует, просто верните это, сделано. - Извлеките соответствующий объект чата по ключу, теперь у вас есть список MessageLogs - Извлеките все MessageLogs с помощью ключа. Теперь у вас есть все ваши сообщения в чате, и они актуальны. - Разберите все MessageLogs и восстановите весь журнал чата. РЕКОМЕНДОВАНО: Храните восстановленный журнал сообщений в memcache, так что вам не нужно делать это снова. - Верните реконструированный журнал чата.

Рассмотрите возможность использования API канала для отправки сообщений зрителям. Зрители могут получать сообщения быстрее, чем один раз в секунду. Я лично считаю, что API канала не на 100% надежный, поэтому я не смог бы полностью избавиться от опроса, но вы можете быть в порядке с опросом один раз каждые 30 секунд в качестве резервной копии.

Представьте себе чат с 100 сообщениями в нем. Ваш первоначальный план будет стоить около 101 читать ops, прочитав 100 сообщений. В этом новом методе у вас будет что-то вроде 5-10 объектов MessageLog, поэтому стоимость будет 6-11 операций чтения. Если вы получаете хит memcache, вам не нужны никакие операции чтения. Но вам нужно написать код для восстановления журнала чата из нескольких объектов MessageLog.

+0

Очень подробный ответ! Большое вам спасибо, я изучу этот ответ. – codefactor

+0

Очень подробный ответ действительно. Однако у меня есть один вопрос. После чата какое-то время, если клиент (браузер) отключится от сервера, как мы найдем тот же экземпляр чата для добавления сообщений? Как хранить данные, чтобы мы могли выполнять согласованный запрос и всегда находили тот же самый экземпляр чата, который использовался до отключения? –

+0

Сверху моей головы вы хотите индексировать свои чат-экземпляры пользователем и датой/временем и искать самую новую. Консистенция может быть проблемой, я действительно не думал о том, как ее решить. Вероятно, пользователи не будут подключаться слишком часто, поэтому вы сможете выжить в результате последовательного запроса. – dragonx

Смежные вопросы