公号:码农充电站pro
主页:https://codeshellme.github.iohtml
本节介绍 ES 文档,索引及其基本操做。python
在 ES 中,文档(Document)是可搜索数据的最小存储单位,至关于关系数据库中的一条记录。git
文档以 Json 数据格式保存在 ES 中,Json 中保存着多个键值对,它能够保存不一样类型的数据,好比:github
Python 语言中的字典类型,就是 Json 数据格式。shell
文档中的数据类型能够指定,也能够由 ES 自动推断。数据库
每一个文档中都有一个 Unique ID,用于惟一标识一个文档。Unique ID 能够由用户指定,也能够由 ES 自动生成。json
Unique ID 其实是一个字符串。api
好比下面的 Json 就是一个文档:数组
{ "name" : "XiaoMing", "age" : 19, "gender" : "male" }
将上面那个 Json 数据存储到 ES 后,会像下面这样:安全
{ "_index": "person", "_type": "_doc", "_id": "2344563", "_version": 1, "_source": { "name": "XiaoMing", "age": 19, "gender": "male" } }
其中如下划线开头的字段就是元数据:
_index
:文档所属的索引。_type
:文档的类型。ES 7.0 开始,一个索引只能有一种 _type
。_id
:文档的惟一 ID。_source
:文档的原始 Json 数据。_version
:文档更新的次数。你能够查看这里,了解“为何单个Index下,再也不支持多个Tyeps?”。
更多关于元数据的信息,能够参考这里。
ES 中文档的删除操做不会立刻将其删除,而是会将其标记到 del 文件中,在后期合适的时候(好比 Merge 阶段)会真正的删除。
ES 中的文档是不可变动的,更新操做会将旧的文档标记为删除,同时增长一个新的字段,而且文档的 version 加 1。
在 ES 中,一个文档默认最多能够有 1000 个字段,能够经过 index.mapping.total_fields.limit 进行设置。
注意在设计 ES 中的数据结构时,不要使文档的字段数过多,这样会使得 mapping 很大,增长集群的负担。
ES 中的文档都会存储在某个索引(Index)中,索引是文档的容器,是一类文档的集合,至关于关系型数据库中的表的概念。
ES 中能够建立不少不一样的索引,表示不一样的文档集合。
每一个索引均可以定义本身的 Mappings 和 Settings:
Mappings
:用于设置文档字段的类型。Settings
:用于设置不一样的数据分布。对于索引的一些参数设置,有些参数能够动态修改,有些参数在索引建立后不能修改,可参考这里。
ES 与传统数据库类比
若是将 ES 中的基本概念类比到传统数据库中,它们的对应关系以下:
ES | 传统数据库 |
---|---|
索引 | 表 |
文档 | 行 |
字段 | 列 |
Mapping | 表定义 |
DSL | SQL 语句 |
索引相关 API
下面给出一些查看索引相关信息的 API:
# 查看索引相关信息 GET index_name # 查看索引的文档总数 GET index_name/_count # 查看指定索引的前10条文档 POST index_name/_search { } #_cat indices API # 查看全部的索引名以 index_prefix 为前缀的索引 GET /_cat/indices/index_prefix*?v&s=index # 查看状态为 green 的索引 GET /_cat/indices?v&health=green # 按照文档个数排序 GET /_cat/indices?v&s=docs.count:desc # 查看指定索引的指定信息 GET /_cat/indices/index_prefix*?pri&v&h=health,index,pri,rep,docs.count,mt # 查看索引使用的内存大小 GET /_cat/indices?v&h=i,tm&s=tm:desc
GET 操做能够获取指定文档的内容。
GET index_name/_count
:获取指定索引中的文档数。
GET index_name/_doc/id
:获取指定索引中的指定文档。
GET index_name/_doc
:不容许该操做。
GET index_name
:获取指定索引的 Mappings
和 Settings
。
POST/PUT 操做用于建立文档。
按照 POST / PUT 方法来区分
POST index_name/_doc
:
POST index_name/_doc
:不指定 ID,老是会插入新的文档,文档数加 1。POST/PUT index_name/_doc/id
:指定 ID
PUT index_name/_create
:
PUT index_name/_create
:不指定 ID,不容许该操做。PUT index_name/_create/id
:指定 ID
PUT index_name/_doc
:
PUT index_name/_doc
:不指定 ID,不容许该操做。PUT/POST index_name/_doc/id
:指定 ID
PUT index_name/_doc/id?op_type=XXX
op_type=create
:
op_type=index
:
按照是否指定 ID 来区分
指定 ID:
POST/PUT index_name/_doc/id
:指定 ID,称为 Index 操做
PUT index_name/_doc/id?op_type=index
PUT index_name/_doc/id?op_type=create
:指定 ID,称为 Create 操做
PUT index_name/_create/id
不指定 ID:
POST index_name/_doc
:不指定 ID,老是会插入新的文档,文档数加 1。PUT index_name/_doc
:不指定 ID,不容许该操做。PUT index_name/_create
:不指定 ID,不容许该操做。Update 操做用于更新文档的内容。
POST index_name/_update/id/
:更新指定文档的内容。更新的内容要放在 doc 字段中,不然会报错。
实际上 ES 中的文档是不可变动的,更新操做会将旧的文档标记为删除,同时增长一个新的字段,而且文档的 version 加 1。
Delete 操做用于删除索引或文档。
DELETE /index_name/_doc/id
:删除某个文档。
not_found
。DELETE /index_name
:删除整个索引,要谨慎使用!
404
错误。批量操做指的是,在一次 API 调用中,对不一样的索引进行屡次操做。
每次操做互不影响,即便某个操做出错,也不影响其余操做。
返回的结果中包含了全部操做的执行结果。
Bulk 支持的操做有 Index
,Create
,Update
,Delete
。
Bulk 操做的格式以下:
POST _bulk { "index" : { "_index" : "test", "_id" : "1" } } { "field1" : "value1" } { "delete" : { "_index" : "test", "_id" : "2" } } { "create" : { "_index" : "test2", "_id" : "3" } } { "field1" : "value3" } { "update" : {"_id" : "1", "_index" : "test"} } { "doc" : {"field2" : "value2"} }
注意 Bulk 请求体的数据量不宜过大,建议在 5~15M。
Mget 一次读取多个文档的内容,设计思想相似 Bulk 操做。
Mget 操做的格式以下:
GET _mget { "docs" : [ {"_index" : "index_name1", "_id" : "1"}, {"_index" : "index_name2", "_id" : "2"} ] }
也能够在 URI 中指定索引名称:
GET /index_name/_mget { "docs" : [ {"_id" : "1"}, {"_id" : "2"} ] }
还能够用 _source
字段来设置返回的内容:
GET _mget { "docs" : [ {"_index" : "index_name1", "_id" : "1"}, {"_index" : "index_name2", "_id" : "2", "_source" : ["f1", "f2"]} ] }
Msearch 操做用于批量查询,格式以下:
POST index_name1/_msearch {} # 索引名称,不写的话就是 URI 中的索引 {"query" : {"match_all" : {}},"size":1} {"index" : "index_name2"} # 改变了索引名称 {"query" : {"match_all" : {}},"size":2}
URI 中也能够不写索引名称,此时请求体里必须写索引名称:
POST _msearch {"index" : "index_name1"} # 索引名称 {"query" : {"match_all" : {}},"size":1} {"index" : "index_name2"} # 索引名称 {"query" : {"match_all" : {}},"size":2}
上文中介绍了 3 种批量操做,分别是 Bulk,Mget,Msearch。注意在使用批量操做时,数据量不宜过大,避免出现性能问题。
当咱们的请求发生错误的时候,ES 会返回相应的错误码,常见的错误码以下:
错误码 | 含义 |
---|---|
429 | 集群过于繁忙 |
4XX | 请求格式错误 |
500 | 集群内部错误 |
有时候咱们须要重建索引,好比如下状况:
mappings
发生改变:好比字段类型或者分词器等发生更改。settings
发生改变:好比索引的主分片数发生更改。ES 中提供两种重建 API:
先在一个索引中插入数据:
DELETE blogs/ # 写入文档 PUT blogs/_doc/1 { "content":"Hadoop is cool", "keyword":"hadoop" } # 查看自动生成的 Mapping GET blogs/_mapping # 查询文档 POST blogs/_search { "query": { "match": { "content": "Hadoop" } } } # 能够查到数据
如今修改 mapping(添加子字段是容许的),为 content 字段加入一个子字段:
# 修改 Mapping,增长子字段,使用英文分词器 PUT blogs/_mapping { "properties" : { "content" : { # content 字段 "type" : "text", "fields" : { # 加入一个子字段 "english" : { # 子字段名称 "type" : "text", # 子字段类型 "analyzer":"english" # 子字段分词器 } } } } } # 查看新的 Mapping GET blogs/_mapping
修改 mapping 以后再查询文档:
# 使用 english 子字段查询 Mapping 变动前写入的文档 # 查不到文档 POST blogs/_search { "query": { "match": { "content.english": "Hadoop" } } } # 注意:不使用 english 子字段是能够查询到以前的文档的 POST blogs/_search { "query": { "match": { "content": "Hadoop" } } }
结果发现,使用 english 子字段是查不到以前的文档的。这时候就须要重建索引。
下面使用 Update by query
对索引进行重建:
# Update全部文档 POST blogs/_update_by_query { }
重建索引以后,不论是使用 english 子字段仍是不使用,均可以查出文档。
Update by query
操做还能够设置一些条件:
request-body 示例:
POST tech_blogs/_update_by_query?pipeline=blog_pipeline { "query": { # 将 query 的查询结果进行重建 "bool": { "must_not": { "exists": {"field": "views"} } } } }
在原有 mapping 上,修改字段类型是不容许的:
# 会发生错误 PUT blogs/_mapping { "properties" : { "content" : { "type" : "text", "fields" : { "english" : { "type" : "text", "analyzer" : "english" } } }, "keyword" : { # 修改 keyword 字段的类型 "type" : "keyword" } } }
这时候只能建立一个新的索引,设置正确的字段类型,而后再将原有索引中的数据,重建到新索引中。
创建一个新的索引 blogs_new:
# 建立新的索引而且设定新的Mapping PUT blogs_new/ { "mappings": { "properties" : { "content" : { "type" : "text", "fields" : { "english" : { "type" : "text", "analyzer" : "english" } } }, "keyword" : { "type" : "keyword" } } } }
下面使用 Reindex 将原来索引中的数据,导入到新的索引中:
# Reindx API POST _reindex { "source": { # 指定原有索引 "index": "blogs" }, "dest": { # 指定目标索引 "index": "blogs_new" } }
Reindex API 中的 source 字段和 dest 字段还有不少参数能够设置,具体可参考其官方文档。
另外 Reindex 请求的 URI 中也能够设置参数,能够参考这里。
同一个资源在多并发处理的时候,会发生冲突的问题。
传统数据库(好比 MySQL)会采用锁的方式,在更新数据的时候对数据进行加锁,来防止冲突。
而 ES 并无采用锁,而是将并发问题交给了用户处理。
在 ES 中能够采用两种方式:
if_seq_no
和 if_primary_term
version
和 version_type=external
示例,首先插入数据:
DELETE products PUT products/_doc/1 { "title":"iphone", "count":100 } # 上面的插入操做会返回 4 个字段: #{ # "_id" : "1", # "_version" : 1, # "_seq_no" : 0, # "_primary_term" : 1 #}
使用内部版本控制的方式:
PUT products/_doc/1?if_seq_no=0&if_primary_term=1 { "title":"iphone", "count":100 } # 上面的更新操做返回下面内容: #{ # "_id" : "1", # "_version" : 2, # 加 1 # "_seq_no" : 1, # 加 1 # "_primary_term" : 1 # 不变 #}
若是再次执行这句更新操做,则会出错,出错以后由用户决定如何处理,这就达到了解决冲突的目的。
# 再执行则会出错,由于 seq_no=0 且 primary_term=1 的数据已经不存在了 PUT products/_doc/1?if_seq_no=0&if_primary_term=1
先看下数据库中的数据:
GET products/_doc/1 # 返回: { "_index" : "products", "_type" : "_doc", "_id" : "1", # id "_version" : 2, # version "_seq_no" : 1, "_primary_term" : 1, "found" : true, "_source" : { "title" : "iphone", "count" : 100 } }
使用外部版本控制的方式:
# 若是 URI 中的 version 值与 ES 中的 version 值相等,则出错 # 下面这句操做会出错,出错以后,由用户决定如何处理 PUT products/_doc/1?version=2&version_type=external { "title":"iphone", "count":1000 } # 若是 URI 中的 version 值与 ES 中的 version 值不相等,则成功 # 下面这句操做会成功 PUT products/_doc/1?version=3&version_type=external { "title":"iphone", "count":1000 }
Ingest 节点用于对数据预处理,它是在 ES 5.0 后引入的一种节点类型,能够达到必定的 Logstash 的功能。
默认状况下,全部的节点都是 Ingest 节点。
Ingest 节点经过添加一些 processors 来完成特定的处理,Pipeline 能够看作是一组 processors 的顺序执行。
Ingest 节点的处理阶段以下图所示:
ES 中内置了不少现成的 Processors 供咱们使用:
ES 中提供了一个 simulate 接口,用于测试 Processors。
示例:
POST _ingest/pipeline/_simulate { "pipeline": { # 定义 pipeline "description": "to split blog tags", # 描述 "processors": [ # 一系列的 processors { "split": { # 一个 split processor "field": "tags", "separator": "," # 用逗号分隔 } }, { "set":{ # 能够设置多个 processor "field": "views", "value": 0 } } ] }, "docs": [ # 测试的文档 { # 第 1 个文档 "_index": "index", "_id": "id", "_source": { "title": "Introducing big data......", "tags": "hadoop,elasticsearch,spark", "content": "You konw, for big data" } }, { # 第 2 个文档 "_index": "index", "_id": "idxx", "_source": { "title": "Introducing cloud computering", "tags": "openstack,k8s", "content": "You konw, for cloud" } } ] }
当 Processors 测试经过后,能够向 ES 中添加(设置)一个 Pipeline,语法:
# blog_pipeline 为 pipeline 名称 PUT _ingest/pipeline/blog_pipeline { "description": "a blog pipeline", "processors": [ { "split": { # 第 1个 Processor "field": "tags", "separator": "," } }, { "set":{ # 第 2个 Processor "field": "views", "value": 0 } } ] }
# 查看 Pipleline GET _ingest/pipeline/blog_pipeline # 删除 Pipleline DELETE _ingest/pipeline/blog_pipeline
# blog_pipeline 是 Pipeline 名称 POST _ingest/pipeline/blog_pipeline/_simulate { "docs": [ { # 一个文档 "_source": { "title": "Introducing cloud computering", "tags": "openstack,k8s", "content": "You konw, for cloud" } } ] }
使用 Pipeline 插入文档时,文档会先通过 Pipeline 的处理,而后再插入到 ES 中。
# URI 中指定了 Pipeline 的名字 PUT tech_blogs/_doc/2?pipeline=blog_pipeline { "title": "Introducing cloud computering", "tags": "openstack,k8s", "content": "You konw, for cloud" }
最终插入的文档是这样的:
{ "title": "Introducing cloud computering", "tags": ["openstack", "k8s"], "content": "You konw, for cloud", "views": 0 }
另外 update-by-query(重建索引)的 URI 中也能够设置 pipeline 参数来使用一个 Pipeline。
上文介绍到的全部操做,能够参考 ES 的官方文档。
(本节完。)
推荐阅读:
Kibana,Logstash 和 Cerebro 的安装运行
欢迎关注做者公众号,获取更多技术干货。