第三章 Elasticsearch 数据索引:索引管理,映射,文档

说实话,很多新手上来就写告警规则,结果发现查询慢、数据不对、字段类型报错。为什么?因为没搞懂 ES 里的数据是怎么存、怎么管的。这一章我就带你把这些基础打牢。

3.1 索引管理——你的数据仓库

索引在 ES 里相当于关系数据库里的数据库 + 表。给它起个好名字,后面查起来才顺手。

3.1.1 创建索引

最简单的创建方式:

PUT /alerts-nginx-error
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  }
}

我个人习惯把告警相关的索引按 类型-日期 命名,比如 alerts-nginx-2025-04-18。这样后续做 ILM(索引生命周期管理)也方便。

3.1.2 删除与关闭索引

删除索引:DELETE /alerts-nginx-error。注意,生产环境千万别手滑。我记得有一次同事不小心删错了,还好有快照才能恢复。

关闭索引可以保留数据但禁止读写,适合临时维护:

POST /alerts-nginx-error/_close

3.1.3 索引别名

别名是个好东西。你可以把多个索引指向同一个别名,查询时用别名就行。比如每天建一个索引,但告警查询永远指向 alerts-current:

POST /_aliases
{
  "actions": [
    { "add": { "index": "alerts-nginx-2025-04-18", "alias": "alerts-current" } },
    { "remove": { "index": "alerts-nginx-2025-04-17", "alias": "alerts-current" } }
  ]
}

3.2 映射(Mapping)——字段类型决定一切

映射定义了字段的类型、分词规则、是否被索引。它直接影响查询性能和数据准确性。

3.2.1 动态映射 vs 显式映射

对比项动态映射显式映射
定义ES 根据第一份文档自动推断类型创建索引时手动指定字段类型
优点快,零配置精度高,类型可控
缺点容易把时间戳推断为文本,导致排序、聚合失败需要额外代码维护
推荐场景临时测试、日志简单场景生产环境、告警相关索引

我在项目里踩过一个坑:动态映射把 error_count 识别成了字符串,结果告警规则里做 range 查询直接报错。从那以后,所有生产索引我都用显式映射。

3.2.2 定义显式映射

PUT /alerts-nginx-error
{
  "mappings": {
    "properties": {
      "timestamp": { "type": "date" },
      "host":      { "type": "keyword" },
      "status":    { "type": "integer" },
      "message":   { "type": "text", "analyzer": "standard" },
      "error_code":{ "type": "keyword" }
    }
  }
}

注意:keyword 适合精确匹配(告警里的 status == 500),text 适合全文搜索(告警里的 message LIKE "%timeout%")。

小提示:如果你不确定字段类型,先往测试索引里写入几条数据,用 GET /test-index/_mapping 查看 ES 推断的结果,再修改正式映射。

3.3 文档——你要操作的核心

文档就是 ES 里的一条数据,类似关系数据库的一行。每个文档有一个唯一 _id。

3.3.1 增删改查

  • 新增(索引)文档:PUT /alerts-nginx-error/_doc/1 {"host":"web01","status":500}
  • 查询文档:GET /alerts-nginx-error/_doc/1
  • 更新文档:POST /alerts-nginx-error/_update/1 {"doc":{"status":502}}
  • 删除文档:DELETE /alerts-nginx-error/_doc/1

注意,ES 的更新本质上是先删除旧文档再索引新文档,并非原位修改。你想想看,如果频繁更新同一文档,性能开销会很大。

3.3.2 批量写入(Bulk API)

告警数据通常秒级产生,用单条写入肯定扛不住。Bulk API 可以把多条写入合并成一个请求:

POST /_bulk
{"index":{"_index":"alerts-nginx-error","_id":"1"}}
{"host":"web01","status":500,"timestamp":"2025-04-18T10:00:00Z"}
{"index":{"_index":"alerts-nginx-error","_id":"2"}}
{"host":"web02","status":503,"timestamp":"2025-04-18T10:00:01Z"}

我曾经用 Bulk API 把写入吞吐从每秒 500 条提升到了 8000 条,关键就是设置了合适的 batch_size(通常 1000~5000 条一批)。

3.4 用于告警的数据源设计

好的数据源设计能让告警规则又准又快。反例:把所有字段塞进一个超大索引,查询时到处扫描。

3.4.1 典型索引模式

模式名称说明适用场景
单索引 + 类型字段所有告警日志放在同一个索引,用 service 字段区分小规模,< 10 个服务
按服务分索引alerts-nginx、alerts-webapp 等中等规模,查询隔离性好
按时间分索引alerts-2025-04-18 每天一个数据量大,配合 ILM 自动删除旧数据
推荐组合alerts-{service}-{date} 例如 alerts-nginx-2025-04-18生产环境通用

3.4.2 字段设计建议

  • 时间字段:必须用 date 类型,建议用 ISO8601 格式。告警规则里的 @timestamp 字段就是靠它来划定时间窗口。
  • 状态码/级别:用 keyword 类型。因为告警查询经常是 status: 500 这种精确匹配。
  • 错误消息:用 text 类型并配合 match 查询。注意:如果把消息也设为 keyword,那就没法做模糊搜索了。
  • 数值指标:比如 response_time 用 float 或 integer,方便做聚合统计。
注意:不要为每个字段都开启 "index": true。不用于查询的字段(比如原始日志的冗长堆栈)可以设为 "index": false,减少存储开销。

3.5 本章知识体系

下面这张图帮你理清索引、映射、文档、数据源和告警规则之间的关系。只有搞清楚数据是怎么进来的、类型是什么、放在哪个索引里,你写的告警规则才能真正发挥作用。

ELK告警数据源结构 ELK 告警数据源核心结构 应用日志 / 指标 索引管理 映射(Mapping) 文档(Document) 告警数据源(按服务+时间分索引) 告警规则(ES 查询 + 条件) 关键点:映射决定字段类型 → 文档按映射写入 → 数据源按索引模式组织 → 告警规则在数据源上执行
一句话总结:先把索引结构、映射定义、文档写入这三件事理顺,你的告警数据源才干净。后续告警规则写起来就顺了。