4、日志存储与索引:Elasticsearch 索引设计原则、分片与副本策略、生命周期管理

日志数据存到 Elasticsearch 里,说白了就是两个问题:怎么写进去怎么查出来。写快了怕丢数据,查快了怕撑爆磁盘。我这些年踩过的坑,十有八九都跟索引设计有关。

今天咱们就聊聊索引设计的三板斧:分片与副本生命周期管理,以及那些容易忽略的设计原则

4.1 索引设计原则:别让 ES 替你背锅

很多人上来就建索引,字段全用 text,分片数随便填个 5。结果查询慢得像蜗牛,还怪 ES 性能差。其实问题出在设计上。

4.1.1 字段类型选对了吗?

日志里最常见的字段是时间戳、IP、状态码、请求路径。我建议你按这个原则来:

  • 时间戳:用 date 类型,别用 text。否则你没法做时间范围聚合。
  • IP 地址:用 ip 类型。ES 原生支持 CIDR 匹配,查询效率高一个数量级。
  • 状态码:用 keyword。你不需要对它做全文搜索,只需要精确匹配。
  • 请求路径:用 keywordtext + keyword 子字段。既支持精确匹配,又支持模糊搜索。

重要原则:能用 keyword 就别用 text。text 字段会分词,占用大量存储空间,而且查询性能差。只有真正需要全文搜索的字段才用 text。

4.1.2 索引模板:批量创建的利器

日志数据通常是按天或按小时创建索引的。手动建索引?不存在的。用索引模板自动搞定。

PUT _template/logs_template
{
  "index_patterns": ["logs-*"],
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1,
    "refresh_interval": "30s"
  },
  "mappings": {
    "properties": {
      "@timestamp": { "type": "date" },
      "client_ip": { "type": "ip" },
      "status_code": { "type": "keyword" },
      "request_path": { 
        "type": "text",
        "fields": {
          "keyword": { "type": "keyword" }
        }
      },
      "response_time_ms": { "type": "integer" }
    }
  }
}

这样,只要索引名匹配 logs-*,ES 就会自动套用模板。省心又规范。

4.2 分片与副本策略:多一分则肥,少一分则瘦

分片是 ES 的并行单元。副本是分片的备份。两者配合好了,性能和可靠性都能兼顾。

4.2.1 分片数怎么定?

我见过有人给一个 10GB 的索引分了 50 个分片。结果每个分片只有 200MB,查询时集群要协调 50 个分片,反而更慢。

我的经验是:每个分片控制在 20GB~50GB 之间。比如你每天产生 100GB 日志,分 3~5 个分片就够了。

数据量 建议分片数 说明
< 50GB 1~3 小索引,分片多了浪费资源
50GB~200GB 3~5 适中,兼顾查询和写入
200GB~1TB 5~10 大索引,需要更多并行度
> 1TB 10~20 超大索引,建议按时间拆分

小技巧:分片数一旦设定,后期很难修改。所以建索引前先估算好数据量。如果拿不准,宁可少分,也别多分。少了可以加节点,多了只能重建索引。

4.2.2 副本数:1 还是 2?

副本的作用有两个:高可用提升查询吞吐。但副本多了,写入压力也会增大。

我个人习惯:生产环境至少 1 个副本。如果集群节点数足够,可以设 2 个。但别超过 3 个,否则写入性能会明显下降。

举个例子:你有一个 3 节点的集群,索引有 3 个主分片。设 1 个副本,总共 6 个分片,每个节点 2 个。设 2 个副本,总共 9 个分片,每个节点 3 个。写入时,每个分片都要写副本,压力翻倍。

4.3 生命周期管理:让 ES 自动「断舍离」

日志数据有个特点:越老越不值钱。昨天的日志可能还要查,去年的日志基本没人看。但 ES 不会自动清理,你得告诉它怎么做。

ES 的 ILM(Index Lifecycle Management) 就是干这个的。它把索引的生命周期分成四个阶段:

  • Hot:热数据,频繁写入和查询。用 SSD 存储,分片数多。
  • Warm:温数据,不再写入,偶尔查询。用 HDD 存储,可以合并分片。
  • Cold:冷数据,很少查询。可以降低副本数,甚至只保留一份。
  • Delete:删除数据。超过保留期限的直接删掉。

4.3.1 配置一个 ILM 策略

假设你的日志保留 30 天,前 7 天在 Hot 阶段,7~30 天在 Warm 阶段,30 天后删除。可以这样配:

PUT _ilm/policy/logs_policy
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_size": "50GB",
            "max_age": "1d"
          },
          "set_priority": {
            "priority": 100
          }
        }
      },
      "warm": {
        "min_age": "7d",
        "actions": {
          "forcemerge": {
            "max_num_segments": 1
          },
          "shrink": {
            "number_of_shards": 1
          },
          "allocate": {
            "require": {
              "data_type": "warm"
            }
          }
        }
      },
      "delete": {
        "min_age": "30d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}

这个策略做了几件事:

  • Hot 阶段:索引超过 50GB 或 1 天就滚动。避免单个索引过大。
  • Warm 阶段:7 天后合并段、收缩分片、迁移到慢速节点。节省存储和内存。
  • Delete 阶段:30 天后自动删除。不用你手动操心。

注意:ILM 的 rollover 动作需要索引别名配合。你写入数据时应该写别名,而不是直接写索引名。否则滚动不会生效。

4.3.2 我曾经踩过的坑

有一次,我配置了 ILM 策略,但忘了设置 rollovermax_age。结果一个索引跑了 3 个月,涨到 500GB。查询时 ES 直接 OOM 了。

后来我学乖了:永远给 rollover 设置一个时间上限。哪怕数据量很小,也要按天滚动。这样即使某个索引出了问题,影响范围也有限。

4.4 知识体系总览

下面这张图总结了本章的核心逻辑。你可以把它当作索引设计的检查清单:

日志存储与索引:核心知识体系 索引设计原则 • 字段类型选择 • 索引模板配置 • mapping 优化 • 别名与滚动 分片与副本策略 • 分片数估算 • 副本数权衡 • 节点资源规划 • 分片再平衡 生命周期管理 • Hot → Warm → Cold • rollover 策略 • forcemerge 合并 • 自动删除 核心目标 1. 写入性能:分片数合理,避免写入瓶颈 2. 查询性能:字段类型优化,减少扫描数据量 3. 存储成本:生命周期管理,自动清理冷数据 4. 运维成本:模板 + ILM,减少人工干预 三者缺一不可,共同构成 ES 日志存储的最佳实践

4.5 总结

日志存储与索引,说白了就是三个字:规、分、管

  • :规范字段类型,用模板统一管理。
  • :合理分片,控制副本数,别贪多。
  • :用 ILM 自动管理生命周期,让 ES 自己干活。

做到这三点,你的 ES 集群就能稳定运行,不会动不动就 OOM 或者查询超时。嗯,这就是我这些年总结出来的经验。


公众号:蓝海资料掘金营,微信deep3321