9

我们目前正在我们的平台上实施即时通讯系统。我们需要为我们的用户提供聊天记录,并能够显示用户最近的 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" 
         },....
               ]
}

我们希望收到您对此解决方案的反馈,如果您有更好的解决方案,我们也希望得到您的反馈。

提前致谢

4

1 回答 1

17

注意:这个建议的解决方案不仅从快速读取的角度(按照 OP 的要求),而且着眼于最大限度地减少索引开销。嵌套文档及其父对象被写为一个块,因此在嵌套提案中添加每个额外的“消息”都会导致该会话中所有先前的消息和会话数据也被重新索引。

这是我对 Facebook 实现消息的一般方法的猜测(如果您要使用 Elasticsearch 做类似的事情)

在此处输入图像描述

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

使用以下方式显示最近对话的摘要:

  • - 最近三个对话参与者的有序列表中三个最近参与者的合成头像。
  • 如果> 3,则额外参与者的数量
  • 对话中最新消息的时间戳
  • 对话中最新消息的片段

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

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

搜索框

  • Typeahead:(使用匹配的参与者姓名完成对话)
  • 搜索:(使用消息正文中的匹配文本搜索消息)

驱动预览的数据结构可能在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",
 }

这里没有嵌套 b/c 只需要最新的对话摘要,而不是消息。

性能会很快,因为不需要进行评分,只需对 [current user] in 进行过滤,participant_ids然后按降序排序last_message_timestamp

您可以在现场使用 Elasticsearch Term Suggester 复制预输入功能participant_names

较少数量的conversation文档(与message文档相比)将有助于索引频繁更新,从而在规模上很好地发挥作用。

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


在特定的中显示Messagesconversation时,您将查询message带有您的 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 可能会被证明对于实现一个主要由另一个文档存储或具有文本搜索功能的关系数据库构建的解决方案来说太过分了。通过规范化conversationmessage在上面的例子中,Elasticsearch 不再依赖于“嵌套”。

此实现的Elasticsearch优势包括过滤搜索结果的有效缓存、快速自动完成和快速文本搜索,但 Elasticsearch 的弱点是需要足够的内存来轻松容纳所有索引数据。

消息应用程序的性能特征决定了只有最近的消息才可能以任何频率被访问或搜索,因此在某些时候,如果您的应用程序需要扩展,您应该计划一种方法来归档旧的,不是最近的-在“冷存储”中访问的消息,这样它们需要更少的应用程序资源,但仍然可以足够快地“解冻”以提供关键字搜索而不会产生过多的延迟。

于 2016-09-14T19:31:48.033 回答