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操作。
- 指定索引批量插入数据
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"}
- 批量执行多种操作
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 单个文档部分更新
- 在原有基础上新增字段
dsl
PUT lwy_index/_doc/1
{
"counter":1,
"tag":"red"
}
POST lwy_index/_update/1
{
"doc": {
"name":"lwy"
}
}
POST lwy_index/_search
- 在原有字段基础上部分修改字段值
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,用于存储手机号码信息。
- 存在则更新,不存在则插入给定值
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
- 基于Painless脚本批量更新
dsl
POST lwy_index/_update_by_query
{
"script": {
"source": "ctx._source.counter++",
"lang": "painless"
},
"query": {
"term": {
"counter":5
}
}
}
- script:执行脚本
- source:执行的语句
- lang:使用的脚本语言
- 基于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 同集群索引间基于特定条件的数据迁移
- 源索引设置检索条件
dsl
POST _reindex
{
"source": {
"index": "lwy_index",
"query": {
"match": {
"test": "data"
}
}
},
"dest":{
"index": "lwy_index1"
}
}
- 基于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"
}
}
- 基于预处理管道的数据迁移
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迁移操作。