前言: 了解 ES 的索引管理方法有助于扬长避短,更好的利用 ES 的强大功能,特别是当遇到性能问题时,原因通常都可回溯至数据的索引方式以及集群中的分片数量。如果未能在一开始做出最佳选择,随着数据量越来越大,便有可能会引发性能问题。集群中的数据越多,要纠正这一问题就越难,本文旨在帮助大家了解 ES 容量管理的方法,在一开始就管理好索引的容量,避免给后面留坑。

为什么要做容量管理

  • 在生产环境使用 ES 要面对的第一个问题通常是索引容量的规划,不合理的分片数,副本数和分片大小会对索引的性能产生直接的影响。
  • Elasticsearch 中的每个索引都由一个或多个分片组成的,每个分片都是一个 Lucene 索引实例,您可以将其视作一个独立的搜索引擎,它能够对 Elasticsearch 集群中的数据子集进行索引并处理相关查询。
  • 查询和写入的性能与索引的大小是正相关的,所以要保证高性能,一定要限制索引的大小,具体来说是限制分片数量和单个分片的大小。
  • 分片和索引的大小太大和太多容易导致性能问题。

官方博客建议的分片数量

注意: ES 官方推荐分片的大小是 20G - 40G,最大不能超过 50G。

如何管理ES的容量

在介绍了为何要管理ES的容量后,我们接下来需要考虑的是如何进行管理,以下为通常的做法。

1. 使用在索引名称上带上时间的方法管理索引

例如: <logs-{now{yyyyMMddHH|+08:00}}-000001>

需要注意的是,在使用HTTP接口创建索引时,索引名称要进行urlencode编码:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# 创建索引
$ PUT /%3Cnginxlogs-%7Bnow%7ByyyyMMddHH%7C%2B08%3A00%7D%7D-000001%3E
{
  "aliases": {
  "nginxlogs-read-alias": {}
  }
}
# 返回结果
{
  "acknowledged" : true,
  "shards_acknowledged" : true,
  "index" : "nginxlogs-2020061518-000001"
}

# 写入数据
$ POST /%3Cnginxlogs-%7Bnow%7ByyyyMMddHH%7C%2B08%3A00%7D%7D-000001%3E/_doc
{"name":"xxx"}
{
  "_index" : "nginxlogs-2020061518-000001",
  "_type" : "_doc",
  "_id" : "VNZut3IBgpLCCHbxDzDB",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

# 查询数据 
## 使用多索引查询
$ GET /nginxlogs-2020061518-000001,nginxlogs-2020061519-000001/_search
{"query":{"match_all":{}}}

## 使用通配符查询多索引
$ GET /nginxlogs-*/_search
{
  "query": {
    "match_all": {}
  }
}
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "nginxlogs-2020061518-000001",
        "_type" : "_doc",
        "_id" : "VNZut3IBgpLCCHbxDzDB",
        "_score" : 1.0,
        "_source" : {
          "name" : "xxx"
        }
      }
    ]
  }
}

# 使用别名查询
$ GET /nginxlogs-read-alias/_search
{
  "query": {
    "match_all": {}
  }
}

1.2时间索引的缺点

注意: 虽然使用带时间的索引可以带来很多方便,但是在实际过程中使用带时间的索引也有一定的缺陷。

  • 对于写入之后需要数据变更的不是用时间索引
  • 直接使用时间分割也可能存在某段时间数据量集中,导致索引分片超过设计容量的问题,可能影响性能
  • 索引维护起来比较麻烦(当然可以使用template进行管理,前提是满足业务需求)

2. 使用 Rollover 管理索引

Rollover 的原理是使用一个别名指向真正的索引,当指向的索引满足一定条件(文档数或时间或索引大小)更新实际指向的索引。

2.1创建索引

1
2
3
4
5
6
PUT myro-000001
{
  "aliases": {
    "myro_write_alias":{}
  }
}

2.2通过别名写入数据

这里使用bulk接口进行写入数据

 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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
$ POST /myro_write_alias/_bulk?refresh=true
{"create":{}}
{"name":"xxx"}
{"create":{}}
{"name":"xxx"}
{"create":{}}
{"name":"xxx"}

{
  "took" : 37,
  "errors" : false,
  "items" : [
    {
      "create" : {
        "_index" : "myro-000001",
        "_type" : "_doc",
        "_id" : "wVvFtnIBUTVfQxRWwXyM",
        "_version" : 1,
        "result" : "created",
        "forced_refresh" : true,
        "_shards" : {
          "total" : 2,
          "successful" : 2,
          "failed" : 0
        },
        "_seq_no" : 0,
        "_primary_term" : 1,
        "status" : 201
      }
    },
    {
      "create" : {
        "_index" : "myro-000001",
        "_type" : "_doc",
        "_id" : "wlvFtnIBUTVfQxRWwXyM",
        "_version" : 1,
        "result" : "created",
        "forced_refresh" : true,
        "_shards" : {
          "total" : 2,
          "successful" : 2,
          "failed" : 0
        },
        "_seq_no" : 1,
        "_primary_term" : 1,
        "status" : 201
      }
    },
    {
      "create" : {
        "_index" : "myro-000001",
        "_type" : "_doc",
        "_id" : "w1vFtnIBUTVfQxRWwXyM",
        "_version" : 1,
        "result" : "created",
        "forced_refresh" : true,
        "_shards" : {
          "total" : 2,
          "successful" : 2,
          "failed" : 0
        },
        "_seq_no" : 2,
        "_primary_term" : 1,
        "status" : 201
      }
    }
  ]
}

2.3执行 rollover 操作

给索引设置具体的rollover条件,任意一个条件触发都会进行rollover:

下面我们给索引别名设置rollover规则为

  • 最大文档数为3
  • 分片最大大小为5gb
  • 文档最长时间7d
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
$ POST /myro_write_alias/_rollover
{
  "conditions": {
    "max_age":   "7d",
    "max_docs":  3,
    "max_size": "5gb"
  }
}
{
  "acknowledged" : true,
  "shards_acknowledged" : true,
  "old_index" : "myro-000001",
  "new_index" : "myro-000002",
  "rolled_over" : true,
  "dry_run" : false,
  "conditions" : {
    "[max_docs: 3]" : true,
    "[max_size: 5gb]" : false,
    "[max_age: 7d]" : false
  }
}

此时,我们已经设置了rollover规则,这个时候尝试继续写入.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ POST /myro_write_alias/_doc
{"name":"xxx"}
# 发现已经写入新的索引,因为前面已经有三个doc,这次写入触发了rollover规则
{
  "_index" : "myro-000002",
  "_type" : "_doc",
  "_id" : "BdbMtnIBgpLCCHbxhihi",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

2.4使用 Rollover 的缺点

  • 必须对索引别名先进行rollover规则设置才可以进行自动roll
  • 对于开发者不够友好

3. 使用 ILM(Index Lifecycle Management ) 管理索引

ES 一直在索引管理这块进行优化迭代,从 6.7 版本推出了索引生命周期管理(Index Lifecycle Management ,简称 ILM) 机制,是目前官方提供的比较完善的索引管理方法。所谓 Lifecycle (生命周期) 是把索引定义了四个阶段:

  • Hot: 索引可写入,也可查询,也就是我们通常说的热数据,为保证性能数据通常都是在内存中的
  • Warm: 索引不可写入,但可查询,介于热和冷之间,数据可以是全内存的,也可以是在 SSD 的硬盘上的
  • Cold: 索引不可写入,但很少被查询,查询的慢点也可接受,基本不再使用的数据,数据通常在大容量的磁盘上
  • Delete: 索引可被安全的删除

这 4 个阶段是 ES 定义的一个索引从生到死的过程,Hot -> Warm -> Cold -> Delete 4 个阶段只有 Hot 阶段是必须的,其他 3 个阶段根据业务的需求可选。

3.1简历Lifecycle策略

kibina中创建lifecycle策略

如图,只启用了Hot阶段,并且设置了rollover规则。

对应的HTTP请求参数如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
$ PUT _ilm/policy/myindex-lifecycle
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_age": "30d",
            "max_size": "50gb",
            "max_docs": 2
          },
          "set_priority": {
            "priority": 100
          }
        }
      }
    }
  }
}

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
$ PUT /_template/myindex_template
{
  "index_patterns": [
    "myindex-*"
  ],
  "aliases": {
    "myindex_reade_alias": {}
  },
  "settings": {
    "index": {
      "lifecycle": {
        "name": "myindex-lifecycle",
        "rollover_alias": "myindex_write_alias"
      },
      "refresh_interval": "30s",
      "number_of_shards": "12",
      "number_of_replicas": "1"
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "keyword"
      }
    }
  }
}

注意:

  • 模板匹配以索引前缀开头的索引(myindex-)
  • 使用此模板的索引会自动创建myindex_write_alias的别名,方便数据检索
  • 模版绑定了上面创建的 Lifecycle 策略,并且用于 rollover 的别名是myindex_write_alias

4.3创建索引

1
2
3
4
5
6
$ PUT /myindex-testindex-000001
{
  "aliases": {
    "myindex_write_alias":{}
  }
}

4.4查看索引配置

 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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
$ GET /myindex-testindex-000001
{}
{
  "myindex-testindex-000001" : {
    "aliases" : {
      "myindex_reade_alias" : { },
      "myindex_write_alias" : { }
    },
    "mappings" : {
      "dynamic_templates" : [
        {
          "message_full" : {
            "match" : "message_full",
            "mapping" : {
              "fields" : {
                "keyword" : {
                  "ignore_above" : 2048,
                  "type" : "keyword"
                }
              },
              "type" : "text"
            }
          }
        },
        {
          "message" : {
            "match" : "message",
            "mapping" : {
              "type" : "text"
            }
          }
        },
        {
          "strings" : {
            "match_mapping_type" : "string",
            "mapping" : {
              "type" : "keyword"
            }
          }
        }
      ],
      "properties" : {
        "name" : {
          "type" : "keyword"
        }
      }
    },
    "settings" : {
      "index" : {
        "lifecycle" : {
          "name" : "myindex-lifecycle",
          "rollover_alias" : "myindex_write_alias"
        },
        "refresh_interval" : "30s",
        "number_of_shards" : "12",
        "translog" : {
          "sync_interval" : "5s",
          "durability" : "async"
        },
        "provided_name" : "myindex-testindex-000001",
        "max_result_window" : "65536",
        "creation_date" : "1592222799955",
        "unassigned" : {
          "node_left" : {
            "delayed_timeout" : "5m"
          }
        },
        "priority" : "100",
        "number_of_replicas" : "1",
        "uuid" : "tPwDbkuvRjKtRHiL4fKcPA",
        "version" : {
          "created" : "7050199"
        }
      }
    }
  }
}

4.5写入数据

 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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
$ POST /myindex_write_alias/_bulk?refresh=true
{"create":{}}
{"name":"xxx"}
{"create":{}}
{"name":"xxx"}
{"create":{}}
{"name":"xxx"}

{
  "took" : 18,
  "errors" : false,
  "items" : [
    {
      "create" : {
        "_index" : "myindex-testindex-000001",
        "_type" : "_doc",
        "_id" : "jF3it3IBUTVfQxRW1Xys",
        "_version" : 1,
        "result" : "created",
        "forced_refresh" : true,
        "_shards" : {
          "total" : 2,
          "successful" : 2,
          "failed" : 0
        },
        "_seq_no" : 0,
        "_primary_term" : 1,
        "status" : 201
      }
    },
    {
      "create" : {
        "_index" : "myindex-testindex-000001",
        "_type" : "_doc",
        "_id" : "jV3it3IBUTVfQxRW1Xys",
        "_version" : 1,
        "result" : "created",
        "forced_refresh" : true,
        "_shards" : {
          "total" : 2,
          "successful" : 2,
          "failed" : 0
        },
        "_seq_no" : 0,
        "_primary_term" : 1,
        "status" : 201
      }
    },
    {
      "create" : {
        "_index" : "myindex-testindex-000001",
        "_type" : "_doc",
        "_id" : "jl3it3IBUTVfQxRW1Xys",
        "_version" : 1,
        "result" : "created",
        "forced_refresh" : true,
        "_shards" : {
          "total" : 2,
          "successful" : 2,
          "failed" : 0
        },
        "_seq_no" : 0,
        "_primary_term" : 1,
        "status" : 201
      }
    }
  ]
}

4.6配置 Lifecycle 自动 Rollover 的时间间隔

由于ElasticSearch并不是一个realtime的的系统,因此很多操作并不能及时生效,因此需要在lifecycle中设置时间间隔。而ElasticSearch中默认的ILM策略的时间间隔为10min。

1
2
3
4
5
6
7
# 修改时间间隔
$ PUT _cluster/settings
{
  "transient": {
    "indices.lifecycle.poll_interval": "3s"
  }
}

公众号