Elasticsearch:如何在 Elasticsearch 中存储复杂的关系数据

news2024/10/7 15:20:47

在传统的数据库中,对数据关系的描述无外乎三种:一对一、一对多和多对多关系。 如果有关系相关的数据,我们一般在建表的时候加上主外键。 建立数据链接,然后在查询或者统计中通过 join 恢复或者补全数据,最后得到我们需要的结果数据,然后转换到 Elasticsearch中,如何处理这些关系数据呢?

我们都知道 Elasticsearch 是一个 NoSQL 类型的数据库,弱化了对关系的处理,因为像 Lucene、Elasticsearch、Solor 这样的全文搜索框架对性能的要求更高。 一旦发生 join 操作,性能会很差,所以在使用搜索框架的时候,应该避免把搜索引擎当作关系型数据库来使用。

当然实际数据肯定是有关联的,那么在 Elasticsearch 中如何处理和管理这些关联数据呢?

大家都知道 Elasticsearch 天生支持 JSON 数据是完美的,只要是标准 JSON 结构的数据,不管多复杂,不管嵌套多少层,都可以存储在 Elasticsearch 中,然后可以查询分析,检索。在该机制中,处理和管理关系的方式主要有以下三种:

1)使用 object 和 array[object] 字段类型自动存储多层结构的 JSON 数据

这是 Elasticsearch 默认的机制,也就是我们没有设置任何 mapping,直接往 Elasticsearch 服务器插入一个复杂的 JSON 数据,也能插入成功,而且可以支持检索,(可以这样是因为 Elasticsearch 默认是动态的 mapping ,只要插入标准的 JSON 结构就会自动转换,当然我们也可以控制映射类型,Elasticsearch 支持动态映射和静态映射,静态映射也分严格类型,弱类型,通用类型,不再在这里展开。有兴趣的可以到官网了解)如下数据之一:

PUT cars/_doc/1
{
  "name": "Zach",
  "car": [
    {
      "maker": "Saturn",
      "model": "SL"
    },
    {
      "maker": "Subaru",
      "model": "Imprezza"
    }
  ]
}

我们在 Kibana 中直接打入上面的命令,我们可以查看这个 cars 索引的 mapping。

GET cars/_mapping

上面的命令返回的结果:

{
  "cars": {
    "mappings": {
      "properties": {
        "car": {
          "properties": {
            "maker": {
              "type": "text",
              "fields": {
                "keyword": {
                  "type": "keyword",
                  "ignore_above": 256
                }
              }
            },
            "model": {
              "type": "text",
              "fields": {
                "keyword": {
                  "type": "keyword",
                  "ignore_above": 256
                }
              }
            }
          }
        },
        "name": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        }
      }
    }
  }
}

生成的存储结构类似于以下内容:

{  
  "name"  :  "Zach" ,  
  "car.maker"  : [ "Saturn" , "Subaru" ]  
  "car.model"  : [ "SL" ,  "Imprezza" ]  
}

因为 Elasticsearch 的底层 Lucene 是天然支持多值存储的,所以看起来像上面的数组结构。 实际上,Elasticsearch 是作为一个多值字段存储在这个字段中的。

这样的数据实际上包含了数据和关系。 它看起来像一个一对多的关系。 一个人拥有多辆汽车。 但其实并不是严格的关系,因为 Lucene 底层是平放存储的,所以多辆车的数据其实是混在一起的 数据,你不能根据人名来返回其中的一辆车,因为整个数据是一个整体,无论什么操作都会返回整个数据。

上述的数据结够,在有些时候是很有用的,但是它不能维护 car.maker 及 car.model 的对应关系。比如我们查询 car.maker 为 Subaru 时,它不能返回 car.model 为 Imprezza。

更多阅读,请参阅我之前的文章 “Elasticsearch: object 及 nested 数据类型”。

2)使用 nested[object] 类型来存储具有多级关系的数据

在上面的场景中,我们指出了 array 中存储的数组对象并不是严格相关的,因为第二层的数据没有分离。 如果要分离,则必须使用 nested 类型显式定义数据结构。 只有这样,第二层的多辆汽车数据才相互独立,也就是说可以单独获取或查询某辆汽车的数据。Nested 类型是 object 数据类型的特殊版本。它允许对象数组以一种可以彼此独立查询的方式进行索引

同样的 JSON 数据:


  "name": "Zach",
  "car": [
    {
      "maker": "Saturn",
      "model": "SL"
    },
    {
      "maker": "Subaru",
      "model": "Imprezza"
    }
  ]

如果我们使用上面的 object 来进行存储的话,那么 Elasticsearch 将把整个信息当做一个整体进行存储。如果我们把 car 数据定义为 nested 数据类型,它的形式如下:

PUT cars
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text"
      },
      "car": {
        "type": "nested",
        "properties": {
          "maker": {
            "type": "keyword"
          },
          "model": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

如上所示, car 被定义为 nested 数据类型。最终 Elasticsearch 显示的存储为3个:1 个是 root 文档,另外两个是 car 数组中的两个文档。查询的时候可以独立查询,性能还不错,缺点是更新的代价比较大,每次子文档更新都要重建整个结构的索引,所以 nested 适用于嵌套多级关系不经常更新的场景。

Nested 类型的数据,需要使用其指定的查询和聚合方式才能生效,普通的 Elasticsearch 查询只能查询 1 级或根级属性,nested 属性无法查询,如果要查询,必须使用 embedded 的 Set 查询或聚合。

嵌套应用程序有两种模式:

  • 嵌套查询:每个查询在单个文档中有效,包括排序
  • 嵌套聚合或过滤:同级别所有文档全局有效,包括过滤排序

更多阅读:Elasticsearch: object 及 nested 数据类型

3)父/子关系

父/子模式与嵌套非常相似,但应用侧重点不同。

在使用 parent/children 管理关系时,Elasticsearch 会在每个 shard 的内存中维护一张关系表。 检索时,关联数据由 has_parent 和 has_child 过滤器获取。 在这种模式下,使用父文档和子文档。 也是独立的,查询性能会比嵌套模式略低,因为插入时父文档和子文档会通过路由分布在同一个 shard,但不保证在同一个Lucene sengment index segment,所以检索性能略低。 此外,每次检索 Elasticsearch 时,都需要从内存关系表中获取数据关联信息。 也需要一定的时间。 嵌套的好处是更新父文档或子文档。 不影响其他文档,所以更新频繁的多级关系使用 parent/children 模式是最合适的。

在 Elasticsearch 中,Join 可以让我们创建 parent/child 关系。Elasticsearch 不是一个 RDMS。通常 join 数据类型尽量不要使用,除非不得已。那么 Elasticsearch 为什么需要 Join 数据类型呢?

在 Elasticsearch 中,更新一个 object 需要 root object 一个完整的 reindex:

  • 即使是一个 field 的一个字符的改变
  • 即便是 nested object 也需要完整的 reindex 才可以实现搜索

通常情况下,这是完全 OK 的,但是在有些场合下,如果我们有频繁的更新操作,这样可能对性能带来很大的影响。

join 数据类型可以完全地把两个 object 分开,但是还是保持这两者之前的关系。

  1. parent 及 child 是完全分开的两个文档
  2. parent 可以单独更新而不需要重新 reindex child
  3. children 可以任意被添加/串改/删除而不影响 parent 及其它的 children

与 nested 类型类似,父子关系也允许你将不同的实体关联在一起,但它们在实现和行为上有所不同。 与 nested 文档不同,它们不在同一文档中,而 parent/child 文档是完全独立的文档。 它们遵循一对多关系原则,允许你将一种类型定义为 parent 类型,将一种或多种类型定义为 child 类型

即便 join 数据类型给我们带来了方便,但是,它也在搜索时给我带来额外的内存及计算方便的开销。

join 数据类型是一个特殊字段,用于在同一索引的文档中创建父/子关系。 关系部分定义文档中的一组可能关系,每个关系是父(parent)名称和子(child)名称。 

一个例子:

PUT my_index
{
  "mappings": {
    "properties": {
      "my_join_field": { 
        "type": "join",
        "relations": {
          "question": "answer" 
        }
      }
    }
  }
}

在这里我们定义了一个叫做 my_index 的索引。在这个索引中,我们定义了一个 field,它的名字是 my_join_field。它的类型是 join 数据类型。同时我们定义了单个关系:question 是 answer 的 parent。

要使用 join 来 index 文档,必须在 source 中提供关系的 name 和文档的可选 parent。 例如,以下示例在 question 上下文中创建两个 parent 文档:

PUT my_index/_doc/1?refresh
{
  "text": "This is a question",
  "my_join_field": {
    "name": "question" 
  }
}
 
PUT my_index/_doc/2?refresh
{
  "text": "This is another question",
  "my_join_field": {
    "name": "question"
  }
}

更多阅读:Elasticsearch:Join 数据类型,Elasticsearch:在 Elasticsearch 中的 join 数据类型父子关系。

总结

方法一:

  • 简单、快速、高性能
  • 善于维持一对一的关系
  • 无需特别查询

方法二:

  • 由于底层存储在同一个 Lucene sengment 中,因此读取和查询性能比较方法更快。
  • 更新单个子文档会重建整个数据结构,所以不适合更新频繁嵌套的场景。
  • 可以维护一对多和多对多的存储关系

方法三:

  • 多关系数据,存储完全独立,但存在于同一个分片中,因此读取和查询性能略低于第二种方式。
  • 需要额外内存,维护管理关系表
  • 更新文档不会影响其他子文档,适合更新频繁使用的场景。
  • 排序和打分操作繁琐,需要额外的脚本函数支持
  • 每种方式都有自己适合的应用场景,所以在实践中,我们需要根据实际业务场景选择合适的存储方式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/512532.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Springboot +Flowable,设置流程变量的方式(二)

一.简介 为什么需要流程变量。 举个例子,假设有如下一个流程,截图如下: 这是一个请假流程,那么谁请假、请几天、起始时间、请假理由等等,这些都需要说明,不然领导审批的依据是啥?那么如何传递…

软件测试面试宝典,最常见的7个高频面试题(附答案,建议收藏)

收集了2022年所有黑马学员的面试题后,负责就业的黑马讲师们整理出了7个高频出现的面试题,一起来看看。 高频问题1:请自我介绍下? 高频问题2:请介绍下最近做过的项目? 高频问题3:请介绍下你印象…

2023 年第三届长三角高校数学建模 A 题 快递包裹装箱优化问题

2022 年,中国一年的包裹已经超过 1000 亿件,占据了全球快递事务量的一 半以上。近几年,中国每年新增包裹数量相当于美国整个国家一年的包裹数量, 十年前中国还是物流成本最昂贵的国家,当前中国已经建立起全世界最强大、…

利用jQuery做一个简单的猜数字游戏

目录 利用jQuery做一个简单的猜数字游戏 代码 效果 利用jQuery做一个简单的猜数字游戏 代码 <!DOCTYPE html> <html><head><title>键盘事件-猜数字</title><style>#body_style {background-color: #c7f5db;}#myDiv {position: absolu…

单片机GD32F303RCT6 (Macos环境)开发 (十七)—— i2c1从机中断接收发送数据

i2c1从机中断接收发送数据 1、将i2c1设置为从机模式&#xff0c;与树莓派连接。树莓派发送或者读取数据&#xff0c;gd32中断触发&#xff0c;从而接收数据或者向主机发送数据。 2、关于代码的宏定义配置 Application目录的Makefile中 ENABLE_I2C_TEST yes才会编译I2C1的相关…

为啥马斯克一边反对Open AI,一边又自己另搞AI

这事我过去就说过&#xff0c;我现在再老生常谈一次。 &#xff08;1&#xff09; 我过去说过一个事&#xff1a;汽车自己智能其实还不能做到真正的智能&#xff0c;必须车路人都智能了&#xff0c;智能汽车才能真正智能。 因为车路人智能&#xff0c;这本质是数字世界和数字世…

开源之夏 2023 | 与 Databend 一同探索云数仓的魅力

活动概览 开源之夏是由中科院软件所“开源软件供应链点亮计划”发起并长期支持的一项暑期开源活动&#xff0c;旨在鼓励在校学生积极参与开源软件的开发维护&#xff0c;培养和发掘更多优秀的开发者&#xff0c;促进优秀开源软件社区的蓬勃发展&#xff0c;助力开源软件供应链…

基于AT89C52单片机的交通信号灯设计

点击链接获取Keil源码与Project Backups仿真图&#xff1a; https://download.csdn.net/download/qq_64505944/87772657 源码获取 主要内容&#xff1a; 本次设计所提出的一种基于单片机技术的简易计算器的方案&#xff0c;能更好的解决计算机计算的问题,随着数字生活的到来&…

eSIM-GSMA-1-EID规则定义

规范 SGP.02-v4.0.pdf Official Document SGP.02 - Remote Provisioning Architecture for Embedded UICC Technical Specification eid管理规则-SGP.29-1.0 EID Principles 国家编号机构发布 ICCID 的现有机制&#xff0c;以及后续的用户识别不受影响 EID 的核心目的是唯…

知识管理协同工具:助力企业综合信息规整化发展

随着信息化时代的来临&#xff0c;企业面临的信息越来越庞杂&#xff0c;如何管理和利用这些信息成为了企业发展的关键。知识管理作为一种新型的管理思想和方法&#xff0c;已经被广泛应用于企业管理中。知识管理工具是知识管理的重要手段之一&#xff0c;它可以帮助企业实现信…

STM32的功耗模式

按功耗由高到低排列&#xff0c; STM32 具有运行、睡眠、停止和待机四种工作模式。 低功耗各模式下芯片工作情况&#xff1a; 睡眠模式&#xff1a;仅关闭了内核时钟&#xff0c;内核停止运行&#xff0c;但其片上外设&#xff0c; CM4 核心的外设全都还照常 运行。有两种方式…

一款开源免费、非常好用的的SSH/SFTP客户端Electerm

electerm是一款基于electron开发的SSH/SFTP客户端&#xff0c;同时支持Linux、MAC、Windows操作系统&#xff0c;免费开源。 下载地址 Releases electerm/electerm (github.com) 点击下图箭头所指&#xff0c;下载windwos版本 安装 双击安装&#xff0c;等待安装完成 使用…

Melis4.0[D1s]:8.显示测试:图片格式和透明度

文章目录 1.准备素材图片1.1 测试图片像素格式的软件RawViewer.exe1.1.1 使用方法 1.2 自己生成测试图片 2.D1s显示引擎介绍&#xff08;不保证正确&#xff09;2.1 D1s 可以有2个独立的display device输出&#xff08;可以同时接2个显示器&#xff09;2.2 D1s 的 DISP0 有2个通…

普罗米修斯-docker安装

prometheus 监控原理 1、prometheus &#xff1a;虽然说是监控平台&#xff0c;但是实际上是一套数据库 2、mysql_exporter: 可以理解成程序或者软件&#xff0c;他是工作在我们要监控的目标服务器上&#xff0c;主要是用于监控mysql的数据。 3、node_exporter: 他的作用主要是…

Java 3种IO模型,一次搞懂

大家好&#xff0c;我是老三&#xff0c;上一节我们讨论了Linux的五种IO模型&#xff0c;接下来&#xff0c;我们从Java语言层面&#xff0c;来看看对IO的实现。 在Java中&#xff0c;一共有三种IO模型&#xff0c;分别是阻塞IO(BIO)、非阻塞IO(NIO)和异步IO(AIO)。 Java BIO …

IDEA “Cannot resolve symbol” 解决办法

系列文章目录 文章目录 系列文章目录前言一、Cannot resolve symbol是什么问题&#xff1f;二、第一步&#xff1a;检查Maven配置三、第二步&#xff1a;检查target四、 第三步&#xff1a;检查 project五、第四步&#xff1a;lombok 问题总结 前言 请耐心读完&#xff0c;也许…

FE_Vue框架的重要属性讲解【ref props mixin】

1 ref属性 对于传统的HTML而言&#xff0c;id 和 ref确实没有什么差别&#xff0c;但是对于组件来说就不一样了。给组件加id&#xff0c;打印出获取的结果为组件所对应的完整DOM结构。给组件加ref&#xff0c;打印出获取的结果就是VueComponent实例。 被用来给元素或子组件注册…

【SpringCloud微服务实践】注册与发现(Eureka)

注册与发现(Eureka) 注册与发现初实践&#xff0c;注册中心采用SpringCloud全家桶中的Eureka&#xff0c;并配合RestTemplate从Euraka调用微服务。 在前置章节的电影票-用户服务组的基础上升级&#xff0c;摒弃硬编码微服务地址的方案&#xff0c;使用eureka实现微服务的动态…

计算机毕业论文选题推荐|软件工程|系列三

文章目录 导文题目导文 计算机毕业论文选题推荐|软件工程 (***语言)==使用其他任何编程语言 例如:基于(***语言)门窗账务管理系统的设计与实现 得到:基于JAVA门窗账务管理系统的设计与实现 基于vue门窗账务管理系统的设计与实现 等等 题目 基于(***语言) 一体化智慧停车…

86.qt qml-多种粒子特效按钮实现

截图如下所示: 动图如下所示: 支持黑白模式: 1.实现原理 配合之前我们学习的: 82.qt qml-2D粒子系统、粒子方向、粒子项(一)_诺谦的博客-CSDN博客 83.qt qml-初步学习2D粒子影响器(二)_诺谦的博客-CSDN博客 即可实现出来。 以按钮特效3按钮为例: