在elasticsearch中存储聊天消息的最佳方法

11

我们目前正在我们的平台上实现一个即时通讯系统。我们需要为我们的用户提供聊天记录,并能够展示用户最近的5次对话记录(类似Facebook的预览)。

因此,我们必须考虑如何存储所有这些数据。

我们正在使用Elasticsearch,并认为这可能是一种可靠的解决方案,用于存储聊天消息并使其高度易于进行读取操作。

我们的问题是,在Elasticsearch中,哪种数据结构是最好的,以便我们的读取操作可以快速而且不会太重。

我们想到了很多解决方案,这可能是我们提出的最佳方案。

我们的消息表示形式可能是:

{ 
   "ID" : 1,
   "sender" : "john",
   "receiver" : "doe",
   "content" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
   "date" : "timestamp"
}

我们可以使用嵌套对象在会话中存储消息:

 {
     "ID" : 317,
     "participants" : "john, doe",
     "date" : "timestamp of the last received message",
     "messages": [
         {
            "ID": "49753",
            "sender" : "john", 
            "receiver" : "doe",
            "content" : " Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
            "date" : "timestamp" 
         },
         {
            "ID": "49754",
            "sender" : "doe", 
            "receiver" :"john",
            "content" : " Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
            "date" : "timestamp" 
         },....
               ]
}
我们希望得到您对这个解决方案的反馈,如果您有更好的解决方案,请告诉我们。谢谢!
1个回答

19
注意:这个建议的解决方案不仅考虑了快速阅读(根据OP的要求),还着眼于最小化索引开销。嵌套文档及其父级被写成一个单独的块,因此在嵌套提案中添加每个额外的“消息”都会导致该对话中所有先前的消息和对话数据重新索引。
以下是我对Facebook实现消息的一般方法的猜测(如果您使用Elasticsearch做类似的事情)。

enter image description here

预览:(在消息导航栏下拉菜单和消息页面的左侧栏中)

显示最近几次对话的摘要,包括:

  • 最近三个对话参与者的组合头像 - 最近三个对话参与者的有序列表。
  • 如果参与者数量> 3,则显示附加参与者的数量
  • 对话中最新一条消息的时间戳
  • 对话中最新一条消息的片段

消息窗格:(消息页面的中心列)

  • 显示对话中的所有消息
  • 消息窗格还可用于消息搜索结果,显示包含搜索词的所有消息。

搜索框

  • 类型提示:(使用匹配参与者名称完成对话
  • 搜索:(使用消息正文中的匹配文本进行消息搜索)

预览所使用的数据结构可能在“conversation”索引中(每个对话包含一个文档)。每次添加消息到对话时,这些文档会更新。(就像您的嵌套示例文档的父记录一样)。此“conversation”数据源仅用于绘制预览(快速过滤对话参与者以确保您只看到自己参与的对话)。
 {
     "ID" : 317,
     "participant_ids": [123456789, 987654321],
     "participant_names: ["John Doe", "Jane Doe"],
     "last_message_snippet" : " Lorem ipsum dolor sit amet, consectetur adipiscing elit...",
     "last_message_timestamp" : "timestamp of the last received message",
 }

这里不需要嵌套,因为只需要最新的对话摘要而不是消息。

性能会很快,因为不需要评分,只需在participant_ids中过滤[current user]并按last_message_timestamp降序排序。

您可以使用Elasticsearch Term Suggester在participant_names字段上复制typeahead功能。

message文档相比,conversation文档数量较少,这有助于在大规模更新索引时使其正常运行。

为了进一步扩展此功能,可以使用时间范围索引索引策略(例如,时间范围可以由对话的典型半衰期确定)。


当在特定的对话中显示消息时,您将查询一个message索引,其中包含您的消息文档示例,但是带有对conversation的引用。
 {
     "ID" : 4828274,
     "conversation_id": 317,
     "conversation_participant_ids": [123456789, 987654321],
     "sender_id": 123456789,
     "sender_name: "John Doe",
     "message" : " Lorem ipsum dolor sit amet, consectetur adipiscing elit",
     "message_timestamp" : <timestamp>,
 }

性能将非常快,因为不需要进行评分,只需对conversation_id进行过滤并按message_timestamp降序排序。

在跨会话搜索消息时,您只需要索引message字段(遵循Facebook的实现)。

搜索查询将是以当前用户过滤conversation_participant_ids的搜索词,并按message_timestamp降序排序。

为了最小化检索会话消息时跨节点通信,您需要利用Elasticsearch的routing参数(在索引请求上),使用conversation_id作为新消息索引的routing值,明确地将会话所有消息放置在同一片上。


注意:对于可以在另一个文档存储或关系数据库中具有文本搜索功能的解决方案,Elasticsearch 可能会被证明过于复杂。通过在上面的示例中规范化“conversation”和“message”,不再存在 Elasticsearch 中的“嵌套”依赖关系。
Elasticsearch 的优点包括高效缓存过滤搜索结果、快速自动完成和快速文本搜索,但 Elasticsearch 的弱点是需要足够的内存来舒适地容纳所有索引数据。
消息应用程序的性能特征表明,只有最近的消息可能会被频繁访问或搜索,因此,如果您的应用程序需要扩展,您应该计划一种方法来归档较旧的、不经常访问的消息,以便它们需要更少的应用程序资源,但仍然可以快速“解冻”,以提供关键字搜索而不会出现过多的延迟。

谢谢您提供完整的答案,我会加以利用。接下来的问题是当用户离线时如何管理未读消息... - daley
1
你在考虑如何存储聊天消息并通知接收者吗?不确定你是否想要做与在线接收的消息不同的事情(也许是一个布尔型的“已查看”标志?) - Peter Dixon-Moses
我们必须知道消息是否被阅读才能将其标记为已查看,所以我们需要一种方法。当我们进行测试时,我可能会理解你的观点。 - daley
为什么不坚持你的Redis列表想法来显示已读/未读状态,但在消息发送后立即索引未读消息,以避免用户在回到在线状态时出现不必要的延迟,从而影响消息的可用性和搜索性。 - Peter Dixon-Moses
这应该能解决问题,我会告诉你的,再次感谢你的帮助。 - daley
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接