了解以下几个概念
1. 索引 index
简单的可以理解为关系型数据库的中库或者表。一个elasticsearch集群中可以有多个索引。
2. 文档 docment
可以理解为表中的行数据,表示一个对象。每个文档有一个唯一标识_id,相当于关系数据库的主键。一个索引中可以有多个结构相同的文档。
3. 域 field
这个可以和关系数据库的字段的概念相对应。一个文档可以包含多个字段,也可以包含其他对象。
6.0以前的版本中索引下存在类型(type)的概念,一个索引下可以有多个类型,一个类型下有结构一样的多个文档,所以6.0以前的概念中索引可以理解为数据库,类型可以理解为表,文档可以理解为行数据,文档中的属性可以理解为字段。6.0+版本中一个索引只能有一个类型,而且官方文档中说,以后会去掉类型的概念。
<span id="index-a-doc"></span>
索引一个文档
在kibana的dev-tools中录入以下信息,并点击绿色按钮执行(或者按ctrl + enter)
1POST /employee/doc/1 2{ 3 "first_name" : "John", 4 "last_name" : "Smith", 5 "age" : 25, 6 "about" : "I love to go rock climbing", 7 "interests": [ "sports", "music" ] 8}
上面的代码包括以下信息:
employee 索引名
doc 类型名
1 员工id
first_name 字段名
上面的操作就完成了一个文档的索引工作,这里不需要指定每个字段的数据类型,elsaticsearch会为每个字段动态映射为合适的类型。
查看一个文档
录入以下语句并执行
GET /employee/doc/1
在dev-tools界面你应该看到如下的响应信息
1{ 2 "_index": "employee", 3 "_type": "doc", 4 "_id": "1", 5 "_version": 1, 6 "found": true, 7 "_source": { 8 "first_name": "John", 9 "last_name": "Smith", 10 "age": 25, 11 "about": "I love to go rock climbing", 12 "interests": [ 13 "sports", 14 "music" 15 ] 16 } 17}
elasticsearch 核心简单域类型
- 字符: text, keyword
- 整数: byte, short, integer, long
- 浮点数: float, double
- 日期: date
上面索引一个文档中, 没有指定字段的数据类型,使用的是elasticsearch动态映射,如果要查看索引employee中字段的类型可以使用如下语句
GET /employee/_mapping
可以得到如下的响应数据, 从中可以看到elasticsearch把age动态映射为long类型,其余均为text类型。同时每个text类型下有一个名为keyword的字段,这个字段是不会被分词器分词的,经常应用在如下场景:数据展示、聚合统计、完全匹配搜索。
1{ 2 "employee": { 3 "mappings": { 4 "doc": { 5 "properties": { 6 "about": { 7 "type": "text", 8 "fields": { 9 "keyword": { 10 "type": "keyword", 11 "ignore_above": 256 12 } 13 } 14 }, 15 "age": { 16 "type": "long" 17 }, 18 "first_name": { 19 "type": "text", 20 "fields": { 21 "keyword": { 22 "type": "keyword", 23 "ignore_above": 256 24 } 25 } 26 }, 27 "interests": { 28 "type": "text", 29 "fields": { 30 "keyword": { 31 "type": "keyword", 32 "ignore_above": 256 33 } 34 } 35 }, 36 "last_name": { 37 "type": "text", 38 "fields": { 39 "keyword": { 40 "type": "keyword", 41 "ignore_above": 256 42 } 43 } 44 } 45 } 46 } 47 } 48 } 49}
可以使用dynamic配置来控制动态映射行为,可以有如下选项:
-
true 动态添加新字段,默认值
-
false 忽略新字段
-
strict 如果遇到新字段抛出异常 配置参数 dynamic 可以用在++根object++或任何++object类型的字段++上。你可以将 dynamic 的默认值设置为 strict , 而只在指定的内部对象中开启它
PUT /my_index { "mappings": { "my_type": { "dynamic": "strict", "properties": { "title": { "type": "string"}, "stash": { "type": "object", "dynamic": true } } } } }
把 dynamic 设置为 false 一点儿也不会改变 _source 的字段内容。 _source 仍然包含被索引的整个JSON文档。只是新的字段不会被加到映射中也不可搜索。
日期检测可以通过在根对象上设置 date_detection 为 false 来关闭。判断字符串为日期的规则可以通过 dynamic_date_formats setting 来设置。
自定义映射
只能在新建一个索引时,或一个索引中的文档新增字段时为字段做类型映射,不可以修改一个的字段的类型。 删除employee索引
DELETE /employee
为employee索引自字义映射,这里指定age字段为integer类型,first_name与interests为text类型,其余字段使用elasticsearch动态映射。
1PUT /employee/ 2{ 3 "mappings": { 4 "doc": { 5 "properties": { 6 "age": { 7 "type": "integer" 8 }, 9 "first_name": { 10 "type": "text", 11 "fields": { 12 "keyword": { 13 "type": "keyword", 14 "ignore_above": 256 15 } 16 } 17 }, 18 "interests": { 19 "type": "text", 20 "fields": { 21 "keyword": { 22 "type": "keyword", 23 "ignore_above": 256 24 } 25 } 26 } 27 } 28 } 29 } 30}
成功执行后,通过GET /employee/_mapping查看employee的映射,可以看到,age字段的type为integer。
1... 2"age": { 3 "type": "integer" 4 }, 5...
在字段映射中可使用copy_to参数将多个字段合并成一个字段。
1PUT /my_index 2{ 3 "mappings": { 4 "person": { 5 "properties": { 6 "first_name": { 7 "type": "string", 8 "copy_to": "full_name" 9 }, 10 "last_name": { 11 "type": "string", 12 "copy_to": "full_name" 13 }, 14 "full_name": { 15 "type": "string" 16 } 17 } 18 } 19 } 20}
动态模板
可以使用dynamic_templates来预定义新检测字段的映射。比如通过字段名称或数据类型来指定不同的映射。
每个模板有一个mapping来指定映射应该怎样使用,以及至少一个参数(如match)来定义这个模板适用于哪些字段。
模板按照顺序来检测;第一个匹配的模板会被启用。
关于动态模板的更详细信息可以查看官方文档https://www.elastic.co/guide/en/elasticsearch/reference/6.1/dynamic-templates.html
1PUT /testindex 2{ 3 "mappings": { 4 "_default_": { 5 "dynamic_templates": [ 6 { 7 "int_fields": { 8 "match": "int_*", 9 "match_mapping_type": "string", 10 "mapping": { 11 "type": "integer" 12 } 13 } 14 }, 15 { 16 "string_fields": { 17 "match": "*", 18 "match_mapping_type": "string", 19 "mapping": { 20 "type": "text", 21 "norms": false, 22 "fields": { 23 "keyword": { 24 "type": "keyword", 25 "ignore_above": 256 26 } 27 } 28 } 29 } 30 } 31 ], 32 "properties": {...} 33 } 34 } 35}
match 参数只匹配字段名称
path_match 参数匹配字段在对象上的完整路径,所以 address.*.name 将匹配这样的字段
此外还可以使作unmatch 和 path_unmatch,它们将被用于未被匹配的字段
更新一个文档
elasticsearch 中文档是不可改变的,不能修改它们。 如果想要更新现有的文档,需要重建索引或者进行替换。
执行索引一个文档中的语句,向employee索引加入一个文档。现在需要把id为1的文档中的age修改为26,interests里增加art, 只需执行下面的语句即可完成
1POST /employee/doc/1 2{ 3 "first_name" : "John", 4 "last_name" : "Smith", 5 "age" : 26, 6 "about" : "I love to go rock climbing", 7 "interests": [ "sports", "music", "art" ] 8}
在内部,Elasticsearch 已将旧文档标记为已删除,并增加一个全新的文档。 尽管你不能再对旧版本的文档进行访问,但它并不会立即消失。当继续索引更多的数据,Elasticsearch 会在后台清理这些已删除文档。
批量增加文档
批量操作可以执行index, update, delete操作。在发送批量操作请求时,header中的ContentType应该为application/x-ndjson。请求体的格式为
1{ action: { metadata }}\n 2{ request body }\n 3{ action: { metadata }}\n 4{ request body }\n
各个json之间用换行符(\n)连接到一起,要注意以下两点
- 每行一定要以换行符(\n)结尾, 包括最后一行 。
- 这些换行符被用作一个标记,可以有效分隔行。 这些行不能包含未转义的换行符,因为他们将会对解析造成干扰。这意味着这个 JSON不能使用pretty参数打印。
下面的示例向employee索引新增2个文档
1POST /employee/doc/_bulk 2{"index":{}} 3{"first_name":"Jane","last_name":"Smith", "age":32,"about":"I like to collect rock albums","interests":[ "music" ]} 4{"index":{}} 5{"first_name":"Douglas","last_name":"Fir","age":35,"about":"I like to build cabinets","interests":[ "forestry" ]} 6
上面的语句也可以用下面的语句替换,不同之处就在于指定index和type的方式不同。
1POST _bulk 2{"index":{"_index":"employee", "_type":"doc"}} 3{"first_name":"Jane","last_name":"Smith", "age":32,"about":"I like to collect rock albums","interests":[ "music" ]} 4{"index":{"_index":"employee", "_type":"doc"}} 5{"first_name":"Douglas","last_name":"Fir","age":35,"about":"I like to build cabinets","interests":[ "forestry" ]} 6
响应结果
1{ 2 "took": 105, 3 "errors": false, 4 "items": [ 5 { 6 "index": { 7 "_index": "employee", 8 "_type": "doc", 9 "_id": "pCNo6GABRuyKw8kPyThk", 10 "_version": 1, 11 "result": "created", 12 "_shards": { 13 "total": 2, 14 "successful": 1, 15 "failed": 0 16 }, 17 "_seq_no": 0, 18 "_primary_term": 1, 19 "status": 201 20 } 21 }, 22 { 23 "index": { 24 "_index": "employee", 25 "_type": "doc", 26 "_id": "pSNo6GABRuyKw8kPyThk", 27 "_version": 1, 28 "result": "created", 29 "_shards": { 30 "total": 2, 31 "successful": 1, 32 "failed": 0 33 }, 34 "_seq_no": 1, 35 "_primary_term": 1, 36 "status": 201 37 } 38 } 39 ] 40}
每个子请求都是独立执行,因此某个子请求的失败不会对其他子请求的成功与否造成影响。 如果其中任何子请求失败,最顶层的 error 标志被设置为 true ,并且在相应的请求报告出错误明细。这也意味着 bulk 请求不是原子的: 不能用它来实现事务控制。
为什么每行一定要以换行符(\n)结尾
当我们早些时候在代价较小的批量操作章节了解批量请求时, 您可能会问自己, "为什么 bulk API 需要有换行符的有趣格式,而不是发送包装在 JSON 数组中的请求,例如 mget API?" 。
为了搞懂这一点,我们需要解释一点背景:++在批量请求中引用的每个文档可能属于不同的主分片, 每个文档可能被分配给集群中的任何节点。这意味着批量请求 bulk 中的每个 操作 都需要被转发到正确节点上的正确分片++。
如果单个请求被包装在 JSON 数组中,那就意味着我们需要执行以下操作:
- 将 JSON 解析为数组(包括文档数据,可以非常大)
- 查看每个请求以确定应该去哪个分片
- 为每个分片创建一个请求数组
- 将这些数组序列化为内部传输格式
- 将请求发送到每个分片
这是可行的,但需要大量的 RAM来存储原本相同的数据的副本,并将创建更多的数据结构,Java虚拟机(JVM)将不得不花费时间进行垃圾回收。
相反,Elasticsearch可以直接读取被网络缓冲区接收的原始数据。 它使用换行符字符来识别和解析小的 action/metadata 行来决定哪个分片应该处理每个请求。
这些原始请求会被直接转发到正确的分片。没有冗余的数据复制,没有浪费的数据结构。整个请求尽可能在最小的内存中处理。
避免类型陷阱
如果有两个不同的类型,每个类型都有同名的字段,但映射不同(例如:一个是字符串一个是数字),Elasticsearch 不会允许你定义这个映射。当你配置这个映射时,将会出现异常。 对于整个索引,映射在本质上被 扁平化 成一个单一的、全局的模式。这就是为什么两个类型不能定义冲突的字段:当映射被扁平化时,Lucene 不知道如何去处理。
6.0.0以后一个索引只有一个类型,所以不太会出现上面这样的情况
索引别名
索引 别名 就像一个快捷方式或软连接,可以指向一个或多个索引。 别名 带给我们极大的灵活性,允许我们做下面这些:
- 在运行的集群中可以无缝的从一个索引切换到另一个索引
- 给多个索引分组 (例如, last_three_months)
- 给索引的一个子集创建视图
有两种方式管理别名: _alias 用于单个操作, _aliases用于执行多个原子级操作。
设置别名my_index指向my_index_v1:PUT /my_index_v1/_alias/my_index
一个别名可以指向多个索引,所以我们在添加别名到新索引的同时必须从旧的索引中删除它(旧版本的相同的索引)。这个操作需要原子化,这意味着我们需要使用 _aliases 操作:
1POST /_aliases 2{ 3 "actions": [ 4 { "remove": { "index": "my_index_v1", "alias": "my_index" }}, 5 { "add": { "index": "my_index_v2", "alias": "my_index" }} 6 ] 7}
你可以检测这个别名指向哪一个索引:GET /*/_alias/my_index
或哪些别名指向这个索引:GET /my_index_v1/_alias/*
两者都会返回下面的结果:
1{ 2 "my_index_v1" : { 3 "aliases" : { 4 "my_index" : { } 5 } 6 } 7}