3、日志存储与分析:Elasticsearch集群规划与调优、索引生命周期管理、日志查询DSL实战、日志告警规则设计

日志,说白了就是系统的「黑匣子」。

我见过太多团队,日志是收了,但查起来比登天还难。要么集群挂了,要么索引膨胀到无法管理,要么写个查询语句要憋半天。今天这一章,咱们就把 Elasticsearch 这块硬骨头啃下来。

3.1 Elasticsearch 集群规划与调优

先聊集群规划。很多人一上来就问:「我该用多少节点?」我的回答永远是:先看数据量,再看查询压力。

3.1.1 节点角色分离

我个人习惯,生产环境一定要做角色分离。别图省事把所有角色堆在一个节点上。

节点类型 职责 推荐配置
Master 节点 集群管理、元数据维护 4C8G,3台奇数
Data 节点 数据存储、索引、查询 16C64G起,SSD
Ingest 节点 数据预处理 8C16G,按需扩展
Coordinating 节点 请求路由、结果聚合 8C16G,2-3台
⚠️ 注意:Master 节点千万别挂载数据盘。我曾经见过有人把 Master 当 Data 用,结果集群脑裂,恢复了两天。

3.1.2 内存与磁盘调优

ES 是吃内存大户。我建议给 ES 的堆内存设置为物理内存的一半,但不要超过 32GB。为什么?因为 JVM 的指针压缩在 32GB 以下才生效。

# jvm.options
-Xms16g
-Xmx16g

# 禁用 swap
bootstrap.memory_lock: true

磁盘方面,SSD 是标配。我踩过机械盘的坑——写入一上来,IO 直接打满,查询延迟飙到秒级。

3.1.3 分片与副本规划

分片数不是越多越好。我一般遵循一个原则:每个分片大小控制在 20-40GB 之间。

  • 分片数 = 数据总量 / 30GB(取整)
  • 副本数:生产环境至少 1 个,保证高可用
  • 单个节点分片数不要超过 1000
💡 小技巧:如果你不确定分片数,可以先按 5-10 个分片起步,后续通过 split API 调整。但缩分片只能重建索引,所以一开始别贪多。

3.2 索引生命周期管理(ILM)

日志数据有个特点:越老越不值钱。你想想看,谁会天天查三个月前的 debug 日志?所以,ILM 就是帮我们自动管理索引的「生老病死」。

3.2.1 ILM 策略设计

我常用的策略是「热-温-冷-删」四阶段:

PUT _ilm/policy/logs_policy
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_size": "50GB",
            "max_age": "1d"
          }
        }
      },
      "warm": {
        "min_age": "7d",
        "actions": {
          "allocate": {
            "require": {
              "box_type": "warm"
            }
          },
          "forcemerge": {
            "max_num_segments": 1
          }
        }
      },
      "cold": {
        "min_age": "30d",
        "actions": {
          "freeze": {}
        }
      },
      "delete": {
        "min_age": "90d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}

嗯,这里要注意:rollover 的条件是「或」关系,不是「与」。也就是说,只要索引达到 50GB 或者超过 1 天,就会触发滚动。

3.2.2 索引模板绑定

光有策略不行,还得让索引自动用上它。

PUT _index_template/logs_template
{
  "index_patterns": ["logs-*"],
  "template": {
    "settings": {
      "number_of_shards": 5,
      "number_of_replicas": 1,
      "index.lifecycle.name": "logs_policy",
      "index.lifecycle.rollover_alias": "logs"
    }
  }
}
🔑 关键点:ILM 的 rollover 必须配合别名使用。写入时用别名,ES 会自动把新数据写到最新的索引里。

3.3 日志查询 DSL 实战

DSL 是 ES 的查询语言。说实话,刚开始学的时候我也觉得挺绕,但用多了就发现它其实很符合直觉。

3.3.1 基础查询

查某个字段包含特定关键词:

GET logs-2024-01/_search
{
  "query": {
    "match": {
      "message": "OutOfMemoryError"
    }
  }
}

精确匹配(比如查某个 IP):

GET logs-2024-01/_search
{
  "query": {
    "term": {
      "client_ip": "192.168.1.100"
    }
  }
}

3.3.2 范围查询与聚合

查某个时间段的错误日志:

GET logs-2024-01/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "level": "ERROR" } },
        { "range": { "@timestamp": { "gte": "now-1h", "lte": "now" } } }
      ]
    }
  }
}

按服务名统计错误数:

GET logs-2024-01/_search
{
  "size": 0,
  "aggs": {
    "by_service": {
      "terms": { "field": "service_name.keyword" }
    }
  }
}
💡 避坑指南:我曾经犯过一个低级错误——在 text 字段上做 terms 聚合,结果发现聚合结果不对。记住,聚合一定要用 keyword 类型字段。

3.4 日志告警规则设计

日志收上来不是为了存着看的,是要能发现问题。告警规则设计得好,能帮你省下大把排查时间。

3.4.1 告警频率与阈值

我一般把告警分为三个等级:

等级 触发条件 通知方式
P0(紧急) 5分钟内 ERROR 日志超过 100 条 电话 + 短信
P1(重要) 10分钟内 ERROR 日志超过 50 条 短信 + 群消息
P2(一般) 单个服务 WARN 日志超过 200 条 群消息

3.4.2 基于 ES 的告警实现

以 ElastAlert 为例,一个典型的告警规则:

name: Error Rate Alert
type: frequency
index: logs-*
num_events: 100
timeframe:
  minutes: 5
filter:
- query:
    query_string:
      query: "level: ERROR"
alert:
- "slack"
slack_webhook_url: "https://hooks.slack.com/xxx"
alert_subject: "【P0】错误日志超过阈值"
⚠️ 注意:告警一定要加「冷却时间」。不然半夜一个抖动,你的手机能被打爆。我一般设置 15 分钟冷却,同一个规则 15 分钟内不重复告警。

3.4.3 告警去重与降噪

日志告警最大的敌人是「噪音」。我常用的降噪手段:

  • 聚合告警:把同一类型的错误合并成一条告警
  • 黑名单:已知的预期错误(比如健康检查)直接过滤
  • 动态阈值:基于历史数据自动调整阈值,避免人工拍脑袋

好了,日志这块的内容就到这里。下一章咱们聊聊链路追踪——说白了就是「一个请求从哪来到哪去」,敬请期待。