3、日志存储与分析:Elasticsearch集群规划与调优、索引生命周期管理、日志查询DSL实战、日志告警规则设计
日志,说白了就是系统的「黑匣子」。
我见过太多团队,日志是收了,但查起来比登天还难。要么集群挂了,要么索引膨胀到无法管理,要么写个查询语句要憋半天。今天这一章,咱们就把 Elasticsearch 这块硬骨头啃下来。
3.1 Elasticsearch 集群规划与调优
先聊集群规划。很多人一上来就问:「我该用多少节点?」我的回答永远是:先看数据量,再看查询压力。
3.1.1 节点角色分离
我个人习惯,生产环境一定要做角色分离。别图省事把所有角色堆在一个节点上。
| 节点类型 | 职责 | 推荐配置 |
|---|---|---|
| Master 节点 | 集群管理、元数据维护 | 4C8G,3台奇数 |
| Data 节点 | 数据存储、索引、查询 | 16C64G起,SSD |
| Ingest 节点 | 数据预处理 | 8C16G,按需扩展 |
| Coordinating 节点 | 请求路由、结果聚合 | 8C16G,2-3台 |
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
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"
}
}
}
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" }
}
}
}
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】错误日志超过阈值"
3.4.3 告警去重与降噪
日志告警最大的敌人是「噪音」。我常用的降噪手段:
- 聚合告警:把同一类型的错误合并成一条告警
- 黑名单:已知的预期错误(比如健康检查)直接过滤
- 动态阈值:基于历史数据自动调整阈值,避免人工拍脑袋
好了,日志这块的内容就到这里。下一章咱们聊聊链路追踪——说白了就是「一个请求从哪来到哪去」,敬请期待。