ES1_4文档
常用操作 - 对象(文档 Document)
文档
- ES是面向文档的,文档是所有可搜索数据的最小单位。
- 文档会被序列化成JSON格式,保存在ES中;
JSON对象由字段组成,每个字段都有对应的字段类型。 - 每个文档都有一个Unique ID。
这个Unique ID可以自己指定或由ES自动生成。
文档和字段 - Document、Field
一个文档是一个可被索引的基础信息单元,文档以 JSON 格式来表示。
在一个 index/type 里面,可以存储任意多的文档,每个文档都有唯一 id。
每个文档包含多个字段(fields),即 json 数据里的字段。
文档元数据
一个文档不仅仅包含它的数据,也包含 元数据 —— 有关 文档的信息。 三个必须的元数据元素如下:
- _index
一个 索引 应该是因共同的特性被分组到一起的文档集合。
索引名字必须小写,不能以下划线开头,不能包含逗号。 - _type
Lucene 没有文档类型的概念,而是使用一个元数据字段_type 文档表示的对象类别,数据可能在索引中只是松散的组合在一起,但是通常明确定义一些数据中的子分区是很有用的,不同 types 的文档可能有不同的字段,但最好能够非常相似。
一个 _type 命名可以是大写或者小写,但是不能以下划线或者句号开头,不应该包含逗号, 并且长度限制为 256 个字符。
当我们要检索某个类型的文档时, Elasticsearch 通过在 _type 字段上使用过滤器限制只返回这个类型的文档。 - _id
文档唯一标识,和 _index 以及 _type 组合就可以唯一确定 Elasticsearch 中的一个文档。
id 也可以由 Elasticsearch 自动生成。 - _version
在 Elasticsearch 中每个文档都有一个版本号。当每次对文档进行修改时(包括删除), _version 的值会递增。这个字段用来确保这些改变在跨多节点时以正确的顺序执行。
版本号——不管是内部的还是引用外部的——都必须是在(0, 9.2E+18)范围内的一个 long 类型的正数。 - _source
即索引数据时发送给 Elasticsearch 的原始 JSON 文档。 - _score
相关性打分 _all
整合所有字段内容到该字段,已被废除。
文档属性
文档里有几个最重要的设置:
- type
字段的数据类型,例如 string 或 date - index
字段是否应当被当成全文来搜索(analyzed),或被当成一个准确的值(not_analyzed),还是完全不可被搜索( no ) - analyzer
确定在索引和搜索时全文字段使用的 analyzer - _source
存储代表文档体的 JSON 字符串,和所有被存储的字段一样, _source 字段在被写入磁盘之前先会被压缩。这个字段有以下作用:- 搜索结果包括了整个可用的文档——不需要额外的从另一个的数据仓库来取文档。
- 如果没有 _source 字段,部分 update 请求不会生效。
- 当你的映射改变时,你需要重新索引你的数据,有了_source 字段你可以直接从 Elasticsearch 这样做,而不必从另一个(通常是速度更慢的)数据仓库取回你的所有文档。
- 当你不需要看到整个文档时,单个字段可以从 _source 字段提取和通过 get 或者 search 请求返回。
1
2
3
4
5GET /_search
{
"query": { "match_all": {}},
"_source": [ "title", "created" ]
} - 调试查询语句更加简单,因为你可以直接看到每个文档包括什么,而不是从一列 id 猜测它们的内容。
也可以调用下面的映射来禁用_source 字段:1
2
3
4
5
6
7
8
9
10PUT /my_index
{
"mappings": {
"my_type": {
"_source": {
"enabled": false
}
}
}
}
对象和文档
通常情况下,我们使用的术语 对象 和 文档 是可以互相替换的。不过,有一个区别:
一个对象仅仅是类似于 hash 、 hashmap 、字典或者关联数组的 JSON 对象,对象中也可以嵌套其他的对象。 对象可能包含了另外一些对象。
文档指最顶层或者根对象,这个根对象被序列化成 JSON 并存储到 Elasticsearch 中,指定了唯一 ID 及一些必须的文档元数据。
根对象
映射的最高一层被称为 根对象 ,它可能包含下面几项:
- 一个 properties 节点,列出了文档中可能包含的每个字段的映射
- 各种元数据字段,它们都以一个下划线开头,例如 _type 、 _id 和 _source
- 设置项,控制如何动态处理新的字段,例如 analyzer 、 dynamic_date_formats 和 dynamic_templates
- 其他设置,可以同时应用在根对象和其他 object 类型的字段上,例如 enabled 、 dynamic 和 include_in_all
操作类型
文档的CRUD:
- Index
Index操作——如果ID不存在——则创建新的文档,否则删除现有的再创建新的,版本号会增加PUT my_index/_doc/1
- Create
Create操作——如果ID已经存在——会失败PUT my_index/_create/1
不指定ID,自动生成POST my_index/_doc
- Read
GET my_index/_doc/1
- Update
文档必须已经存在,更新只会对相应字段做增量修改POST my_index/_update/1
- Delete
my_index/_doc/1
常见返回
问题 | 原因 |
---|---|
无法连接 | 网络故障或集群挂了 |
连接无法关闭 | 网络故障或节点出错 |
429 | 集群过于繁忙 |
4xx | 请求体格式有问题 |
500 | 集群内部错误 |
更新 - PUT
更新现有的对象需要自己指定对象的 id,如果不存在将自动创建一个,文档更新后_version 字段的值也会相应提高。在内部,Elasticsearch 已将旧文档标记为已删除,并增加一个全新的文档。 尽管你不能再对旧版本的文档进行访问,但它并不会立即消失。当继续索引更多的数据,Elasticsearch 会在后台清理这些已删除文档。
检索 和 重建索引 步骤的间隔越小,变更冲突的机会越小。 但是它并不能完全消除冲突的可能性。 还是有可能在 update 设法重新索引之前,来自另一进程的请求修改了文档。为了避免数据丢失, update API 在 检索 步骤时检索得到文档当前的 _version 号,并传递版本号到 重建索引 步骤的 index 请求。 如果另一个进程修改了处于检索和重新索引步骤之间的文档,那么 _version 号将不匹配,更新请求将会失败。为了实现版本号控制只需要在请求参数中加入 version(如上所示)。
1 | PUT /website/blog/123 |
如果已经有自己的 _id 、而又想执行创建,那么我们必须告诉 Elasticsearch ,只有在相同的 _index 、 _type 和 _id 不存在时才接受我们的索引请求——而不是覆盖掉,有两种方式:
1 | # 指定ID的index操作,其实是个upsert操作 |
文档是不可变的:他们不能被修改,只能被替换。 update API 必须遵循同样的规则。 从外部来看,我们在一个文档的某个位置进行部分更新。然而在内部, update API 简单使用与之前描述相同的 检索-修改-重建索引 的处理过程。 区别在于这个过程发生在分片内部,这样就避免了多次请求的网络开销。通过减少检索和重建索引步骤之间的时间,我们也减少了其他进程的变更带来冲突的可能性。
创建 - POST
不需要指定对象 id,由 Elasticsearch 自动生成,自动生成的 ID 是 URL-safe、 基于 Base64 编码且长度为 20 个字符的 GUID 字符串。 这些 GUID 字符串由可修改的 FlakeID 模式生成,这种模式允许多个节点并行生成唯一 ID ,且互相之间的冲突概率几乎为零。
1 | POST /website/blog/ |
部分更新 - POST
update 请求最简单的一种形式是接收文档的一部分作为 doc 的参数, 它只是与现有的文档进行合并。对象被合并到一起,覆盖现有的字段,增加新的字段。
1 | # 文档必须已经存在 |
使用脚本部分更新文档:脚本可以在 update API 中用来改变 _source 的字段内容, 它在更新脚本中称为 ctx._source ,运行在一个沙盒内,默认使用 Painless 语言作为脚本语言。下面这个脚本在页面不存在时执行新增并初始化 views=1(第一次运行这个请求时, upsert 值作为新文档被索引,初始化 views 字段为 1 ;在后续的运行中,由于文档已经存在, script 更新操作将替代 upsert 进行应用,对 views 计数器进行累加)、页面被浏览 2 次后执行删除,其他情况浏览量+1 并添加一个新标签:
1 | POST /website/blog/zVmOW2EBsZ0GEqF92yf6/_update |
重试:
正如之前所说,update 操作是检索-修改-重新索引的过程, 检索 和 重建索引 步骤的间隔越小,变更冲突的机会越小。 但是它并不能完全消除冲突的可能性。 还是有可能在 update 设法重新索引之前,来自另一进程的请求修改了文档。为了避免数据丢失, update API 在 检索 步骤时检索得到文档当前的 _version 号,并传递版本号到 重建索引 步骤的 index 请求。 如果另一个进程修改了处于检索和重新索引步骤之间的文档,那么 _version 号将不匹配,更新请求将会失败。
对于部分更新的很多使用场景,文档已经被改变也没有关系。 例如,如果两个进程都对页面访问量计数器进行递增操作,它们发生的先后顺序其实不太重要; 如果冲突发生了,我们唯一需要做的就是尝试再次更新。这可以通过 设置参数 retry_on_conflict 来自动完成, 这个参数规定了失败之前 update 应该重试的次数,它的默认值为 0
1 | POST /website/blog/zVmOW2EBsZ0GEqF92yf6/_update?retry_on_conflict=5 |
GET(搜索)
在请求的查询串参数中加上 pretty 参数,这将会调用 Elasticsearch 的 pretty-print 功能,该功能 使得 JSON 响应体更加可读,但其中的 _source 字段并不是被当成字符串打印出来,而是格式化成了 JSON 串:
1 | GET /website/blog/123?pretty |
将多个请求合并成一个,避免单独处理每个请求花费的网络延时和开销。 如果你需要从 Elasticsearch 检索很多文档,那么使用 multi-get 或者 mget API 来将这些检索请求放在一个请求中,将比逐个文档请求更快地检索到全部文档。
mget API 要求有一个 docs 数组作为参数,每个 元素包含需要检索文档的元数据, 包括 _index 、 _type 和 _id 。如果你想检索一个或者多个特定的字段,那么你可以通过 _source 参数来指定这些字段的名字:
1 | GET /_mget |
HEAD(ping)
如果只想检查一个文档是否存在——根本不想关心内容——那么用 HEAD 方法来代替 GET 方法。
1 | HEAD /website/blog/124 |
DELETE(删除)
1 | DELETE /website/blog/123 |
bulk(批量操作)
每一行——包括最后一行——都必须以换行符结尾,格式如下所示:
1 | { action: { metadata }}\n |
action/metadata 行指定 哪一个文档 做 什么操作 。action 必须是以下选项之一:
create:如果文档不存在,那么就创建它。类似POST
或PUT /_create
。
index:创建一个新文档或者替换一个现有的文档。类似POST
或PUT
。
update:部分更新一个文档。类似POST /_update
。
delete:删除一个文档。类似DELETE
。
metadata 应该 指定被索引、创建、更新或者删除的文档的 _index 、 _type 和 _id ,每个请求的 metadata 都会覆盖请求 URL 中带上的默认元数据。
request body 行由文档的 _source 本身组成–文档包含的字段和值。它是 index、create、update 操作所必需的。
为什么不直接用一个 JSON 数组来保存?主要是考虑效率问题,解析为数组需要有更多的 RAM 空间,且 JVM 要花时间进行 gc。而直接使用原始数据只需要多注意每条数据之间的间隔(换行符)。
每个子请求都是独立执行,因此某个子请求的失败不会对其他子请求的成功与否造成影响。 如果其中任何子请求失败,最顶层的 error 标志被设置为 true ,并且在相应的请求报告出错误明细。这也意味着 bulk 请求不是原子的: 不能用它来实现事务控制。每个请求是单独处理的,因此一个请求的成功或失败不会影响其他的请求。
1 | POST /_bulk |
批量请求的大小有一个最佳值,大于这个值,性能将不再提升,甚至会下降。 但是最佳值不是一个固定的值,它完全取决于硬件、文档的大小和复杂度、索引和搜索的负载的整体情况。
幸运的是,很容易找到这个 最佳点 :通过批量索引典型文档,并不断增加批量大小进行尝试。 当性能开始下降,那么你的批量大小就太大了。一个好的办法是开始时将 1,000 到 5,000 个文档作为一个批次, 如果你的文档非常大,那么就减少批量的文档个数。并且请求的文档也最好不要太大,一个好的批量大小在开始处理后所占用的物理大小约为 5-15 MB。