了解以下几个概念
1. 索引 index
简单的可以理解为关系型数据库的中库或者表。一个elasticsearch集群中可以有多个索引。
2. 文档 docment
可以理解为表中的行数据,表示一个对象。每个文档有一个唯一标识_id,相当于关系数据库的主键。一个索引中可以有多个结构相同的文档。
3. 域 field
这个可以和关系数据库的字段的概念相对应。一个文档可以包含多个字段,也可以包含其他对象。
6.0以前的版本中索引下存在类型(type)的概念,一个索引下可以有多个类型,一个类型下有结构一样的多个文档,所以6.0以前的概念中索引可以理解为数据库,类型可以理解为表,文档可以理解为行数据,文档中的属性可以理解为字段。6.0+版本中一个索引只能有一个类型,而且官方文档中说,以后会去掉类型的概念。
索引一个文档
在kibana的dev-tools中录入以下信息,并点击绿色按钮执行(或者按ctrl + enter)
POST /employee/doc/1
{
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
上面的代码包括以下信息:
employee
索引名
doc
类型名
1
员工id
first_name
字段名
上面的操作就完成了一个文档的索引工作,这里不需要指定每个字段的数据类型,elsaticsearch会为每个字段动态映射为合适的类型。
查看一个文档
录入以下语句并执行
GET /employee/doc/1
在dev-tools界面你应该看到如下的响应信息
{
"_index": "employee",
"_type": "doc",
"_id": "1",
"_version": 1,
"found": true,
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [
"sports",
"music"
]
}
}
elasticsearch 核心简单域类型
- 字符: text, keyword
- 整数: byte, short, integer, long
- 浮点数: float, double
- 日期: date
上面索引一个文档中, 没有指定字段的数据类型,使用的是elasticsearch动态映射,如果要查看索引employee中字段的类型可以使用如下语句
GET /employee/_mapping
可以得到如下的响应数据, 从中可以看到elasticsearch把age动态映射为long
类型,其余均为text
类型。同时每个text类型下有一个名为keyword的字段,这个字段是不会被分词器分词的,经常应用在如下场景:数据展示、聚合统计、完全匹配搜索。
{
"employee": {
"mappings": {
"doc": {
"properties": {
"about": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"age": {
"type": "long"
},
"first_name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"interests": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"last_name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
}
可以使用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动态映射。
PUT /employee/
{
"mappings": {
"doc": {
"properties": {
"age": {
"type": "integer"
},
"first_name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"interests": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
成功执行后,通过GET /employee/_mapping
查看employee的映射,可以看到,age字段的type为integer。
...
"age": {
"type": "integer"
},
...
在字段映射中可使用copy_to参数将多个字段合并成一个字段。
PUT /my_index
{
"mappings": {
"person": {
"properties": {
"first_name": {
"type": "string",
"copy_to": "full_name"
},
"last_name": {
"type": "string",
"copy_to": "full_name"
},
"full_name": {
"type": "string"
}
}
}
}
}
动态模板
可以使用dynamic_templates来预定义新检测字段的映射。比如通过字段名称或数据类型来指定不同的映射。
每个模板有一个mapping来指定映射应该怎样使用,以及至少一个参数(如match
)来定义这个模板适用于哪些字段。
模板按照顺序来检测;第一个匹配的模板会被启用。
关于动态模板的更详细信息可以查看官方文档https://www.elastic.co/guide/en/elasticsearch/reference/6.1/dynamic-templates.html
PUT /testindex
{
"mappings": {
"_default_": {
"dynamic_templates": [
{
"int_fields": {
"match": "int_*",
"match_mapping_type": "string",
"mapping": {
"type": "integer"
}
}
},
{
"string_fields": {
"match": "*",
"match_mapping_type": "string",
"mapping": {
"type": "text",
"norms": false,
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
],
"properties": {...}
}
}
}
match 参数只匹配字段名称
path_match 参数匹配字段在对象上的完整路径,所以 address.*.name 将匹配这样的字段
此外还可以使作unmatch 和 path_unmatch,它们将被用于未被匹配的字段
更新一个文档
elasticsearch 中文档是不可改变的,不能修改它们。 如果想要更新现有的文档,需要重建索引或者进行替换。
执行索引一个文档中的语句,向employee索引加入一个文档。现在需要把id为1的文档中的age修改为26,interests里增加art, 只需执行下面的语句即可完成
POST /employee/doc/1
{
"first_name" : "John",
"last_name" : "Smith",
"age" : 26,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music", "art" ]
}
在内部,Elasticsearch 已将旧文档标记为已删除,并增加一个全新的文档。 尽管你不能再对旧版本的文档进行访问,但它并不会立即消失。当继续索引更多的数据,Elasticsearch 会在后台清理这些已删除文档。
批量增加文档
批量操作可以执行index
, update
, delete
操作。在发送批量操作请求时,header中的ContentType应该为application/x-ndjson。请求体的格式为
{ action: { metadata }}\n
{ request body }\n
{ action: { metadata }}\n
{ request body }\n
各个json之间用换行符(\n)连接到一起,要注意以下两点
- 每行一定要以换行符(\n)结尾, 包括最后一行 。
- 这些换行符被用作一个标记,可以有效分隔行。 这些行不能包含未转义的换行符,因为他们将会对解析造成干扰。这意味着这个 JSON不能使用pretty参数打印。
下面的示例向employee索引新增2个文档
POST /employee/doc/_bulk
{"index":{}}
{"first_name":"Jane","last_name":"Smith", "age":32,"about":"I like to collect rock albums","interests":[ "music" ]}
{"index":{}}
{"first_name":"Douglas","last_name":"Fir","age":35,"about":"I like to build cabinets","interests":[ "forestry" ]}
上面的语句也可以用下面的语句替换,不同之处就在于指定index和type的方式不同。
POST _bulk
{"index":{"_index":"employee", "_type":"doc"}}
{"first_name":"Jane","last_name":"Smith", "age":32,"about":"I like to collect rock albums","interests":[ "music" ]}
{"index":{"_index":"employee", "_type":"doc"}}
{"first_name":"Douglas","last_name":"Fir","age":35,"about":"I like to build cabinets","interests":[ "forestry" ]}
响应结果
{
"took": 105,
"errors": false,
"items": [
{
"index": {
"_index": "employee",
"_type": "doc",
"_id": "pCNo6GABRuyKw8kPyThk",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1,
"status": 201
}
},
{
"index": {
"_index": "employee",
"_type": "doc",
"_id": "pSNo6GABRuyKw8kPyThk",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1,
"status": 201
}
}
]
}
每个子请求都是独立执行,因此某个子请求的失败不会对其他子请求的成功与否造成影响。 如果其中任何子请求失败,最顶层的 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 操作:
POST /_aliases
{
"actions": [
{ "remove": { "index": "my_index_v1", "alias": "my_index" }},
{ "add": { "index": "my_index_v2", "alias": "my_index" }}
]
}
你可以检测这个别名指向哪一个索引:GET /*/_alias/my_index
或哪些别名指向这个索引:GET /my_index_v1/_alias/*
两者都会返回下面的结果:
{
"my_index_v1" : {
"aliases" : {
"my_index" : { }
}
}
}