cod*_*tor 5 java google-app-engine google-cloud-datastore
在我的应用程序中,我希望有一个实时聊天功能 - 其中多个人(可能是5个或更多)可以同时聊天.
我正在使用基于Java的Google App Engine - 这是我第一次尝试使用GAE数据存储区,我已经习惯使用Oracle/MySQL,所以我认为我的策略是错误的.
注意:为简单起见,我省略了任何验证/安全检查.
在一些名为WriteMessageI的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);
Run Code Online (Sandbox Code Playgroud)
在其他一些叫做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());
Run Code Online (Sandbox Code Playgroud)
在javascript客户端代码中 - WriteMessage每当用户提交新消息时ReadMessages调用servlet - 并且每秒调用servlet以获取新消息.
为了优化,javascript将在后续请求中发送它收到的最后一条消息的id(或者可能是它到目前为止收到的最高id)ReadMessage,以便响应仅包含它之前没有见过的消息.
这一切似乎都起作用,但我想这个代码可能有一些问题.
这是我认为错的:
有些消息可能无法读取,因为我依赖于ChatMessage键的id来过滤掉JS客户端之前已经看过的消息 - 我认为这不可靠吗?
有些写入可能会失败,因为在同一时间可能会有5或6个传入写入 - 我的理解是,ConcurrentModificationException如果每秒写入次数太多,则可能会导致这种情况.
在实体上传递的日期是应用程序服务器上JRE的当前日期 - 也许我应该在SQL中使用类似"sysdate()"的东西?我不知道这是否真的是一个问题.
如何修复代码以便:
所有聊天消息都将被写入 - 是否最好进行故障转移,以便如果请求失败,javascript将重新尝试直到成功?
将读取所有聊天消息(无例外)
清理旧邮件,以便只存储1000条左右的邮件
dra*_*onx 11
当某人在向SO发布问题之前实际处理问题时,这有点令人耳目一新.
虽然你确实列出了一堆你的方法遇到的有效问题,但我建议你最大的问题是费用.您正在为每个聊天消息添加新实体,此外该实体需要编制索引.所以你在谈论发送的每条消息的多个写操作.您还必须为您删除的每个实体付费,因此您必须付清单才能清理.
在您的设计的正面,您没有使用事务或祖先来创建您的实体,因此您不应该达到写入性能限制.
在阅读方面,您为每条消息读取了一个实体,因此成本也会在那里加起来.您在没有事务或祖先查询的情况下进行查询这一事实意味着您在查询时可能看不到最新的ChatMessage实体.
此外,与SQL不同,GAE数据存储ID不是单调增加的,因此ID GREATER_THAN的查询不起作用.
现在提出建议.我警告你,这将是很多工作.
最小化您使用的实体数量.不是为每个消息添加新实体,而是使用一个更大的实体,每个实体存储多个消息.
而不是查询消息实体,而是按键获取它们.按键获取实体将为您提供强烈一致的结果,而不是最终一致的结果.如果您想确保读取所有最新的聊天消息(无例外),这一点很重要
这确实引入了您需要处理的两个新问题:
如果多个写入到同一个实体,您将达到某种写入性能限制.
由于您的实体可能会变得很大,因此您需要处理此案例以确保它们不会超过1MB的限制.
你需要两个实体种类.您需要一个存储多条消息的MessageLog种类.您可能希望将消息存储为MessageLog中的List.对于给定的聊天,您将需要多个MessageLog实体,主要用于写入性能.(搜索"Google App Engine Sharding"了解更多信息).
您需要一个基本上存储MessageLog键列表的聊天类型.这允许多个聊天继续.您的原始实现似乎只有一个全局聊天.或者如果你想要,只需使用聊天的单个实例.
这些都不需要编入索引,因为您将通过Key获取所有内容.这将降低成本.
当您开始新的聊天时,您将根据您期望的需要创建许多MessageLog实体.每期每秒写入1个实体.如果聊天中有更多人,我会创建更多MessageLog.然后创建一个Chat实体并在其中存储MessageLog键列表.
在写入消息时,您将执行以下操作: - 按键获取相应的聊天实体,您现在有一个MessageLog列表 - 选择一个MessageLog来分配负载,这样所有写入都不会到达同一个实体.选择一个可能有多种技术,但是对于这个例子,随机选择一个. - 格式化新消息并将其插入MessageLog.您也可以考虑在此时删除MessageLog中的旧消息.您还需要进行一些安全检查,以确保MessageLog在1MB实体大小限制内. - 编写MessageLog.这应该只产生1个写操作而不是写入新实体的最少3个写操作.推荐:将消息附加到包含整个聊天记录的给定聊天的memcache条目.
在阅读时,您将执行以下操作:建议:首先检查给定聊天的memcache条目,如果存在,则返回完成. - 按键获取相应的聊天实体,现在有一个MessageLog列表 - 按键获取所有MessageLog.现在,您在聊天中收到了所有消息,并且它们是最新的. - 解析所有MessageLog,并重建整个聊天记录.建议:将重建的消息日志存储在内存缓存中,这样您就不必再次执行此操作. - 返回重建的聊天记录.
考虑使用Channel API将消息发送给查看者.观看者可以通过这种方式比每秒更快地接收消息.我个人发现Channel API不是100%可靠,所以我不会完全摆脱轮询,但是你可能每隔30秒轮询一次作为备份.
想象一下聊天,其中包含100条消息.您的原始计划将花费大约101个读取操作读取100条消息.在这个新方法中,你有5-10个MessageLog实体,因此成本将是6-11个读取操作.如果你得到一个memcache命中,你不需要任何读操作.但是您必须编写代码以从多个MessageLog对象重建聊天日志.
| 归档时间: |
|
| 查看次数: |
2827 次 |
| 最近记录: |