Elasticsearch 日志索引设计:按小时切分 + 小时内 Rollover 兜底(ILM 实战,附完整脚本)

Elasticsearch 日志索引设计:按小时切分 + 小时内 Rollover 兜底(ILM 实战,附完整脚本)

日志场景下 Elasticsearch 索引怎么切?按天建索引,大流量日单索引轻松突破几百 GB,merge 变慢、查询抖动;只靠 ILM 按大小 rollover,索引边界又和业务时间对不上,排障时想拉某个小时的日志都费劲。本文给出一套外层按整点小时切分 + 内层 Rollover 兜底的双层别名方案,ILM 策略、索引模板、cron 脚本、应用层写入路由全部可复制。

一、为什么日志索引推荐按小时切分

先看两种常见做法的坑:

做法问题
单一大索引(按月/永久)索引无限膨胀,segment merge 停不下来,删除只能 delete_by_query,集群越用越慢
按天切分常规流量没问题,但高峰日单索引可到几百 GB;排查问题想精确定位某小时,仍要在巨型索引里扫
只用 ILM rollover索引边界由数据量决定,与时间无关,对账、归档、按时间段删除都不方便

日志数据有强时间特征:排查看小时、对账按小时、归档删过期也按时间。所以理想方案是索引边界严格对齐整点小时,同时在单小时数据突增时还能自动拆分——这就是双层设计。

二、方案总览:双层别名架构

  • 外层(小时级):每整点由 cron 新建一个独立索引,名字带小时戳
  • 内层(rollover 级):同一小时内数据量超阈值时自动 rollover 出新物理索引
  • 双层 alias:外层别名按小时命名,承载写入和查询;内层 rollover 由 ILM 维护
logs-2026061114-000001 ─┐
logs-2026061114-000002 ─┼─ alias: logs-2026061114 (is_write_index: 当前可写)
logs-2026061115-000001 ──── alias: logs-2026061115 (is_write_index: 当前可写)

应用层只认 logs-YYYYMMDDHH 这个小时级别名,rollover 发生时别名自动切换,写入方零改动

需要的资源一共 5 项:

资源类型名称数量作用
ILM Policylogs-policy1 份全集群共用,管 rollover 和过期删除
Index Templatelogs-template1 份自动应用 mappings + settings
Cron 脚本create_hourly_index.sh1 个每整点执行
外层 Aliaslogs-YYYYMMDDHH每小时 1 个写入与查询入口
内层 Alias索引名即别名每物理索引 1 个rollover 维护

三、部署步骤

3.1 创建 ILM 策略(全集群一份)

PUT _ilm/policy/logs-policy
{
  "policy": {
    "phases": {
      "hot": {
        "actions": {
          "rollover": {
            "max_size": "20gb",
            "max_docs": 100000000
          }
        }
      },
      "warm": {
        "min_age": "3d",
        "actions": {
          "forcemerge": { "max_num_segments": 1 }
        }
      },
      "delete": {
        "min_age": "30d",
        "actions": { "delete": {} }
      }
    }
  }
}

阶段说明:

  • hot:可写阶段,单索引到 20GB 或 1 亿文档即 rollover,防止索引过大
  • warm:3 天后强制段合并为 1 段,省存储、提查询
  • delete:30 天后自动删除

⚠️ 不要配 max_age。整点切索引已经承担了时间维度的切分,ILM 里再配 max_age 会和小时边界冲突,出现"不到整点就滚"的脏边界。

3.2 创建索引模板

PUT _index_template/logs-template
{
  "index_patterns": ["logs-*"],
  "priority": 100,
  "template": {
    "settings": {
      "number_of_shards": 10,
      "number_of_replicas": 1
    },
    "mappings": {
      "properties": {
        "@timestamp": { "type": "date" },
        "level":      { "type": "keyword" },
        "message":    { "type": "text" },
        "service":    { "type": "keyword" },
        "host":       { "type": "keyword" }
      }
    }
  }
}

注:mappings 按实际日志字段补充。ILM 绑定和 rollover_alias 不要写在模板里,留到每小时的种子索引上单独指定,避免污染所有 logs-* 索引。

3.3 Cron 脚本:每整点创建种子索引

脚本路径 /opt/scripts/create_hourly_index.sh

#!/bin/bash
set -euo pipefail

ES_HOST="${ES_HOST:-http://es-host:9200}"
HOUR=$(date -u +%Y%m%d%H)
INDEX="logs-${HOUR}-000001"
ALIAS="logs-${HOUR}"

# 幂等检查:索引已存在则跳过
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
  -u "${ES_USER}:${ES_PASS}" \
  -X HEAD "${ES_HOST}/${INDEX}")

if [ "$STATUS" = "200" ]; then
  echo "[$(date)] Index ${INDEX} already exists, skip."
  exit 0
fi

if [ "$STATUS" != "404" ]; then
  echo "[$(date)] Unexpected status ${STATUS} for ${INDEX}, abort."
  exit 1
fi

# 创建种子索引,同时挂外层 alias 和 ILM 配置
RESP=$(curl -s -u "${ES_USER}:${ES_PASS}" \
  -X PUT "${ES_HOST}/${INDEX}" \
  -H 'Content-Type: application/json' \
  -d "{
    \"aliases\": {
      \"${ALIAS}\": { \"is_write_index\": true }
    },
    \"settings\": {
      \"index.lifecycle.name\": \"logs-policy\",
      \"index.lifecycle.rollover_alias\": \"${ALIAS}\"
    }
  }")

echo "[$(date)] Create ${INDEX} alias ${ALIAS}: ${RESP}"

crontab 配置:

# 每小时 0 分 5 秒执行(避开整点集群压力尖峰)
5 * * * * /opt/scripts/create_hourly_index.sh >> /var/log/es-hourly-index.log 2>&1

两个细节:脚本里 date -u 用 UTC,全链路统一时区;crontab 建议主备双机部署,防止单点漏建。

四、应用层接入

4.1 写入路由:按当前小时选别名

Java 示例:

String hour = ZonedDateTime.now(ZoneOffset.UTC)
    .format(DateTimeFormatter.ofPattern("yyyyMMddHH"));
String alias = "logs-" + hour;

IndexRequest req = new IndexRequest(alias)
    .source(jsonMap, XContentType.JSON);

Python 示例:

from datetime import datetime, timezone

def current_alias():
    hour = datetime.now(timezone.utc).strftime("%Y%m%d%H")
    return f"logs-{hour}"

es.index(index=current_alias(), document=log_entry)

4.2 查询姿势

# 单小时查询
GET logs-2026061114/_search

# 跨小时:明确列出,别用通配符(展开慢)
GET logs-2026061113,logs-2026061114/_search

# 时间范围查询兜底
GET logs-*/_search
{
  "query": {
    "range": { "@timestamp": { "gte": "now-2h", "lte": "now" } }
  }
}

五、Rollover 行为详解

触发时机:小时内数据满足任一条件自动 rollover——单索引 ≥20GB,或文档数 ≥1 亿。

状态变化

触发前:
  logs-2026061114-000001 ← 可写 (is_write_index: true)

触发后:
  logs-2026061114-000001 ← 只读
  logs-2026061114-000002 ← 可写 (is_write_index: true)

小时别名 logs-2026061114 自动指向 000002,应用层无感知。

与下个整点的衔接

  • 14:59:59 数据仍写入 logs-2026061114-000002
  • 15:00:05 cron 执行,新建 logs-2026061115-000001
  • 应用层下次写入按当前小时自动切到 logs-2026061115-*

六、验证与日常运维命令

# 查看某小时索引的 ILM 进度
GET logs-2026061114/_ilm/explain

# 查看别名当前指向
GET _alias/logs-2026061114

# 列出最近索引的大小与文档数
GET _cat/indices/logs-*?h=index,docs.count,store.size&s=index

测试时手动触发一次 rollover 验证链路:

POST logs-2026061114/_rollover
{
  "conditions": { "max_docs": 1 }
}

七、监控告警配置

监控项阈值级别
某小时索引缺失当前时间 +5 分钟该小时索引不存在Critical
ILM 阶段卡住phase 5 分钟无变化Warning
集群分片数接近 cluster.max_shards_per_nodeWarning
磁盘使用率≥80%Warning

监控建议接 Prometheus + ElastAlert 或自研 watcher,重点盯索引缺失——cron 失败意味着应用写入直接报错。

八、踩坑清单

后果应对
cron 失败整点索引缺失应用层写入失败监控告警 + 应用层降级写 logs-* 通配兜底
单小时数据爆增索引过大性能劣化rollover 自动兜底(max_size 20gb)
rollover_alias 配置缺失ILM 静默不工作种子索引脚本里强制写入该配置
跨小时查询用 logs-*通配展开慢应用层按需拼接具体小时别名
应用节点时区不一致写错小时索引全链路统一 UTC

九、部署 Checklist

  • 创建 ILM policy logs-policy
  • 创建 Index template logs-template
  • 部署 create_hourly_index.sh 到独立调度机
  • 配置 crontab(主备双机)
  • 应用层改造写入路由逻辑
  • 配置监控告警(缺失索引、ILM 卡住、磁盘)
  • 测试环境完整跑一次 rollover 验证

十、版本兼容性

ES 版本支持情况
7.8+✅ 推荐,ILM + rollover 稳定
7.0 - 7.7⚠️ 部分 API 行为有差异,建议升级
8.x✅ 兼容本方案全部 API

总结

这套方案的本质是让时间边界和数据量边界各管各的:整点切分满足排障、对账、归档的时间对齐需求,ILM rollover 兜住流量突增,双层别名把两者对应用层透明。全部资源就一份 ILM 策略 + 一份模板 + 一个 cron 脚本,复制文中 DSL 即可落地。

你们的日志索引是按天切、按小时切,还是纯 rollover?有没有遇到过单索引几百 GB 的场面?评论区聊聊你的切法。


文中脚本与 DSL 在 ES 7.17/8.x 环境验证通过,字段映射请按实际业务调整。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sayai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值