第一次遇到Elasticsearch集群突然拒绝写入请求时,我正喝着咖啡准备处理当天的日志数据。控制台突然弹出的红色警告让我差点把咖啡喷在键盘上——集群进入了只读模式!后来才知道,这是Elasticsearch的自我保护机制在起作用,就像汽车在油量过低时会自动熄火保护发动机一样。
Elasticsearch的这套保护机制其实非常智能。当磁盘空间不足时,它会分三个阶段逐步限制写入操作:
我后来在测试环境做过实验,当磁盘使用率达到95%时,连最简单的文档插入都会返回"cluster_block_exception"错误。这种设计虽然会影响业务,但避免了更严重的数据损坏风险——毕竟在磁盘写满的情况下强行写入,轻则数据丢失,重则整个节点崩溃。
吃过一次亏后,我花了两个月时间搭建了一套完整的监控体系。现在我们的生产环境已经连续300多天没触发过只读模式,关键就在于提前预防。
Elasticsearch自带的监控API其实非常强大,只需要在elasticsearch.yml中添加这几行配置:
yaml复制cluster.routing.allocation.disk.threshold_enabled: true
cluster.routing.allocation.disk.watermark.low: 85%
cluster.routing.allocation.disk.watermark.high: 90%
cluster.routing.allocation.disk.watermark.flood_stage: 95%
配合Kibana的Stack Monitoring功能,可以实时查看每个节点的磁盘使用情况。我特别喜欢它的预警功能——当某个节点达到85%阈值时,我的Slack就会收到这样格式的告警:
code复制[ES磁盘预警] node-1 磁盘使用率已达86.7%
/data挂载点剩余空间:12.4GB
建议立即清理索引:logstash-2023.08.*
对于更复杂的场景,我推荐使用Prometheus+Grafana组合。这是我现在用的exporter配置片段:
yaml复制scrape_configs:
- job_name: 'elasticsearch'
metrics_path: '/_prometheus/metrics'
static_configs:
- targets: ['es-node1:9200','es-node2:9200']
在Grafana中,我设计了一个直观的面板,用交通信号灯的颜色区分不同风险等级:
监控只是第一步,真正的功夫在日常维护。经过多次实践,我总结出几个特别有效的空间优化方法。
这是我最推荐的做法。通过ILM策略可以自动完成索引的滚动、冻结和删除。比如这个策略会保留最近7天的热数据,30天的温数据,之后自动删除:
json复制PUT _ilm/policy/logs_policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "50GB",
"max_age": "7d"
}
}
},
"delete": {
"min_age": "30d",
"actions": {
"delete": {}
}
}
}
}
}
我们现在的生产环境采用3个热节点+2个冷节点的配置。热节点使用SSD存储最新数据,冷节点用大容量HDD存储历史数据。通过这样的配置,整体存储成本降低了40%,查询性能反而提升了15%。
关键配置在于打标签:
bash复制# 热节点配置
node.attr.box_type: hot
# 冷节点配置
node.attr.box_type: cold
然后在索引配置中指定数据迁移规则:
json复制PUT logs-*/_settings
{
"index.routing.allocation.require.box_type": "hot"
}
即使预防做得再好,意外总会发生。上个月我们的日志量突然暴增,还是触发了一次只读模式。幸好我们有一套成熟的应急方案。
第一招:清理临时文件
bash复制# 查看各索引占用空间
GET _cat/indices?v&h=index,store.size&s=store.size:desc
# 删除测试索引
DELETE /test-*
第二招:收缩索引
json复制POST /large-index/_shrink/small-index
{
"settings": {
"index.number_of_replicas": 1,
"index.number_of_shards": 2,
"index.blocks.write": null
}
}
第三招:紧急扩容
如果实在来不及清理,可以临时挂载新磁盘。我常用的脚本:
bash复制# 添加新磁盘到LVM卷组
vgextend esvg /dev/sdb1
lvextend -L +100G /dev/esvg/elasticsearch
resize2fs /dev/esvg/elasticsearch
当磁盘空间回到安全线以下(建议降到90%以下),执行这个命令解除只读状态:
bash复制curl -X PUT -H "Content-Type: application/json" \
http://localhost:9200/_cluster/settings \
-d '{
"persistent": {
"cluster.blocks.read_only_allow_delete": null
}
}'
记得检查所有数据节点状态,有时候不同节点可能处于不同警戒级别:
bash复制GET _nodes/stats/fs?filter_path=**.disk.*
现在我们的运维已经完全自动化了。分享几个实用的自动化脚本:
这个Python脚本会在磁盘达到88%时自动清理7天前的日志索引:
python复制import requests
from datetime import datetime, timedelta
def clean_old_indices(es_host, threshold=0.88):
# 检查磁盘空间
res = requests.get(f"http://{es_host}:9200/_nodes/stats/fs")
disk_usage = res.json()['nodes'][node_id]['fs']['total']['disk']['used_percent']
if disk_usage >= threshold*100:
# 计算7天前的日期
date_7days_ago = (datetime.now() - timedelta(days=7)).strftime("%Y.%m.%d")
# 删除旧索引
requests.delete(f"http://{es_host}:9200/logstash-{date_7days_ago}*")
对于云环境,可以配置自动扩容策略。这是我们的AWS自动扩容配置模板:
json复制{
"AutoScalingGroupName": "es-data-nodes",
"PolicyName": "scale-out-policy",
"ScalingAdjustment": 1,
"Cooldown": 300,
"MetricAggregationType": "Average",
"TargetValue": 85.0,
"PredefinedMetricSpecification": {
"PredefinedMetricType": "ASGAverageDiskUtilization"
}
}
在实际运维中,总会遇到一些教科书上没讲过的情况。分享几个典型案例:
有一次为了快速释放空间,我把所有索引的副本数从2改为0:
json复制PUT /*/_settings
{
"number_of_replicas": 0
}
结果第二天某个节点宕机,导致数据永久丢失。现在我采用更安全的渐进式调整:
bash复制# 先调整非关键索引
PUT /logs-*/_settings
{
"number_of_replicas": 1
}
# 观察一段时间后再调整核心索引
PUT /orders-*/_settings
{
"number_of_replicas": 1
}
我们的快照仓库原本配置在集群本地:
json复制PUT _snapshot/my_backup
{
"type": "fs",
"settings": {
"location": "/mnt/backups"
}
}
后来发现快照本身也在占用磁盘空间,形成恶性循环。现在改用S3存储快照,磁盘压力小了很多。
在长期运维中,我发现磁盘空间管理不是简单的数字游戏,而是需要平衡多个因素:
我的经验法则是保持磁盘使用率在70%-80%的甜蜜区间。为此我们开发了一个动态平衡算法,自动计算最优的索引保留策略和分片配置。
这套系统上线后,我们的集群稳定性显著提升。最近一次大促期间,日志量暴涨3倍的情况下,磁盘使用率始终稳定在82%-84%之间,既没有触发只读模式,也没有浪费存储资源。