☰
用Elasticsearch ILM优化SkyWalking存储:从索引膨胀到自动管理
2026/10/10 17:08:12 网站建设 项目流程

1. 先看清楚痛点:SkyWalking 的存储到底膨胀在哪

用 SkyWalking 做应用监控的人,基本都会遇到同一个问题:明明只是接了几个服务,Elasticsearch 集群的磁盘占用却像滚雪球一样涨。我见过太多团队,SkyWalking 部署得挺顺利,但跑了一两个月后,ES 节点磁盘告警,一查才发现,存储里堆了一大堆按天滚动的小索引,有的索引几天没写入还在那躺着,有的数据早就过期了却迟迟没删掉。

这就是我今天想聊的:怎么用 Elasticsearch 的索引生命周期管理(ILM)把 SkyWalking 的存储成本压下来。SkyWalking默认用 Elasticsearch 保存链路数据、指标和日志,而这类可观测数据的典型特征就是“写入量大、价值随时间衰减”。链路明细过了热评期就没多少人再查了,但磁盘空间不会因为没人查就自动释放。存储优化的核心,就是在数据还有价值的时候保留、价值变低的时候降级、彻底没用的时候删除,ILM就是干这个的。

这篇文章适合谁看?如果你正在维护 SkyWalking + Elasticsearch 这套组合,或者正准备把 SkyWalking 从 demo 阶段推向生产环境,那这篇文章能帮你少踩几个坑。我会从存储为什么膨胀讲起,到 ILM 的原理拆解,再到 SkyWalking 对接 ILM 的具体配置和常见问题排查,一步步说清楚。

1.1 默认存储机制与索引形态

SkyWalking 的 ES 存储插件有一个特点:它默认按日期维度拆索引。也就是说,trace 数据、segment 数据、日志数据都会生成带日期后缀的索引,形如skywalking_segment_trace_20240809、skywalking_log_20240809这种“业务前缀 + 日期”的组合。不同版本命名规则略有差异,但总体思路是一致的:每天一个索引,往里面灌当天产生的数据。

这种按天拆索引的设计,本来是为了查询方便——查某一天的数据,只需要查对应那一个索引,不用全表扫描。但代价也随之而来:索引数量会持续累积。SkyWalking 官方默认的数据保留时间是 3 天,也就是说 3 天前的数据会被定时任务清理掉,但如果你调整过 TTL,或者清理任务没跑起来,索引就会越积越多。哪怕只保留 3 天,算上 segment、trace、log、alarm 这些类型,每天也有七八个索引在同时存在,一周下来就是几十个。

1.2 存储膨胀带来的三座大山

第一座山是磁盘。链路数据最占空间的就是 trace 明细和 log 日志。以我维护的一套环境为例,每天产生大约 20GB 的原始数据,3 天保留期,平时看起来也就六七十GB,但如果某个服务日志量大爆炸,或者某个链路异常导致 segment 暴增,一天冲上 50GB 也不是什么稀奇事。

第二座山是集群压力。Elasticsearch 的每个索引都由分片(shard)组成,SkyWalking 默认一个索引 2 个主分片,再算上副本,分片数量会被成倍放大。分片本身会占用堆内存和文件句柄,索引一多,集群的 master 节点光维护元数据就累得够呛,_cat/indices拉出来的列表能有一长串。

第三座山是查询性能。SkyWalking 查询链路时会跨多个索引搜索,索引碎、分片多,意味着一次查询要分散到大量 shard 上,协调节点要聚合的结果也更多,响应自然变慢。后台的拓扑分析、服务度量查询,也会因为分片太多而出现明显的毛刺。

这三座山叠加在一起,就是很多团队 ES 集群“莫名其妙就卡了”的真相。而 ILM 就是用来把这堆失控的索引从“放任自流”变成“按剧本走”的。

2. ILM 机制:让索引用一张“剧本”自动退休

ILM 是 Elasticsearch 自带的索引生命周期管理功能,从 ES 6.6 开始引入,7.x 之后逐渐成熟。它的核心思路很简单:给索引定一套生命周期阶段,索引从出生到删除都按这个流程自动走,不需要人工干预。

我第一次接触 ILM 的时候,脑子里第一反应是“这不就是个定时任务吗”。用下来才发现,它比定时任务聪明得多,因为它不是简单粗暴地在某个时间点删数据,而是可以配合索引的写入情况、大小、时长,在合适的时机做不同的事。

2.1 ILM 的阶段模型

一条索引进来之后,生命周期会依次经历几个阶段:Hot(热)、Warm(温)、Cold(冷)、Frozen(冻结),最后Delete(删除)。每个阶段可以配置一系列动作,动作粒度很细,比如“只读”“强制合并”“缩容分片”“迁移到冷节点”“删除”等。

拿生活类比就是:后厨的食材刚买回来放在操作台上(Hot),随时要取用;过了高峰时段挪到冷藏柜(Warm),还能用但不常用;再放几天转到冷冻室(Cold),基本不碰了但留着备查;确认过期之后直接扔进垃圾桶(Delete)。

对应到 ES 的场景:

  • Hot:索引持续接收新写入,CPU 和磁盘 IO 压力最大。适合放在高性能节点上,并配置 rollover 让它不要无限膨胀。
  • Warm:一般不再写入,但可能还有零星查询。适合做 forcemerge 合并段文件,减少底层 segment 数量,降低查询开销和磁盘占用。
  • Cold:数据冷备,几乎不访问。可以缩容分片,或者迁移到低配大容量节点降低成本。
  • Delete:数据彻底过期,直接删索引,释放磁盘。

SkyWalking 的可观测数据恰好适用这套阶段:链路明细在故障排查时重要,过几天价值就骤降,保留一定周期后删除对业务几乎没有影响。

2.2 最关键的 rollover:写满就换

ILM 体系里最重要的一个动作不是删除,而是rollover(滚动)。它的作用是:当索引满足一定条件(比如主分片容量超过 30GB,或者索引创建超过 1 天),就把写入操作切换到新索引,旧索引自动进入下一个生命周期阶段。

为什么 rollover 这么关键?因为 ES 的单个分片容量不是越大越好。单分片太大,恢复慢、查询慢,稍有不慎还会触发熔断。如果索引只在固定大小范围内滚动,每个分片都能保持健康的体量,集群整体会更稳定。

默认情况下,SkyWalking 按天分索引,如果某天数据量特别大,当天的索引会被撑得特别胖;反之数据量小,索引又很瘦。开了 ILM rollover 之后,索引会在“容量达到阈值”或“时间达到阈值”时主动滚动,从根本上解决“胖瘦不均”的问题。

2.3 各阶段动作怎么挑

我给 SkyWalking 设计 ILM 策略时,踩过一个坑:什么动作都往里塞,结果策略复杂到自己也看不懂。后来总结出一个保守但可靠的原则——只做你确定有效果的动作,不确定的动作先不加。

  • Hot:只配 rollover,条件用max_size和max_age两个就够了。可以先设 30GB 和 1 天,谁先触发谁滚动。
  • Warm:可以配forcemerge和readonly。forcemerge把大量小 segment 合并成大 segment,对查询性能有正向作用。但注意这个动作对 CPU 消耗比较大,最好在业务低峰期生效。
  • Cold:如果集群没有专门的冷节点,就直接跳过。强行给索引分配到一个不存在的节点属性,策略会卡在 waiting 状态,反而添乱。
  • Delete:设一个min_age,比 SkyWalking 自身 TTL 略长一点。这里有一个关键细节,后面在问题排查部分我会单独展开。

3. SkyWalking 对接 ILM 实操全流程

理论说完了,接下来是大家最关心的部分:怎么在 SkyWalking 里把 ILM 真正跑起来。我下面的步骤基于 SkyWalking 8.7+ 和 Elasticsearch 7.x/8.x,这套组合在生产环境中非常常见。整个过程不用改代码、不用动业务服务,只改 OAP 服务和 ES 侧的配置。

3.1 动手前先确认版本与开关

首先确认你的 SkyWalking 版本。ILM 的完整支持从 8.7.0 开始,之前的版本也能用,但要手动配置模板和策略,麻烦得多。如果你是 8.x 之前的版本,我建议先升级 OAP,再谈 ILM,否则后面很多配置项都不认。

然后是 ES 侧的版本。ILM 本身 7.x 就足够稳定,8.x 也兼容。唯一要注意的是模板写法:ES 7.8 之前用_template接口,7.8 之后引入_index_template(组合模板)。如果你用的 ES 7.8+,请直接使用新的组合模板,SkyWalking 在创建索引模板时也是按新接口的思路做的。

接着找到 OAP 的配置文件,通常是config/application.yml,里面有一段storage.elasticsearch配置。确保里面有这一项:

storage: elasticsearch: # 其他配置省略 ilmEnabled: ${SW_STORAGE_ES_ILM_ENABLED:true}

如果你没显式配置,默认也是 true。但很多团队装 SkyWalking 时会顺手用老版本的配置模板,某些老版本里这个字段根本没出现,那就等于没开。最好显式写出来,确认是true。

3.2 创建一套自己的 ILM 策略

坦白说,SkyWalking 内置的 ILM 策略也能用,但我更建议自己建一套,原因很简单:内置策略的保留时间、滚动阈值不一定适合你的数据规模。

用 curl 直接调 ES 的 API,一条命令搞定:

curl -X PUT "http://localhost:9200/_ilm/policy/skywalking-ilm-policy" -H 'Content-Type: application/json' -d ' { "policy": { "phases": { "hot": { "actions": { "rollover": { "max_size": "30GB", "max_age": "1d" } } }, "warm": { "min_age": "1d", "actions": { "readonly": {}, "forcemerge": { "max_num_segments": 1 } } }, "delete": { "min_age": "7d", "actions": { "delete": {} } } } } } '

解释一下参数:

  • max_size: 30GB表示当索引主分片总容量达到 30GB 时触发 rollover;
  • max_age: 1d表示索引存在满 1 天后也触发 rollover。两个条件谁先满足就执行。
  • warm 阶段在 1 天后执行,forcemerge会把 segment 合并成 1 个,减少文件数量。
  • delete 阶段在 7 天后删除索引。这里 7 天是我常用的值,具体按你的业务需求调。

要注意,这个策略里的 7 天只是 ILM 视角的保留时间,SkyWalking 还有自己的 TTL 默认 3 天,两者的删数逻辑会打架,后面我会专门讲怎么协调。

3.3 让 SkyWalking 模板绑定策略

策略建好了,还要让 SkyWalking 创建的索引都套上这个策略。这里就需要让索引模板引用 ILM 策略,模板的作用是约定“新建索引长什么样”。

在 SkyWalking 里,建议先关掉它自动创建模板的功能,手动控制,避免覆盖冲突。修改application.yml:

storage: elasticsearch: ilmEnabled: true indexTemplate: # 关闭 SkyWalking 自动初始化模板,改成自己控制 auto: false # 指定 ILM 策略名,对应上面创建的策略 ilmPolicyName: skywalking-ilm-policy

如果不关掉自动模板,SkyWalking 会用内置策略覆盖你的配置,结果就是策略不生效。这一步是我踩坑踩出来的,官方文档写得很隐晦,默认配置下你以为开了 ILM,其实用的是内置策略,自己建的策略根本没挂上。

修改之后重启 OAP 服务。重启后 SkyWalking 会重新初始化索引模板,模板里会带上index.lifecycle.name: skywalking-ilm-policy。如果你日志里看到类似Index template preparation finished的信息,说明模板已经准备好了。

如果想验证模板是否真的绑定了策略,直接查模板:

curl "http://localhost:9200/_index_template/skywalking*?pretty"

重点看模板里有没有index.lifecycle.name字段,以及它指向的策略是不是你自己的策略名。

3.4 验证配置是否生效

先看 SkyWalking 下一次生成的新索引,有没有被 ILM 托管:

curl "http://localhost:9200/skywalking_segment_trace_20240809/_ilm/explain?pretty"

返回结果里如果managed字段是true,就说明索引已经在 ILM 管理中了。如果还是false,检查索引模板有没有生效,以及索引是不是在模板创建之前就存在了——已存在的旧索引不会被模板自动接管。

再看看策略当前执行到哪一步:

curl "http://localhost:9200/_ilm/status?pretty"

正常状态下返回"phase": "RUNNING"。如果看到"phase": "STOPPED",说明 ILM 在集群层面被停用了,需要手动启动:

curl -X POST "http://localhost:9200/_ilm/start"

4. 实测效果与参数调优经验

配置跑起来之后,我从三方面做了对比:索引数量、查询性能、磁盘占用。

4.1 开启 ILM 后的指数对比

先说索引数量。没开 ILM 的旧环境,两周时间产生的索引数量一度高达 300 多个,其中大部分是瘦小索引——某一天数据量小,索引只有 1-2GB,但占用的分片成本和普通大索引没有区别。

开了 ILM 之后,rollover 以容量为主触发,数据量小的日子索引不会因为“到点”就滚动,数据量大的日子索引也不会撑到 50GB 以上。同样是两周时间,索引数量稳定在一百个以内。特别明显的改善是 ES master 节点的负载下来了,之前因为它要跟踪大量索引的状态,CPU 经常冲高,现在平稳了很多。

4.2 查询性能与集群压力变化

查询方面,链路查询、服务拓扑查询的响应时间都有肉眼可见的提升。原因不难理解:索引数量少了、分片数量少了,同样一条查询语句要扫描的分片数大幅减少。加上 warm 阶段执行了forcemerge,索引里的 segment 从海量小文件变成少量大文件,顺序读取效率更高。

磁盘占用也变得更可预期。过去因为索引清理不及时,磁盘占用会一直爬升,甚至出现过把数据节点磁盘打满的情况。配置 ILM 的 delete 阶段之后,过期数据到点就删,磁盘空间稳定在一个固定水位,这在容量规划上帮了大忙。

4.3 一套能直接抄的调优建议

下面这套参数适合中小规模集群,数据量每天不超过 30GB 的场景。如果你的数据量更大,往下看我会单独说大流量的调整思路。

阶段参数建议值说明
Hotmax_size30GB单索引超过 30GB 就滚动
Hotmax_age1d时间维度兜底,防止低流量时索引不滚动
Warmmin_age1drollover 后 1 天进 warm
Warmmax_num_segments1强制合并到 1 个 segment
Deletemin_age7d保留 7 天后删除

如果你的峰值流量非常大,比如每天 100GB 以上,30GB的滚动阈值会让单日索引数量过多,建议改成max_size: 50GB,同时把max_age设为12h,保证索引不会在一天内滚动太多次。

如果你的集群有低配大容量冷节点,可以在 warm 之后增加 cold 阶段:

{ "cold": { "min_age": "3d", "actions": { "allocate": { "require": { "box_type": "cold" } } } } }

但前提是你的数据节点打上了box_type: cold的标签,否则索引会卡在分配阶段,永远进不了下一步。

5. 常见问题排查与避坑实录

这一部分是我实际运维中碰到过的问题,有的花了半天才定位到原因,写出来给大家省点时间。

5.1 索引一直不滚动,先查这三点

最典型的现象:索引明明超过 30GB 了,却一直不滚动。排查思路按顺序来:

第一,确认 rollover 的别名(alias)有没有正确指向索引。ILM 的 rollover 是依赖 alias 的,也就是说,只有当一个索引是is_write_index时,它才有资格被滚出写入。查看方式:

curl "http://localhost:9200/_alias?pretty"

如果 SkyWalking 相关索引没有 alias,rollover 根本不会触发。

第二,检查策略是否真的被应用到了索引上。有时模板配置改了但没重启 OAP,新索引还是用的旧模板。用_ilm/explain看一眼索引,如果显示"policy": "",那策略根本没绑定。

第三,看策略的执行状态。ES 的 ILM 是异步执行的,不是实时生效。正常情况下周期是 10 分钟一次,如果你刚修改了策略,可以等十几分钟再看,不用着急。

5.2 模板冲突导致策略没绑上

ES 里模板是有优先级的,当你既有 SkyWalking 自动创建的模板,又有自己手动创建的模板时,优先级高的模板会覆盖低优先级的设置。如果你自己建的模板优先级低于 SkyWalking 内置模板,那你设置的 ILM 策略会被直接覆盖掉。

我自己遇到的情况是:SkyWalking 自动模板里绑定了它内置的 ILM 策略,我手动建了一个优先级更高的模板,结果部分索引用了手动模板,部分索引还是走了自动模板,整个集群的策略状态五花八门。

解决办法很简单:关掉 SkyWalking 的自动模板功能,只保留一份模板配置,让所有策略统一。如果你已经造成了索引模板混乱,手动删掉多余的模板,让 SkyWalking 重新初始化一份,再验证新创建的索引都指向同一套策略。

5.3 数据删不掉,磁盘没释放

这个问题最隐蔽,因为表面上看策略配置没问题,索引也确实进入了 delete 阶段,但数据就是一直删不掉。关键在于min_age的计算基准。

ILM 的min_age是从索引创建时间开始计算的,不是从 rollover 时间开始算。举个例子:一个索引 8 月 1 日创建,8 月 2 日 rollover(此时索引刚 1 天),如果你给 delete 阶段设的min_age是 3 天,那么这个索引要等到 8 月 4 日才会被删。这是符合逻辑的。

但要注意和 SkyWalking 自身的 TTL 配合。SkyWalking 的定时器默认只保留 3 天数据,到时间就会主动删除旧索引,如果你在 ILM 策略里设置的 delete 时间是 30 天,那实际删除动作是被 SkyWalking 的 TTL 先执行了,ILM 的 delete 根本等不到触发。这样会导致两个结果:一是你的 ILM 看起来“没生效”,二是 TTL 的删除是简单的DELETE /index,不会走 warm、forcemerge 这些阶段,你做的冷热分离完全是白费的。

所以配置的时候,要么把 SkyWalking 的 TTL 调大(甚至关掉),完全交给 ILM 来管理删除;要么就让 ILM 的 delete 时间和 TTL 保持一致。我个人推荐前者:SkyWalking 的 TTL 只负责“业务数据保留”,ILM 负责“物理索引生命周期”,两者各管一摊,不要重叠职责。

关闭 SkyWalking 的 TTL 定时清理,可以在application.yml里这样配:

storage: elasticsearch: ttl: ${SW_STORAGE_ES_TTL:10d}

把 TTL 调到比 ILM delete 时间更长即可,删数据这件事完全交给 ILM。

5.4 节点角色不足和版本兼容

还有一个高频问题:配置了 warm/cold 阶段的allocate动作,但集群里没有对应标签的节点,索引会一直处于 pending 状态,ILM 卡住。解决方法是先确认节点是否打了标签,没打标签就把这个动作去掉。另外还要注意:warm 阶段的 readonly 动作会在索引层面加只读限制,如果后续想手工删除或修改索引,会收到 forbidden 的错误,这个不要慌,临时把只读去掉就行。

版本兼容方面,ES 8.x 移除了_template旧接口的部分兼容逻辑,如果你还在用很老的 ES 6.x,ILM 功能的接口形态不同,建议直接升级 ES 到 7.x+。SkyWalking 这边,8.7 以下版本对 ILM 是实验性支持,表现不稳定,能升就升到 8.9 以上,或者直接上 9.x,这部分坑会少很多。

最后聊点个人体会。我在生产环境把 SkyWalking 的存储完全切换到 ILM 托管之后,最大的感受不是磁盘省了多少,而是运维心智负担小了:以前要定期盯索引数量、手动删过期索引、担心哪个节点磁盘打满,现在这些都不需要了。ES 自己会把这些事管好,我只需要在容量规划的时候看一眼策略参数。

如果你正准备在测试环境做这套改造,建议先跑两三天观察索引滚动是否符合预期,再上生产。删除阶段是物理删除,没有任何恢复手段,所以在策略上线之前,先确认 ES 的快照备份是打开的。等你在生产跑顺了这一套,你会发现“存储优化”不该靠人肉盯,而是靠机制。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询