Elasticsearch索引容量管理实践

最新活动

包含文章发布时段最新活动,前往ES产品介绍页,可查找ES当前活动统一入口

Elasticsearch Service自建迁移特惠政策>>

Elasticsearch Service 新用户特惠狂欢,最低4折首购优惠 >>

Elasticsearch Service 企业首购特惠,助力企业复工复产>>

 

Elasticsearch是目前大数据领域最热门的技术栈之一,腾讯云 Elasticsearch Service(ES)是基于开源搜索引擎 Elasticsearch 打造的高可用、可伸缩的云端全托管 Elasticsearch 服务,完善的高可用解决方案,让业务可以放心的把重要数据存储到腾讯云 ES 中。了解 ES 的索引管理方法有助于扬长避短,更好的利用 ES 的强大功能,特别是当遇到性能问题时,原因通常都可回溯至数据的索引方式以及集群中的分片数量。如果未能在一开始做出最佳选择,随着数据量越来越大,便有可能会引发性能问题。集群中的数据越多,要纠正这一问题就越难,本文旨在帮助大家了解 ES 容量管理的方法,在一开始就管理好索引的容量,避免给后面留坑。

 

1. 为什么要做索引容量管理

  • 在生产环境使用 ES 要面对的第一个问题通常是索引容量的规划,不合理的分片数,副本数和分片大小会对索引的性能产生直接的影响;

  • Elasticsearch 中的每个索引都由一个或多个分片组成的,每个分片都是一个 Lucene 索引实例,您可以将其视作一个独立的搜索引擎,它能够对 Elasticsearch 集群中的数据子集进行索引并处理相关查询;

  • 查询和写入的性能与索引的大小是正相关的,所以要保证高性能,一定要限制索引的大小,具体来说是限制分片数量和单个分片的大小;

  • 关于分片数量,索引大小的问题这里不再赘述,可以参考 ES 官方 blog 《我在 Elasticsearch 集群内应该设置多少个分片?》(https://www.elastic.co/cn/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster)

  • 直接说结论:ES 官方推荐分片的大小是 20G - 40G,最大不能超过 50G;

 

本文介绍 3种管理索引容量的方法,从这3种方法可以了解到 ES 管理索引容量的演进过程。

 

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

2.1 创建索引

索引名上带日期的写法:

<static_name{date_math_expr{date_format|time_zone}}>

例如

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

参考官方文档:Date math support in index names(https://www.elastic.co/guide/en/elasticsearch/reference/7.x/date-math-index-names.html)

在使用的时候,索引名要 urlencode 后再使用

  1.  
    PUT /%3Cmylogs-%7Bnow%7ByyyyMMddHH%7C%2B08%3A00%7D%7D-000001%3E
  2.  
    {
  3.  
     "aliases": {
  4.  
       "mylogs-read-alias": {}
  5.  
    }
  6.  
    }

执行结果:

  1.  
    {
  2.  
     "acknowledged" : true,
  3.  
     "shards_acknowledged" : true,
  4.  
     "index" : "mylogs-2020061518-000001"
  5.  
    }

 

2.2 写入数据

写入数据的时候也要带上日期

  1.  
    POST /%3Cmylogs-%7Bnow%7ByyyyMMddHH%7C%2B08%3A00%7D%7D-000001%3E/_doc
  2.  
    {"name":"xxx"}

 

执行结果:

  1.  
    {
  2.  
     "_index" : "mylogs-2020061518-000001",
  3.  
     "_type" : "_doc",
  4.  
     "_id" : "VNZut3IBgpLCCHbxDzDB",
  5.  
     "_version" : 1,
  6.  
     "result" : "created",
  7.  
     "_shards" : {
  8.  
       "total" : 2,
  9.  
       "successful" : 2,
  10.  
       "failed" : 0
  11.  
    },
  12.  
     "_seq_no" : 0,
  13.  
     "_primary_term" : 1
  14.  
    }

 

2.3 查询数据

由于数据分布在多个索引里,查询的时候要在符合条件的所有索引查询,可以使用下面的方法查询

2.3.1 使用逗号分割指定多个索引

  1.  
    GET /mylogs-2020061518-000001,mylogs-2020061519-000001/_search
  2.  
    {"query":{"match_all":{}}}

 

2.3.2 使用通配符查询

  1.  
    GET /mylogs-*/_search
  2.  
    {
  3.  
     "query": {
  4.  
       "match_all": {}
  5.  
    }
  6.  
    }

 

执行结果:

  1.  
    {
  2.  
     "took" : 0,
  3.  
     "timed_out" : false,
  4.  
     "_shards" : {
  5.  
       "total" : 1,
  6.  
       "successful" : 1,
  7.  
       "skipped" : 0,
  8.  
       "failed" : 0
  9.  
    },
  10.  
     "hits" : {
  11.  
       "total" : {
  12.  
         "value" : 1,
  13.  
         "relation" : "eq"
  14.  
      },
  15.  
       "max_score" : 1.0,
  16.  
       "hits" : [
  17.  
        {
  18.  
           "_index" : "mylogs-2020061518-000001",
  19.  
           "_type" : "_doc",
  20.  
           "_id" : "VNZut3IBgpLCCHbxDzDB",
  21.  
           "_score" : 1.0,
  22.  
           "_source" : {
  23.  
             "name" : "xxx"
  24.  
          }
  25.  
        }
  26.  
      ]
  27.  
    }
  28.  
    }

 

2.3.3 使用别名查询

  1.  
    GET /mylogs-read-alias/_search
  2.  
    {
  3.  
     "query": {
  4.  
       "match_all": {}
  5.  
    }
  6.  
    }

执行结果同上

 

2.4 使用带日期的索引名称的缺陷

这个方法的优点是比较直观能够通过索引名称直接分辨出数据的新旧,缺点是:

  • 不是所有数据都适合使用时间分割,对于写入之后还有修改的数据不适合

  • 直接使用时间分割也可能存在某段时间数据量集中,导致索引分片超过设计容量的问题,从而影响性能

  • 为了解决上述问题还需要配合 rollover 策略使用,索引的维护比较复杂

 

3. 方法2: 使用 Rollover 管理索引

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

3.1 创建索引并且设置别名

注意: 索引名称的格式为 {.*}-d 这种格式的,数字默认是 6位

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

 

3.2 通过别名写数据

使用 bulk 一次写入了 3条记录

  1.  
    POST /myro_write_alias/_bulk?refresh=true
  2.  
    {"create":{}}
  3.  
    {"name":"xxx"}
  4.  
    {"create":{}}
  5.  
    {"name":"xxx"}
  6.  
    {"create":{}}
  7.  
    {"name":"xxx"}

执行结果:

  1.  
    {
  2.  
     "took" : 37,
  3.  
     "errors" : false,
  4.  
     "items" : [
  5.  
      {
  6.  
         "create" : {
  7.  
           "_index" : "myro-000001",
  8.  
           "_type" : "_doc",
  9.  
           "_id" : "wVvFtnIBUTVfQxRWwXyM",
  10.  
           "_version" : 1,
  11.  
           "result" : "created",
  12.  
           "forced_refresh" : true,
  13.  
           "_shards" : {
  14.  
             "total" : 2,
  15.  
             "successful" : 2,
  16.  
             "failed" : 0
  17.  
          },
  18.  
           "_seq_no" : 0,
  19.  
           "_primary_term" : 1,
  20.  
           "status" : 201
  21.  
        }
  22.  
      },
  23.  
      {
  24.  
         "create" : {
  25.  
           "_index" : "myro-000001",
  26.  
           "_type" : "_doc",
  27.  
           "_id" : "wlvFtnIBUTVfQxRWwXyM",
  28.  
           "_version" : 1,
  29.  
           "result" : "created",
  30.  
           "forced_refresh" : true,
  31.  
           "_shards" : {
  32.  
             "total" : 2,
  33.  
             "successful" : 2,
  34.  
             "failed" : 0
  35.  
          },
  36.  
           "_seq_no" : 1,
  37.  
           "_primary_term" : 1,
  38.  
           "status" : 201
  39.  
        }
  40.  
      },
  41.  
      {
  42.  
         "create" : {
  43.  
           "_index" : "myro-000001",
  44.  
           "_type" : "_doc",
  45.  
           "_id" : "w1vFtnIBUTVfQxRWwXyM",
  46.  
           "_version" : 1,
  47.  
           "result" : "created",
  48.  
           "forced_refresh" : true,
  49.  
           "_shards" : {
  50.  
             "total" : 2,
  51.  
             "successful" : 2,
  52.  
             "failed" : 0
  53.  
          },
  54.  
           "_seq_no" : 2,
  55.  
           "_primary_term" : 1,
  56.  
           "status" : 201
  57.  
        }
  58.  
      }
  59.  
    ]
  60.  
    }

记录都写到了 myro-000001 索引下

 

3.3 执行 rollover 操作

rollover 的3个条件是并列关系,任意一个条件满足就会发生 rollover

  1.  
    POST /myro_write_alias/_rollover
  2.  
    {
  3.  
     "conditions": {
  4.  
       "max_age":   "7d",
  5.  
       "max_docs":  3,
  6.  
       "max_size": "5gb"
  7.  
    }
  8.  
    }

执行结果:

  1.  
    {
  2.  
     "acknowledged" : true,
  3.  
     "shards_acknowledged" : true,
  4.  
     "old_index" : "myro-000001",
  5.  
     "new_index" : "myro-000002",
  6.  
     "rolled_over" : true,
  7.  
     "dry_run" : false,
  8.  
     "conditions" : {
  9.  
       "[max_docs: 3]" : true,
  10.  
       "[max_size: 5gb]" : false,
  11.  
       "[max_age: 7d]" : false
  12.  
    }
  13.  
    }

分析一下执行结果:

  1.  
    "new_index" : "myro-000002"
  2.  
    "[max_docs: 3]" : true,

从结果看出满足了条件("[max_docs: 3]" : true)发生了 rollover,新的索引指向了 myro-000002

再写入一条记录:

  1.  
    POST /myro_write_alias/_doc
  2.  
    {"name":"xxx"}

已经写入了新的索引,结果符合预期

  1.  
    {
  2.  
     "_index" : "myro-000002",
  3.  
     "_type" : "_doc",
  4.  
     "_id" : "BdbMtnIBgpLCCHbxhihi",
  5.  
     "_version" : 1,
  6.  
     "result" : "created",
  7.  
     "_shards" : {
  8.  
       "total" : 2,
  9.  
       "successful" : 2,
  10.  
       "failed" : 0
  11.  
    },
  12.  
     "_seq_no" : 0,
  13.  
     "_primary_term" : 1
  14.  
    }

 

3.4 使用 Rollover 的缺点

  • 必须明确执行了 rollover 指令才会更新 rollover 的别名对应的索引

  • 通常可以在写入数据之后 再执行一下 rollover 命令,或者采用配置系统 cron 脚本的方式

  • 增加了使用的 rollover 的成本,对于开发者来说不够自动化

 

4. 方法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个阶段根据业务的需求可选。

使用方法通常是下面几个步骤:

4.1 建立 Lifecycle 策略

这一步通常在 Kibana 上操作,需要的时候再导出 ES 语句

例如下面这个策略

 

  • 暂时只配置了 Hot 阶段

  • 为了方便验证,最大文档数(max_docs) 超过 2个时就 rollover

导出的语句如下

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

 

4.2 建立索引模版

ES 语句如下:

  1.  
    PUT /_template/myes_template
  2.  
    {
  3.  
     "index_patterns": [
  4.  
       "myes-*"
  5.  
    ],
  6.  
     "aliases": {
  7.  
       "myes_reade_alias": {}
  8.  
    },
  9.  
     "settings": {
  10.  
       "index": {
  11.  
         "lifecycle": {
  12.  
           "name": "myes-lifecycle",
  13.  
           "rollover_alias": "myes_write_alias"
  14.  
        },
  15.  
         "refresh_interval": "30s",
  16.  
         "number_of_shards": "12",
  17.  
         "number_of_replicas": "1"
  18.  
      }
  19.  
    },
  20.  
     "mappings": {
  21.  
       "properties": {
  22.  
         "name": {
  23.  
           "type": "keyword"
  24.  
        }
  25.  
      }
  26.  
    }
  27.  
    }

 

⚠注意:

  • 模版匹配以索引名称 myes- 开头的索引

  • 所有使用此模版创建的索引都有一个别名 myes_reade_alias 用于方便查询数据

  • 模版绑定了上面创建的 Lifecycle 策略,并且用于 rollover 的别名是 myes_write_alias

4.3 创建索引

ES 语句:

  1.  
    PUT /myes-testindex-000001
  2.  
    {
  3.  
     "aliases": {
  4.  
       "myes_write_alias":{}
  5.  
    }
  6.  
    }

 

⚠注意:

  • 索引的名称是 .*-d 的形式

  • 索引的别名用于 lifecycle 做 rollover

4.4 查看索引配置

  1.  
    GET /myes-testindex-000001
  2.  
    {}

 

执行结果:

  1.  
    {
  2.  
     "myes-testindex-000001" : {
  3.  
       "aliases" : {
  4.  
         "myes_reade_alias" : { },
  5.  
         "myes_write_alias" : { }
  6.  
      },
  7.  
       "mappings" : {
  8.  
         "dynamic_templates" : [
  9.  
          {
  10.  
             "message_full" : {
  11.  
               "match" : "message_full",
  12.  
               "mapping" : {
  13.  
                 "fields" : {
  14.  
                   "keyword" : {
  15.  
                     "ignore_above" : 2048,
  16.  
                     "type" : "keyword"
  17.  
                  }
  18.  
                },
  19.  
                 "type" : "text"
  20.  
              }
  21.  
            }
  22.  
          },
  23.  
          {
  24.  
             "message" : {
  25.  
               "match" : "message",
  26.  
               "mapping" : {
  27.  
                 "type" : "text"
  28.  
              }
  29.  
            }
  30.  
          },
  31.  
          {
  32.  
             "strings" : {
  33.  
               "match_mapping_type" : "string",
  34.  
               "mapping" : {
  35.  
                 "type" : "keyword"
  36.  
              }
  37.  
            }
  38.  
          }
  39.  
        ],
  40.  
         "properties" : {
  41.  
           "name" : {
  42.  
             "type" : "keyword"
  43.  
          }
  44.  
        }
  45.  
      },
  46.  
       "settings" : {
  47.  
         "index" : {
  48.  
           "lifecycle" : {
  49.  
             "name" : "myes-lifecycle",
  50.  
             "rollover_alias" : "myes_write_alias"
  51.  
          },
  52.  
           "refresh_interval" : "30s",
  53.  
           "number_of_shards" : "12",
  54.  
           "translog" : {
  55.  
             "sync_interval" : "5s",
  56.  
             "durability" : "async"
  57.  
          },
  58.  
           "provided_name" : "myes-testindex-000001",
  59.  
           "max_result_window" : "65536",
  60.  
           "creation_date" : "1592222799955",
  61.  
           "unassigned" : {
  62.  
             "node_left" : {
  63.  
               "delayed_timeout" : "5m"
  64.  
            }
  65.  
          },
  66.  
           "priority" : "100",
  67.  
           "number_of_replicas" : "1",
  68.  
           "uuid" : "tPwDbkuvRjKtRHiL4fKcPA",
  69.  
           "version" : {
  70.  
             "created" : "7050199"
  71.  
          }
  72.  
        }
  73.  
      }
  74.  
    }
  75.  
    }

 

⚠注意:

  • 索引使用了之前建立的索引模版

  • 索引绑定了 lifecycle 策略并且写入别名是 myes_write_alias

4.5 写入数据

  1.  
    POST /myes_write_alias/_bulk?refresh=true
  2.  
    {"create":{}}
  3.  
    {"name":"xxx"}
  4.  
    {"create":{}}
  5.  
    {"name":"xxx"}
  6.  
    {"create":{}}
  7.  
    {"name":"xxx"}

执行结果:

  1.  
    {
  2.  
     "took" : 18,
  3.  
     "errors" : false,
  4.  
     "items" : [
  5.  
      {
  6.  
         "create" : {
  7.  
           "_index" : "myes-testindex-000001",
  8.  
           "_type" : "_doc",
  9.  
           "_id" : "jF3it3IBUTVfQxRW1Xys",
  10.  
           "_version" : 1,
  11.  
           "result" : "created",
  12.  
           "forced_refresh" : true,
  13.  
           "_shards" : {
  14.  
             "total" : 2,
  15.  
             "successful" : 2,
  16.  
             "failed" : 0
  17.  
          },
  18.  
           "_seq_no" : 0,
  19.  
           "_primary_term" : 1,
  20.  
           "status" : 201
  21.  
        }
  22.  
      },
  23.  
      {
  24.  
         "create" : {
  25.  
           "_index" : "myes-testindex-000001",
  26.  
           "_type" : "_doc",
  27.  
           "_id" : "jV3it3IBUTVfQxRW1Xys",
  28.  
           "_version" : 1,
  29.  
           "result" : "created",
  30.  
           "forced_refresh" : true,
  31.  
           "_shards" : {
  32.  
             "total" : 2,
  33.  
             "successful" : 2,
  34.  
             "failed" : 0
  35.  
          },
  36.  
           "_seq_no" : 0,
  37.  
           "_primary_term" : 1,
  38.  
           "status" : 201
  39.  
        }
  40.  
      },
  41.  
      {
  42.  
         "create" : {
  43.  
           "_index" : "myes-testindex-000001",
  44.  
           "_type" : "_doc",
  45.  
           "_id" : "jl3it3IBUTVfQxRW1Xys",
  46.  
           "_version" : 1,
  47.  
           "result" : "created",
  48.  
           "forced_refresh" : true,
  49.  
           "_shards" : {
  50.  
             "total" : 2,
  51.  
             "successful" : 2,
  52.  
             "failed" : 0
  53.  
          },
  54.  
           "_seq_no" : 0,
  55.  
           "_primary_term" : 1,
  56.  
           "status" : 201
  57.  
        }
  58.  
      }
  59.  
    ]
  60.  
    }

 

⚠注意:

  • 3 条记录都写到了 myes-testindex-000001 中, Lifecycle 策略明明设置的是 2条记录就 rollover 为什么会三条都写到同一个索引了呢?

再次执行上面的语句,写入 3条记录发现新的数据都写到了 myes-testindex-000002 中, 结果符合预期。

  1.  
    {
  2.  
     "took" : 17,
  3.  
     "errors" : false,
  4.  
     "items" : [
  5.  
      {
  6.  
         "create" : {
  7.  
           "_index" : "myes-testindex-000002",
  8.  
           "_type" : "_doc",
  9.  
           "_id" : "yl0JuHIBUTVfQxRWvsv5",
  10.  
           "_version" : 1,
  11.  
           "result" : "created",
  12.  
           "forced_refresh" : true,
  13.  
           "_shards" : {
  14.  
             "total" : 2,
  15.  
             "successful" : 2,
  16.  
             "failed" : 0
  17.  
          },
  18.  
           "_seq_no" : 0,
  19.  
           "_primary_term" : 1,
  20.  
           "status" : 201
  21.  
        }
  22.  
      },
  23.  
      {
  24.  
         "create" : {
  25.  
           "_index" : "myes-testindex-000002",
  26.  
           "_type" : "_doc",
  27.  
           "_id" : "y10JuHIBUTVfQxRWvsv5",
  28.  
           "_version" : 1,
  29.  
           "result" : "created",
  30.  
           "forced_refresh" : true,
  31.  
           "_shards" : {
  32.  
             "total" : 2,
  33.  
             "successful" : 2,
  34.  
             "failed" : 0
  35.  
          },
  36.  
           "_seq_no" : 0,
  37.  
           "_primary_term" : 1,
  38.  
           "status" : 201
  39.  
        }
  40.  
      },
  41.  
      {
  42.  
         "create" : {
  43.  
           "_index" : "myes-testindex-000002",
  44.  
           "_type" : "_doc",
  45.  
           "_id" : "zF0JuHIBUTVfQxRWvsv5",
  46.  
           "_version" : 1,
  47.  
           "result" : "created",
  48.  
           "forced_refresh" : true,
  49.  
           "_shards" : {
  50.  
             "total" : 2,
  51.  
             "successful" : 2,
  52.  
             "failed" : 0
  53.  
          },
  54.  
           "_seq_no" : 0,
  55.  
           "_primary_term" : 1,
  56.  
           "status" : 201
  57.  
        }
  58.  
      }
  59.  
    ]
  60.  
    }

 

⚠注意:

  • 如果按照这个步骤没有发生自动 rollover 数据仍然写到了 myes-testindex-000001 中,需要 配置 Lifecycle 自动 Rollover的时间间隔, 参考下文

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

  • 由于 ES 是一个准实时系统,很多操作都不能实时生效

  • Lifecycle 的 rollover 之所以不用每次手动执行 rollover 操作是因为 ES 会隔一段时间判断一次索引是否满足 rollover 的条件

  • ES检测 ILM 策略的时间默认为10min

修改 Lifecycle 配置:

  1.  
    PUT _cluster/settings
  2.  
    {
  3.  
     "transient": {
  4.  
       "indices.lifecycle.poll_interval": "3s" 
  5.  
    }
  6.  
    }

 

 

5. ES 在 QQ 家校群作业统计功能上的实践

疫情期间线上教学需求爆发,QQ的家校群功能也迎来了一批发展红利,家校群的作业功能可以轻松在 QQ 群里实现作业布置,提交,批改等功能,深受师生们的喜爱。

5.1 使用场景简介

近期推出的作业统计功能,可以指定时间段+指定科目动态给出排名,有效提高了学生答题的积极性。在功能的实现上如果用传统的 SQL + KV 的方式实现成本比较高,要做到高性能也需要花不少精力,借助 ES 强大的统计聚合能力大大降低了开发成本,实现了需求的快速上线。

 

5.2 实际耗时情况

  • 插入:~25ms

  • 更新:~15ms

  • 聚合:200ms 以内

 

参考链接(可复制链接至浏览器打开)

  • 我在 Elasticsearch 集群内应该设置多少个分片?

    (https://www.elastic.co/cn/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster)

  • 使用索引生命周期管理实现热温冷架构

    (https://www.elastic.co/cn/blog/implementing-hot-warm-cold-in-elasticsearch-with-index-lifecycle-management)

  • Index lifecycle management settings in Elasticsearchedit

    (https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-settings.html)


社区底图

关注“腾讯云大数据”公众号,技术交流、最新活动、服务专享一站Get~

posted @ 2020-09-14 08:27  腾讯云大数据  阅读(1129)  评论(0编辑  收藏  举报