java - 如何使用Google App Engine数据存储(HRE)创建可靠的聊天?

标签 java google-app-engine google-cloud-datastore

在我的应用程序中,我想拥有一个实时聊天功能-在其中多个人(也许5个或更多)可以同时聊天。

我正在使用基于Java的Google App Engine-这确实是我第一次尝试使用GAE数据存储,我已经习惯于使用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);

在其他名为ReadMessages的servlet中,我有以下代码
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-并且每秒都会调用ReadMessages servlet以获取新消息。

为了进行优化,javascript将在对ReadMessage的后续请求中发送其收到的最后一条消息的ID(或可能是迄今为止收到的最高ID),以便响应仅包含以前未见过的消息。

乍一看,这一切似乎都可行,但是我认为这段代码可能有几处错误。

我认为这是错误的:
  • 某些消息可能无法读取,因为我依靠ChatMessage的键的ID来过滤掉JS客户端之前已经看到的消息-我认为这样可靠吗?
  • 某些写入操作可能会失败,因为在同一确切时间可能有5或6次传入写入操作-我的理解是,如果每秒写入次数过多,则可能会导致ConcurrentModificationException
  • 在实体上传递的日期是应用程序服务器上JRE的当前日期-也许我应该在SQL中使用类似“sysdate()”的名称?我不知道这是否真的是一个问题。

  • 我该如何修复代码,以便:
  • 所有聊天消息都将被写入-是否最好进行故障转移,以便如果请求失败,则JavaScript将重新尝试直到成功?
  • 将读取所有聊天消息(无异常(exception))
  • 清理旧消息,以便仅存储1000条左右的消息
  • 最佳答案

    当有人在向SO发布问题之前实际处理过一个问题时,这有点令人耳目一新。

    尽管您确实列出了您的方法遇到的一系列有效问题,但我建议您最大的问题是成本。您将在每个聊天消息中添加一个新实体,此外,该实体需要编制索引。因此,您正在谈论发送的每条消息的多个写入操作。您还必须为删除的每个实体付费,因此您必须付费进行清理。

    从设计的正面来看,您没有使用事务或祖先来创建您的实体,因此您不应达到写入性能限制。

    在读取方面,每条消息读取一个实体,因此成本也将在此相加。您在没有事务或祖先查询的情况下查询的事实意味着您查询时可能看不到最新的ChatMessage实体。

    此外,与SQL不同,GAE数据存储区ID不会单调增加,因此按ID GREATER_THAN进行的查询将无法正常工作。

    现在提出建议。我警告您,这将需要大量工作。

  • 最小化您使用的实体数量。与其为每个消息添加一个新的实体,不如使用一个较大的实体,每个实体存储多个消息。
  • 而不是查询消息实体,而是通过键来获取它们。通过 key 获取实体将为您提供非常一致的结果,而不是最终的一致结果。如果您要确保读取所有最新的聊天消息(无异常(exception)),这很重要

  • 这确实引入了两个新的问题,您需要处理这些问题:
  • 如果多次写入同一实体,则将达到某种写入性能限制。
  • 由于您的实体可能会变得很大,因此您需要处理此案以确保​​它们不会超出1MB的限制。

  • 您将需要两个实体种类。您将需要一个MessageLog Kind来存储多条消息。您可能需要将消息存储为MessageLog中的列表。对于给定的聊天,您将需要多个MessageLog实体,主要是为了提高写性能。 (搜索“Google App Engine分片”以获取更多信息)。

    您将需要一个聊天类型,该聊天类型实际上存储了MessageLog键的列表。这样可以进行多个聊天。您最初的实现似乎只有一个全局聊天。或者,如果您需要的话,只需使用聊天的一个实例即可。

    这些都不是真正需要索引的,因为您将通过Key获取所有内容。这样可以降低成本。

    当开始新的聊天时,您将根据期望的性能创建许多MessageLog实体。您期望的每秒写入次数为1个实体。如果您在聊天中有更多人,我会创建更多MessageLogs。然后创建一个Chat实体,并将MessageLog键的列表存储在其中。

    在写消息时,您将执行以下操作:
    -通过 key 获取适当的聊天实体,您现在有了MessageLogs的列表
    -选择一个MessageLog来分配负载,以便所有写入都不会到达同一实体。采摘一种可能有多种技术,但在此示例中,随机采摘一种。
    -格式化新消息并将其插入到MessageLog中。您也可以考虑在此时将旧消息放入MessageLog中。您还希望进行一些安全检查,以确保MessageLog在1MB实体大小限制之内。
    -编写MessageLog。这仅会产生1个写入操作,而不是用于写入新实体的最少3个写入操作。
    推荐:将消息追加到包含整个聊天记录的给定聊天的memcache条目中。

    阅读时,您将执行以下操作:
    推荐:首先检查给定聊天的内存缓存条目,如果存在,则返回,完成。
    -通过 key 获取适当的聊天实体,您现在有了MessageLogs的列表
    -通过 key 获取所有MessageLogs。现在您可以在聊天中保存所有消息,并且它们是最新的。
    -解析所有MessageLogs,并重建整个聊天记录。
    推荐:将重建的消息日志存储在内存缓存中,因此您无需再次进行。
    -返回重建的聊天记录。

    考虑使用Channel API将消息发送给查看者。这样一来,观看者可以比每秒接收消息更快地接收消息。我个人发现Channel API不是100%可靠的,因此我不会完全摆脱轮询,但是您可以每30秒轮询一次作为备份。

    想象一下聊天中有100条消息。您的原始计划读取100条消息大约要花费101次阅读操作。在这种新方法中,您将拥有5-10个MessageLog实体,因此成本为6-11个读取操作。如果您遇到了Memcache,则不需要任何读取操作。但是您必须编写代码以从多个MessageLog对象重构聊天记录。

    关于java - 如何使用Google App Engine数据存储(HRE)创建可靠的聊天?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16806297/

    相关文章:

    python - 如何在谷歌应用程序引擎中创建唯一的键名称

    python - GAE 数据存储上的模式迁移

    python - GAE 必须以 Movie 实例作为第一个参数调用未绑定(bind)方法 put() 是什么意思

    google-app-engine - 通过 Google App Engine 后端批量下载

    google-app-engine - 将查询变成投影查询,节省成本

    java - 我想在玩家重生时传送他,但它不起作用,玩家不会被传送

    java - 设置 ViewPart 的输入

    java - 如何使用javascript发送文件

    java - Google App Engine - 在数据存储查看器中看不到子项

    java - 尝试使用 Google App Engine(java) 使用不受信任的证书连接到 SSL (HTTPS) 时出现异常