目录

Elasticsearch 使用总结及分享

目录

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 动词

  • HEAD
  • GET
  • PUT
  • POST
  • DELETE

1.10. 查看当前 ES 版本

1
GET /

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. 请求

1
GET /<index>

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. 请求

1
HEAD /<index>

2.8.2. 返回

1
200 - OK
1
404 - Not Found

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 删除

1
/<index>/_alias/{name}

2.10.3 别名列表

1
GET /_alias

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 关闭旧索引

当索引关闭后,后节省掉除磁盘占用以外的其它所有资源。

清空事务日志
1
POST <index>/_flush
关闭索引
1
POST <index>/_close 
打开索引
1
POST <index>/_open 

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. 字段级检索

即针对不同的字段类型进行相应的查询

  • term

  • terms

  • range

    时区问题

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>"
}
  • scroll 为下一次返回的游戏缓存时间

修改可用游标上限

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>"
}

7.1. format 输出格式

  • 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;
}

测试结果

  • bulk 顺序
随机 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