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

Best way to store chat messages in elasticsearch

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

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

提前致谢

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

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

Preview:(在 Messages 导航栏下拉菜单中,以及 Messages 页面的左栏)

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

  • 最近三位对话参与者的有序列表中最近三位参与者的合成头像。
  • 额外参与者的数量如果 > 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",
 }

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

性能会很快,因为不需要进行评分,只需对 participant_ids 中的 [当前用户] 进行筛选,然后按 last_message_timestamp.

进行降序排序

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

较少数量的 conversation 文档(与 message 文档相比)将有助于索引更新如此频繁地大规模运行。

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


显示特定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 进行降序排序。

search Messages 跨对话时,您只需要索引 message 字段。 (在 Facebook 实施之后)。

搜索查询将是由 [当前用户] 在 conversation_participant_ids 中过滤的搜索词,按 message_timestamp.

降序排序

为了在检索对话消息时最大限度地减少搜索集群中的串扰,您需要确保利用 Elasticsearch's routing parameter (on indexing requests) 明确地将对话的所有消息放在一起同一个分片,在索引新消息时使用 conversation_id 作为 routing 值。


注意:Elasticsearch 可能被证明是矫枉过正的,因为它实施的解决方案在很大程度上可以从具有文本搜索功能的另一个文档存储或关系数据库构建。通过对上例中的 conversationmessage 进行归一化处理,Elasticsearch 中不再存在对 "nesting" 的任何依赖。

此实现的

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

消息应用程序的性能特征决定了 只有最近的消息 可能会以任何频率访问或搜索,因此在某些时候,如果您的应用程序需要规模,您应该计划一种方法来将较旧的、最近未访问的消息存档在 "cold-storage" 中,这样它们需要更少的应用程序资源,但仍然可以 "thawed" 足够快地为关键字搜索提供服务而不会过多延迟。