1. 基础概念
1.1. 集群
由若干节点组成,这些节点在同一个网络内,cluster-name 相同
1.2. 节点
运行的ES实例
- Master 节点:集群中的一个节点会被选为 master 节点,它将负责管理集群范畴的变更,例如创建或删除索引,添加节点到集群或从集群删除节点。master 节点无需参与文档层面的变更和搜索,这意味着仅有一个 master 节点并不会因流量增长而成为瓶颈。任意一个节点都可以成为 master 节点
- Data 节点:持有数据和倒排索引。默认情况下,每个节点都可以通过设定配置文件elasticsearch.yml 中的 node.data 属性为 true (默认)成为数据节点。如果需要一个专门的主节点,应将其 node.data 属性设置为 false
- Client 节点:如果将 node.master 属性和 node.data 属性都设置为false,那么该节点就是一个客户端节点,扮演一个负载均衡的角色,将到来的请求路由到集群中的各个节点
类型 |
配置参数 |
默认值 |
作用 |
主节点(Master) |
node.master |
true |
负责集群级别的维护操作, 集群状态、创建和删除索引、节点上分片的分配操作等等 |
数据节点(Data) |
node.data |
true |
保存数据的节点; 处理和数据相关的请求操作,承载写入和查询的重要节点 |
Ingest节点 |
node.ingest |
true |
[TODO] 用于机器学习 |
协调节点(Client) |
node.ml |
true |
[OUTDATE] 处理路由请求; 处理搜索聚合节点; 分发批里索引请求 |
1.3. 分片
每个索引都有多个分片,每个分片是一个 Lucene 索引
-
单个节点由于物理机硬件限制,存储的文档是有限的,如果一个索引包含海量文档,则不能在单个节点存储。ES提供分片机制,同一个索引可以存储在不同分片(数据容器)中,这些分片又可以存储在集群中不同节点上
-
分片分为主分片(primary shard) 以及从分片(replica shard)
-
主分片与从分片关系:从分片只是主分片的一个副本,它用于提供数据的冗余副本
-
从分片应用:在硬件故障时提供数据保护,同时服务于搜索和检索这种只读请求
-
是否可变:索引中的主分片的数量在索引创建后就固定下来了,但是从分片的数量可以随时改变
-
索引默认创建的分片:默认设置5个主分片和一组从分片(即每个主分片有一个从分片对应),但是从分片没有被启用(主从分片在同一个节点上没有意义),因此集群健康值显示为黄色(yellow)
后期默认主分片数应该分变成1
1.4. 索引
含有相同属性的文档集合
1.5. 类型
索引可以定义一个或多个类型,文档必须属于一个类型
> 该概念在 ES 7.0 开始取消
1.6. 文档
文档是可以被索引的基本数据单位
1.7. 备份
拷贝一份分片就完成了分片的备份
1.8. API 基本格式
1
2
|
http://<ip>:<port>/<索引>/<文档id>
http://<ip>:<port>/<索引>/<功能名称>
|
1.9. 常用 HTTP 动词
1.10. 查看当前 ES 版本
1.11. ES优化建议
https://www.elastic.co/guide/en/elasticsearch/reference/7.12/tune-for-indexing-speed.html
2. 索引
https://www.elastic.co/guide/en/elasticsearch/reference/6.4/indices.html
2.1. 结构化索引
类似MySQL,我们会对索引结构做预定义,包括字段名,字段类型等
2.2. 非结构化索引
就类似Mongo,索引结构未知,根据具体的数据来update索引的mapping。
如何选择两种索引呢,还是跟具体的使用场景有关,结构化相比非结构化,更易优化,性能好些,非结构化相较灵活,只是频繁update索引mapping会有一定的性能损耗
2.3. 数据类型
https://www.elastic.co/guide/en/elasticsearch/reference/6.4/mapping-types.html
2.4. 创建索引
2.4.2. 请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
PUT http://127.0.0.1:9200/<index>
{
"mappings": {
"_doc": {
"properties": {
"id": {
"type": "long"
},
"name": {
"type": "keyword"
},
"memo": {
"type": "text"
},
"gender": {
"type": "boolean"
},
"country":{
"type": "keyword"
},
"age": {
"type": "integer"
},
"date": {
"type": "date",
"format": "date_hour_minute_second_millis||date"
}
}
}
}
}
|
2.4.3. 返回
1
2
3
4
5
|
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "<index>"
}
|
2.5. 删除索引
2.6.1. 请求
1
|
DELETE http://127.0.0.1:9200/<index>
|
2.6.2. 返回
1
2
3
|
{
"acknowledged": true
}
|
2.6. 修改索引
根据目前掌握信息,索引字段只能添加不能删除,同时其类型不可修改。
2.6.1. 请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
PUT <index>/_mapping
{
"properties": {
"[column_name]": {
"type": "long", // 字段类型
"fields": { // 扩展成员
"keyword": { // 扩展成员名称
"type": "keyword", // 扩展成员类型
"ignore_above": 256
}
}
}
}
}
|
2.6.2. 返回
1
2
3
|
{
"acknowledged": true
}
|
2.7. 查看索引
2.7.1. 请求
2.7.2. 返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
{
"people": {
"aliases": {},
"mappings": {
"_doc": {
"properties": {
"age": {
"type": "integer"
},
"country": {
"type": "text"
},
"date": {
"type": "date",
"format": "date_hour_minute_second_millis||date"
},
"demo": {
"type": "long",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"gender": {
"type": "boolean"
},
"id": {
"type": "long"
},
"name": {
"type": "text"
}
}
}
},
"settings": {
"index": {
"creation_date": "1592899624413",
"number_of_shards": "5",
"number_of_replicas": "1",
"uuid": "Kfn9PgSyRiC-yA5E8HMaRw",
"version": {
"created": "6040299"
},
"provided_name": "people"
}
}
}
}
|
2.8. 检查索引是否存在
2.8.1. 请求
2.8.2. 返回
2.9. 打开/关闭 索引
当索引关闭时,一切读写 doc 的操作都将无法进行
2.9.1. 请求
1
2
3
|
POST /<index>/_close
POST /<index>/_open
|
2.9.2. 返回
1
2
3
|
{
"acknowledged": true
}
|
2.10. 索引别名
可以理解为 mysql 的 view
2.10.1 创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
POST /_aliases
{
"actions": [
{
"remove": {
"index": "test1",
"alias": "alias1"
}
},
{
"add": {
"index": "test1",
"alias": "alias2",
"filter": {
"term": {
"user": "kimchy"
}
}
}
}
]
}
|
2.10.2 删除
2.10.3 别名列表
2.11 索引压缩
Refer: https://help.aliyun.com/document_detail/161329.html
1
2
3
4
5
6
7
8
|
PUT <index>
{
"settings": {
"index": {
"codec": "brotli"
}
}
}
|
2.11.1 测试环境
- 机器配置:数据节点16核64 GB*3 + 2 TB SSD云盘。
- 数据集:官方esrally自带的nyc_taxis(74 GB)。
- 索引配置:都使用默认配置,写入完成后都可以进行force merge。
2.11.2 测试结果
压缩算法 |
索引大小(GB) |
写入TPS(doc/s) |
ES默认压缩算法(LZ4) |
35.5 |
202682 |
best_compression(DEFLATE) |
26.4 |
181686 |
brotli |
24.4 |
182593 |
zstd |
24.6 |
181393 |
2.12 索引模板
2.12.1 创建索引模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
PUT _template/mock_bulk
{
"order" : 0,
"index_patterns" : [
"mock_bulk-*"
],
"settings" : {
"index" : {
"format" : "7",
"codec" : "best_compression",
"number_of_shards" : "5",
"number_of_replicas" : "0"
}
},
"mappings" : {
"date_detection" : false,
"dynamic" : false,
"properties" : {
"name" : {
"type" : "keyword"
}
}
},
"aliases" : { }
}
|
2.13 索引运维
Refer: https://www.elastic.co/guide/cn/elasticsearch/guide/2.x/retiring-data.html
2.13.1 索引迁移
标记节点
1
|
./bin/elasticsearch --node.box_type strong
|
box_type 为一个随意的节点标识,类似于标记节点名或者节点群。
请求迁移索引
1
2
3
4
|
POST <index>/_settings
{
"index.routing.allocation.include.box_type" : "strong"
}
|
请求将对应索引迁移至标记了 strong 的节点上。
2.13.2 优化segment
1
|
POST <index>/_optimize?max_num_segments=1
|
2.13.3 关闭旧索引
当索引关闭后,后节省掉除磁盘占用以外的其它所有资源。
清空事务日志
关闭索引
打开索引
3. 文档管理
https://www.elastic.co/guide/en/elasticsearch/reference/6.4/docs.html
3.1. 添加文档
1
2
3
4
5
|
POST /<index>/_doc
{
"username" : "lisi",
"fullname" : "li si"
}
|
3.2. 批量导入文档 [reindex]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
POST _reindex?slices=auto
{
"source": {
"index": "<src_index>",
"size": 5000,
"query": {
"bool": {
"must": [
{
"range": {
"fullname": {
"term": "li"
}
}
}
]
}
}
},
"dest": {
"index": "<dest_index>"
}
}
|
3.3. 查看文档
1
|
GET /<index>/_doc/<_id>
|
3.4. 修改文档
3.4.1 全部修改
1
2
3
4
|
PUT /<index>/_doc/<_id>
{
"username" : "new_username"
}
|
3.4.1 部分修改
1
2
3
4
5
6
|
POST /<index>/_doc/<_id>/_update
{
"doc": {
"username" : "new_username"
}
}
|
3.4.1 按脚本修改
1
2
3
4
5
6
7
8
9
10
|
POST test/_doc/1/_update
{
"script": {
"source": "ctx._source.username = \"new_username\"; if (ctx._source.age < 18) { ctx._source.age += params.count; }",
"lang": "painless",
"params": {
"count": 4
}
}
}
|
3.5. 批量修改文档
- 通过 batch 方式进行批量更新,如果在执行更新后,文件内容有变化会导致更新中断
- 添加 conflicts=proceed 作为 Get 传参,可以忽略更新中断,继续执行更新,并返回冲突的 doc 数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
POST /<index>/_update_by_query
{
"query": {
"bool": {
"must": [
{
"term": {
"username": "lisi"
}
}
]
}
},
"script": {
"source": "ctx._source.age++",
"lang": "painless"
}
}
|
3.6. 删除文档
1
|
DELETE /<index></index>/_doc/<_id>
|
3.7. 批量删除文档
- 通过 batch 方式进行批量删除,如果在执行删除后,文件内容有变化会导致删除失败
- 通过 scroll_size=5000,设置单批次处理数量
- 添加 conflicts=proceed 作为 Get 传参,可以获取 冲突数量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
POST /<index>/_delete_by_query
{
"query": {
"bool": {
"must": [
{
"term": {
"username": "lisi"
}
}
]
}
}
}
|
3.7.1. 查看当前正在执行的删除任务
1
|
GET /_tasks?detailed=true&actions=*/delete/byquery
|
3.7.1. 取消当前正在执行的删除任务
1
|
POST /_tasks/<task_id>/_cancel
|
4. 查询
https://www.elastic.co/guide/en/elasticsearch/reference/6.4/query-dsl.html
4.1. 全文检索
-
match
全分词
-
match_pharse
只针对 doc 分词,但可指定自定义分词
-
match_phrase_prefix
只针对 doc 分词,并按字符串头匹配
-
multi_match
match 多字段检索版本
-
common
-
query_string
按查询语句进行查询
-
simple_query_string
4.2. 字段级检索
即针对不同的字段类型进行相应的查询
4.3. [分页] 游标查询
https://www.elastic.co/guide/en/elasticsearch/reference/6.4/search-request-scroll.html
1
|
GET /<index>/_search?scroll=1m
|
1
2
3
4
5
|
GET /_search/scroll
{
"scroll": "1m",
"scroll_id" : "<_scroll_id>"
}
|
修改可用游标上限
1
2
3
4
5
6
7
8
9
|
PUT /_cluster/settings
{
"persistent" : {
"search.max_open_scroll_context": 2000
},
"transient": {
"search.max_open_scroll_context": 2000
}
}
|
5. 聚合
5.1. 指标聚合
5.1. 桶聚合
-
composite
分页问题
-
date_histogram
时区问题
-
terms
空值问题、聚合脚本
-
filter
5.2. 管道聚合
可以将同级的聚合结果进行二次处理
6. 脚本
6.1. Painless
https://www.elastic.co/guide/en/elasticsearch/painless/6.4/painless-getting-started.html
6.1.1. 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
"script": {
"lang": "painless",
"params": {
"dayOffset": 28800000,
"dayMillis": 86400000
},
"source": """
if ((ctx._source.add_time instanceof java.lang.String) && ctx._source.user_reg_time instanceof java.lang.String) {
SimpleDateFormat dateiso8601T = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
SimpleDateFormat dateiso8601Z = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
dateiso8601Z.setTimeZone(TimeZone.getTimeZone("GMT"));
Date dateFrom = dateiso8601T.parse(ctx._source.user_reg_time);
Date dateTo = dateiso8601Z.parse(ctx._source.add_time);
Calendar calFrom = Calendar.getInstance(TimeZone.getTimeZone("GMT+8"));
calFrom.setTime(dateFrom);
calFrom.set(calFrom.get(Calendar.YEAR), calFrom.get(Calendar.MONTH), calFrom.get(Calendar.DATE), 0, 0, 0);
Calendar calTo = Calendar.getInstance(TimeZone.getTimeZone("GMT+8"));
calTo.setTime(dateTo);
calTo.add(Calendar.HOUR_OF_DAY, 8);
calTo.set(Calendar.HOUR_OF_DAY, 0);
calTo.set(Calendar.MINUTE, 0);
calTo.set(Calendar.SECOND, 0);
calTo.set(Calendar.MILLISECOND, 0);
int days = (int)((float)(calTo.getTimeInMillis()-calFrom.getTimeInMillis())/params.dayMillis);
if(ctx._source.days != days) {
ctx._source.days=days;
}
}
"""
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
"script": {
"lang": "painless",
"source": """
if ((ctx._source.add_time instanceof java.lang.String) && ctx._source.user_reg_time instanceof java.lang.String) {
SimpleDateFormat dateiso8601T = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
SimpleDateFormat dateiso8601Z = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
dateiso8601Z.setTimeZone(TimeZone.getTimeZone("GMT"));
Date dateFrom = dateiso8601T.parse(ctx._source.user_reg_time);
Date dateTo = dateiso8601Z.parse(ctx._source.add_time);
Calendar calFrom = Calendar.getInstance();
calFrom.setTime(dateFrom);
Calendar calTo = Calendar.getInstance();
calTo.setTime(dateTo);
long diff_in_seconds = (calTo.getTimeInMillis()-calFrom.getTimeInMillis())/1000;
if(ctx._source.diff_in_seconds != diff_in_seconds) {
ctx._source.diff_in_seconds=diff_in_seconds;
}
}
"""
}
|
7. SQL
https://www.elastic.co/guide/en/elasticsearch/reference/6.4/xpack-sql.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
POST /_xpack/sql?format=json
{
"time_zone": "+08:00",
"fetch_size": 1, // 相当于 sql 里的 limit, 同时返回游标
"query": "select * from people",
"filter": { // std es query dsl
"range": {
"age": {
"gte": 0,
"lte": 5
}
}
}
}
|
1
2
3
4
|
POST /_xpack/sql
{
"cursor": "<cursor_id>"
}
|
1
2
3
4
|
POST /_xpack/sql/close
{
"cursor": "<cursor_id>"
}
|
- csv
- json
- tsv
- txt
- yaml
- [binary] cbor
- [binary] smile
7.2. 可用的 sql 查询功能
https://www.elastic.co/guide/en/elasticsearch/reference/6.4/sql-functions.html
8. Pipeline
自动添加时间戳
create
1
2
3
4
5
6
7
8
9
10
11
12
|
PUT _ingest/pipeline/timestamp
{
"description": "Adds timestamp to documents",
"processors": [
{
"set": {
"field": "_source.timestamp",
"value": "{{_ingest.timestamp}}"
}
}
]
}
|
usage
1
2
3
4
|
POST index39/_doc?pipeline=timestamp
{
"id":1
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
POST /<index>/_update_by_query?pipeline=parse_api_params_chat
{
"query": {
"bool": {
"filter": [
{
"terms": {
"api_class": [
"ChatAction"
]
}
},
{
"terms": {
"api_method": [
"chat"
]
}
},
{
"exists": {
"field": "api_params"
}
}
],
"must_not": [
{
"exists": {
"field": "api_payload"
}
}
]
}
}
}
|
example
解析数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
PUT _ingest/pipeline/parse_api_params_chat
{
"processors": [
{
"json": {
"if": "ctx.api_class == 'ChatAction' && ctx.api_method == 'chat'",
"field": "api_params",
"target_field": "json_decode"
}
},
{
"script": {
"if": "ctx.json_decode != null",
"source": "ctx.json_decode = ctx.json_decode[0]"
}
},
{
"set": {
"if": "ctx.json_decode?.chatType != null && ctx.json_decode?.content != null",
"field": "api_payload.chat_type",
"value": "{{json_decode.chatType}}"
}
},
{
"set": {
"if": "ctx.json_decode?.chatType != null && ctx.json_decode?.content != null",
"field": "api_payload.chat_content",
"value": "{{json_decode.content}}"
}
},
{
"remove": {
"if": "ctx.json_decode != null",
"field": "json_decode"
}
}
]
}
|
9. 分片
Refer: https://blog.csdn.net/tzs_1041218129/article/details/110848462
查看分片状况
1
|
GET _cat/shards/<index>?v&h=n,index,shard,prirep,state,sto,sc,unassigned.reason,unassigned.details&s=sto,index
|
unassigned.reason 说明
- ALLOCATION_FAILED:由于分片分配失败而未分配。
- CLUSTER_RECOVERED:由于集群恢复而未分配。
- DANGLING_INDEX_IMPORTED:由于导入了悬空索引导致未分配。
- EXISTING_INDEX_RESTORED:由于恢复为已关闭的索引导致未分配。
- INDEX_CREATED:由于API创建索引而未分配。
- INDEX_REOPENED:由于打开已关闭索引而未分配。
- NEW_INDEX_RESTORED:由于恢复到新索引而未分配。
- NODE_LEFT:由于托管的节点离开集群而未分配。
- REALLOCATED_REPLICA:确定了更好的副本位置,并导致现有副本分配被取消。
- REINITIALIZED:当分片从开始移动回初始化,导致未分配。
- REPLICA_ADDED:由于显式添加副本而未分配。
- REROUTE_CANCELLED:由于显式取消重新路由命令而未分配。
查看分析状况原因
1
2
3
4
5
6
|
GET /_cluster/allocation/explain
{
"index": "<index>",
"shard": 0,
"primary": false
}
|
修改节点分片上限
1
2
3
4
5
6
7
8
|
PUT /_cluster/settings
{
"transient": {
"cluster": {
"max_shards_per_node":10000
}
}
}
|
998. Cat Api
Refer: https://www.elastic.co/guide/cn/elasticsearch/guide/2.x/_cat_api.html
999. 其它
一个不严谨的写入测试
服务器
8N8C16G HDD
数据生成
1
2
3
4
5
6
7
8
9
|
echo "POST _bulk\n";
for ($i = 0; $i < 10000; $i++) {
$idx = $i%10;
echo <<<EOF
{ "create" : { "_index" : "mock_bulk-{$idx}" } }
{ "name" : "mock_{$i}" }\n
EOF;
}
|
测试结果
随机 |
Index |
Shards |
Replicas |
平均耗时 |
耗时 |
否 |
1 |
1 |
0 |
489.60 |
408 422 416 805 411 423 600 401 621 410 495 427 511 503 491 |
否 |
10 |
1 |
0 |
513.36 |
95 659 293 219 152 549 145 628 91 108 308 446 966 502 686 489 764 1182 1150 574 1200 88 |
否 |
100 |
1 |
0 |
568.76 |
615 317 774 435 731 164 241 1205 435 602 499 699 333 396 699 861 663 |
否 |
1 |
5 |
0 |
386.13 |
650 423 521 84 91 1183 222 881 333 109 91 169 237 288 396 500 |
否 |
10 |
5 |
0 |
506.06 |
558 520 232 855 560 433 428 423 1087 415 504 118 324 944 347 349 |
否 |
100 |
5 |
0 |
1069.19 |
1067 728 919 639 1353 1313 1025 607 1631 1705 883 639 1569 1457 853 719 |
是 |
10 |
5 |
0 |
325.59 |
115 312 463 110 502 97 188 855 112 991 191 114 391 327 146 504 117 |
是 |
100 |
5 |
0 |
1035.47 |
977 859 1005 1368 727 2157 930 722 678 947 1085 1054 1168 783 1072 |