7. 文档

es索引是一组相关文档的集合体,而文档是存储在索引中的数据,每个文档在索引中都有一个唯一ID。文档是一组字段的组合,字段可以是任意数据类型,例如字符串、数字、布尔值等。

文档就是es中索引的一条记录信息。它与MySQL数据库中的一个元组类似,也就是一行记录。在es中,它是以JSON形式存储

1. 新增文档

新增文档就是向已经创建好的索引中写入数据,分为两种情况:单条写入数据、批量写入数据。

1.1 文档ID

文档ID是基于Base64编码且长度为20个字符的GUID字符串(大小写英文字符、阿拉伯数字组成)。

如果写入文档的时候不进行指定,则系统会分配一个随机值代表文档的唯一ID,例如:

dsl 复制代码
"_id":"UdXGGIIBzpSblKON8uQQ"

1.2 新增单个文档

dsl 复制代码
PUT lwy_index/_doc/1
{
  "user": "lwy",
  "message": "hello world"
}

# 不指定ID
POST lwy_index/_doc/
{
  "user": "ljy",
  "message": "hello world again"
}

以上操作在没有预先创建索引的条件下也可以成功新增数据。因为es默认的创建索引机制是开启的,由action.auto_create_index动态参数决定,该参数默认值为true

使用如下命令行来关闭自动创建索引机制。

dsl 复制代码
PUT _cluster/settings
{
  "persistent": {
    "action.auto_create_index": "false"
  }
}

1.3 批量新增文档

批量操作是bulk操作。批量操作是写入优化中最重要的优化指标之一。相比于一条条插入数据,批量写入数据能有效减少刷新以及段合并操作、提高写入效率。第三方es数据同步工具elastic-dump的本质就是结合reindex与批量bulk操作。

  1. 指定索引批量插入数据
dsl 复制代码
POST lwy_index2/_bulk
{"index":{"_id":1}}
{"field_a":"aaa","author":"jerry", "publisher":"CD"}
{"index":{"_id":2}}
{"field_a":"bbb","author":"tom", "publisher":"CD"}
  1. 批量执行多种操作
dsl 复制代码
POST _bulk
{"index":{"_index":"bulk_index","_id":1}}
{"field1":"value1"}
{"delete":{"_index":"bulk_index","_id":2}}
{"create":{"_index":"bulk_index","_id":3}}
{"field1":"value3"}
{"update":{"_index":"bulk_index","_id":1}}
{"doc":{"field2":"value2"}}

create和index的区别:

  • Create:如果相同ID文档存在,则保持原有文档,且会有报错提示。
  • Index:如果相同ID文档存在,则更新当前新插入文档,并覆盖之前插入的旧文档

2. 删除文档

可以删除单个或批量文档,但有时会发现文档删除后,索引存储空间反而增大。因为es采用段数据结构。每个段都是独立且不可变的索引结构,旨在提高查询性能。

在删除文档时,es并不立即将其从物理存储中移除,而是将其标记为"已删除",并继续保留在段中。随后,在段合并的过程中,已删除文档才会被彻底移除并释放相应的存储空间。这种设计策略是es为了提高查询性能和避免频繁修改索引结构而采用的。

dsl 复制代码
DELETE index/_doc/1

# 批量删除
POST index/_delete_by_query
{
  "query":{
    "match":{
      "name":"lwy"
    }
  }
}

删除操作的实现代码的关键在于"_version+1"。文档不可见、不可被检索,但文档版本是更新的,直到段合并,文档才会彻底被物理删除。

批量删除了很大数据量级的数据,事后发现出错想取消,怎么办呢

dsl 复制代码
# 找到对应的delete task
GET _tasks
# 取消指定的task id
POST _tasks/xxxxxxx/_cancel

3. 更新文档

单个文档更新对应的API是update,是针对某条文档的更新;批量文档更新对应的API是update_by_query,是基于检索条件的更新。

更新文档按照文档修改的内容又可以细分为:部分更新文档,即更新文档的部分字段;全部更新文档,即用新文档覆盖原来的旧文档内容。

3.1 更新文档的前置条件

_source字段必须设置为true,这也是默认设置。如果将其手动设置为false,执行update会报错,示例如下

dsl 复制代码
PUT lwy_index
{
  "mappings": {
    "_source": {
      "enabled": false
    }
  }
}

PUT lwy_index/_doc/1
{
  "counter":1,
  "tag":"red"
}

POST lwy_index/_update/1
{
  "doc": {
    "counter":2
  }
}

3.2 单个文档部分更新

  1. 在原有基础上新增字段
dsl 复制代码
PUT lwy_index/_doc/1
{
  "counter":1,
  "tag":"red"
}

POST lwy_index/_update/1
{
  "doc": {
    "name":"lwy"
  }
}
POST lwy_index/_search
  1. 在原有字段基础上部分修改字段值
dsl 复制代码
POST lwy_index/_update/1
{
 "script": {
   "source": "ctx._source.counter += params.count;ctx._source.phone = '12334576'",
   "lang": "painless", 
   "params": {
     "count":4
   }
 }
}

这里使用了Painless脚本。在脚本中有2个并行操作,如下所示。(Painless脚本下章见)

  • 将params.count的值(此处为4)加到counter当前值上。
  • 新增了字段phone,用于存储手机号码信息。
  1. 存在则更新,不存在则插入给定值

upsert的作用等价于update与insert的叠加效果。

dsl 复制代码
DELETE lwy_index 

POST lwy_index/_update/1
{
 "script": {
   "source": "ctx._source.counter += params.count",
   "lang": "painless", 
   "params": {
     "count":4
   }
 },
 "upsert": {
   "counter":1
 }
}

在id=1的文档不存在的前提下,执行插入upsert操作,得到了counter赋值为1的结果。再次借助upsert更新文档,counter就会变成5。

3.3 整个文档更新

整个文档更新本质上是借助index的API实现的,而不是update的API。

dsl 复制代码
# 创建文档
PUT lwy_index/_doc/1
{
  "counter":1,
  "tag":"red"
}
# 更新文档
PUT lwy_index/_doc/1
{
  "counter":1,
  "color":"blue"
}

原有的counter字段及其内容被全部覆盖。

Mapping会如何变化呢?

3个字段在Mapping中都会存在。原因是Mapping不会因为文档的写入或更新操作而导致收缩,除非通过reindex操作将数据迁移到新的索引上。

3.4 批量文档更新

批量更新的本质是基于特定检索条件的更新。相比于之前单个文档更新使用update,批量文档更新则使用update_by_query

  1. 基于Painless脚本批量更新
dsl 复制代码
POST lwy_index/_update_by_query
{
  "script": {
    "source": "ctx._source.counter++",
    "lang": "painless"
  },
  "query": {
    "term": {
      "counter":5
    }
  }
}
  • script:执行脚本
  • source:执行的语句
  • lang:使用的脚本语言
  1. 基于ingest预处理管道批量更新

如果使用script和ingest都能解决,则推荐使用ingest,因为它更好调试和便于理解。

如下所示,使用了set processor。给文档增加字段

dsl 复制代码
PUT _ingest/pipeline/new-add-field
{
  "description": "new add title field",
  "processors":[
    {
      "set":{
        "field":"title",
        "value": "title testing"
      }
    }
    ]
}
POST lwy_index/_update_by_query?pipeline=new-add-field

4.reindex:迁移文档

4.1 reindex的背景和定义

在MySQL中要修改已有的库表中某个字段的类型是比较方便的。数据量小很快就能生效,数据量大,就是时间问题。类似操作在es如何实现呢?需使用reindex操作进行索引数据迁移,有两种不同的方法。

同集群索引数据迁移:es如何将一个索引的数据克隆到另外一个索引?

跨集群索引数据迁移:es如何将一个集群的索引数据克隆到另外一个集群?

若熟悉第三方工具,可能立马会想到elastic-dump、Logstash等。除了上述工具,es自身有何方式来实现呢?业务零掉线的情况下如何实现数据迁移呢?

reindex,本质是将文档从一个索引复制到另一个索引

dsl 复制代码
POST _reindex
{
  "source": {
    "index": "lwy_index"
  },
  "dest":{
    "index": "lwy_index1"
  }
}

源索引是lwy_index,源索引必须存在,目标索引可以不存在,直接自动创建

4.2 同集群索引间的全量数据迁移

dsl 复制代码
POST _reindex
{
  "conflicts": "proceed",
  "source": {
    "index": "lwy_index"
  },
  "dest":{
    "index": "lwy_index1"
  }
}

conflicts请求正文参数可用于指示_reindex继续处理有关版本冲突的下一个文档。

其他错误类型的处理不受conflicts参数的影响。

在请求正文中设置"conflicts":"proceed"时,_reindex将在版本冲突时继续执行。

4.3 同集群索引间基于特定条件的数据迁移

  1. 源索引设置检索条件
dsl 复制代码
POST _reindex
{
  "source": {
    "index": "lwy_index",
    "query": {
      "match": {
        "test": "data"
      }
    }
  },
  "dest":{
    "index": "lwy_index1"
  }
}
  1. 基于script脚本的索引迁移
dsl 复制代码
POST _reindex
{
  "source": {
    "index": "lwy_index"
  },
  "dest":{
    "index": "lwy_index1"
  },
  "script": {
  # reindex的时候把没用文档中user=lwy的字段直接删除,源文档不变
    "source": "if (ctx._source.user == 'lwy') {ctx._source.remove('user')}",
    "lang": "Painless"
  }
}
  1. 基于预处理管道的数据迁移
dsl 复制代码
POST lwy_index/_bulk
{"index": {"_id":1}}
{"title":" foo bar "}

PUT _ingest/pipeline/my-trim-pipeline
{
  "processors": [
    {
    #去掉title字段的前后空格
      "trim": {
        "field": "title"
      }
    }
  ]
}

POST _reindex
{
  "source": {
    "index": "lwy_index"
  },
  "dest": {
    "index": "lwy_index1",
    "pipeline": "my-trim-pipeline"
  }
}

GET lwy_index1/_search

4.4 不同集群间的数据迁移

涉及跨集群数据迁移,必须要提前配置白名单。在elasticsearch.yml配置文件中完成相关配置后,重启才能生效。

arduino 复制代码
reindex.remote.whitelist: "172.17.0.11:9200, 172.17.0.12:9200"
dsl 复制代码
POST _reindex
{
  "source": {
    "remote": {
      "host": "http://otherhost:9200"
    }, 
    "index": "lwy_index",
    "size": 10
  },
  "dest":{
    "index": "lwy_index1"
  }
}

4.5 查看及取消reindex

reindex是以任务的形式存在的,当同步或者迁移的数据量非常大的时候,reindex任务实际上会在后台运行。

dsl 复制代码
GET _tasks?detailed=true&actions=*reindex
# 拿到任务ID

POST _tasks/任务ID/_cancel

4.6 业务零掉线情况下的数据迁移

需要做数据迁移,但是不想对外停服,也不想重启es,怎么解决?

这个时候就体现出"reindex+别名"的重要性了。以别名的形式从后台向前台提供服务。同时,新建索引并设定好Mapping,然后进行数据reindex迁移操作。

相关推荐
java1234_小锋12 分钟前
Elasticsearch中的节点(比如共20个),其中的10个选了一个master,另外10个选了另一个master,怎么办?
大数据·elasticsearch·jenkins
Elastic 中国社区官方博客13 分钟前
Elasticsearch 开放推理 API 增加了对 IBM watsonx.ai Slate 嵌入模型的支持
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
我的运维人生14 分钟前
Elasticsearch实战应用:构建高效搜索与分析平台
大数据·elasticsearch·jenkins·运维开发·技术共享
Mephisto.java5 小时前
【大数据学习 | Spark】Spark的改变分区的算子
大数据·elasticsearch·oracle·spark·kafka·memcache
mqiqe5 小时前
Elasticsearch 分词器
python·elasticsearch
小马爱打代码5 小时前
Elasticsearch简介与实操
大数据·elasticsearch·搜索引擎
java1234_小锋14 小时前
Elasticsearch是如何实现Master选举的?
大数据·elasticsearch·搜索引擎
梦幻通灵20 小时前
ES分词环境实战
大数据·elasticsearch·搜索引擎
Elastic 中国社区官方博客20 小时前
Elasticsearch 中的热点以及如何使用 AutoOps 解决它们
大数据·运维·elasticsearch·搜索引擎·全文检索
小黑屋说YYDS1 天前
ElasticSearch7.x入门教程之索引概念和基础操作(三)
elasticsearch