3. 日志存储与索引:Elasticsearch存储模型、索引生命周期管理、冷热数据分离策略

好,咱们进入第三章。说实话,日志存储这块儿,是很多风控系统从「能用」到「扛得住」的分水岭。我见过太多团队,日志一多,ES 集群直接被打挂,查询慢得像蜗牛。嗯,今天咱们就把这块硬骨头啃下来。

3.1 Elasticsearch 存储模型:别把日志当关系数据库用

很多人刚接触 ES,习惯性地拿它当 MySQL 用。我一开始也犯过这错。其实 ES 的存储模型,核心就三个概念:索引(Index)、分片(Shard)、段(Segment)

  • 索引:说白了就是一张「表」,但它是倒排索引结构。风控日志里,我习惯按天建索引,比如 risk-log-2025-04-01
  • 分片:一个索引拆成多个分片,分布在不同的节点上。分片数设多少?我建议按数据量和节点数来算。比如每天 10GB 日志,3 个节点,分片数设 6~9 个就够。太多分片反而浪费资源。
  • :每个分片内部由多个小段组成。ES 会定期合并段,减少文件数,提升查询速度。但合并时 CPU 和 IO 开销很大,我一般安排在凌晨低峰期。

核心要点:ES 不是关系型数据库,别搞什么关联查询、事务。风控日志就是写进去、查出来,简单粗暴。

举个例子,我曾在项目中遇到一个坑:某团队把风控日志的索引分片数设成了 30 个,结果集群每天凌晨段合并时 CPU 飙到 100%,查询直接超时。后来我改成 6 个分片,问题就解决了。你想想看,分片不是越多越好,得匹配你的数据量和硬件。

3.2 索引生命周期管理:让日志自动「退休」

风控日志有个特点:越新的数据越有价值。比如实时风控决策,查的是最近 5 分钟的日志;事后分析,可能查最近 7 天的;而 30 天前的日志,基本只有审计才会翻。

所以,我们不能让所有索引都永久保留。ES 提供了 ILM(Index Lifecycle Management),说白了就是给索引定个「退休计划」。

我个人习惯把索引分成四个阶段:

阶段 动作 说明
Hot 写入 + 高频查询 最近 1 天的日志,SSD 存储,副本数 2
Warm 只读 + 低频查询 2~7 天的日志,HDD 存储,副本数 1
Cold 只读 + 极少查询 8~30 天的日志,压缩存储,副本数 0
Delete 删除索引 超过 30 天的日志,直接清理

配置 ILM 策略其实很简单,看下面这段代码:

PUT _ilm/policy/risk_log_policy
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_size": "50GB",
            "max_age": "1d"
          },
          "set_priority": {
            "priority": 100
          }
        }
      },
      "warm": {
        "min_age": "2d",
        "actions": {
          "allocate": {
            "require": {
              "data_type": "warm"
            }
          },
          "forcemerge": {
            "max_num_segments": 1
          },
          "shrink": {
            "number_of_shards": 1
          }
        }
      },
      "cold": {
        "min_age": "8d",
        "actions": {
          "allocate": {
            "require": {
              "data_type": "cold"
            }
          },
          "freeze": {}
        }
      },
      "delete": {
        "min_age": "30d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}

避坑指南:我曾经把 rollovermax_size 设成 10GB,结果一天产生了 20 多个小索引,管理起来非常麻烦。后来改成 50GB,每天 3~4 个索引,刚刚好。记住,索引数量控制在 100 个以内,集群压力会小很多。

3.3 冷热数据分离策略:把钢用在刀刃上

为什么要做冷热分离?说白了就是省钱。SSD 贵,HDD 便宜。热数据(最近 1 天)需要快速写入和查询,必须放 SSD;冷数据(7 天以上)几乎没人查,放 HDD 就行。

我习惯在 ES 集群里打标签。比如给节点打上 data_type: hotdata_type: cold,然后通过 ILM 策略自动迁移数据。

下面这张图是我自己画的冷热分离架构,你看一眼就明白了:

冷热数据分离架构图 风控日志 Hot 节点 (SSD) 最近 1 天数据 写入 + 高频查询 Warm 节点 (HDD) 2~7 天数据 只读 + 低频查询 Cold 节点 8~30 天数据 压缩存储 30天后删除 数据流向:Hot → Warm → Cold → Delete ILM 策略自动管理,无需人工干预 Hot 节点:SSD,高性能 Warm 节点:HDD,平衡 Cold 节点:HDD,低成本

配置节点标签也很简单,在 elasticsearch.yml 里加上:

node.attr.data_type: hot   # 或者 warm、cold

然后在 ILM 策略里通过 allocate 动作指定数据迁移到哪个标签的节点上。嗯,这里要注意:冷节点上的数据是只读的,不能写入。我曾经有个同事把冷节点也配成可写入,结果数据分布乱成一锅粥。

警告:冷热分离不是万能的。如果你的查询模式是「随机查任意一天的数据」,那冷热分离反而会拖慢查询。风控日志的特点是「近期查询多,远期查询少」,这才适合冷热分离。

3.4 实战经验:索引模板与别名

最后分享一个小技巧。我习惯用 索引模板 配合 别名 来管理日志索引。比如:

PUT _template/risk_log_template
{
  "index_patterns": ["risk-log-*"],
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1,
    "index.lifecycle.name": "risk_log_policy",
    "index.lifecycle.rollover_alias": "risk-log-write"
  },
  "mappings": {
    "properties": {
      "timestamp": { "type": "date" },
      "user_id": { "type": "keyword" },
      "event_type": { "type": "keyword" },
      "risk_score": { "type": "float" },
      "message": { "type": "text" }
    }
  }
}

这样,每天新生成的索引会自动套用模板,省去手动配置的麻烦。别名 risk-log-write 指向当前可写的索引,应用程序只管往别名里写数据,底层索引切换完全透明。

个人经验:我曾在一次大促前,忘了给索引模板配 ILM 策略,结果日志量暴增,集群磁盘直接打满。后来我养成了一个习惯:每次上线前,先往测试环境写 100 万条日志,验证 ILM 策略是否生效。你想想看,这步检查能省多少事。

好了,这一章的内容就这些。记住:存储模型是基础,ILM 是自动化工具,冷热分离是省钱利器。把这三点吃透,你的 ES 集群就能稳稳扛住风控日志的洪流。


公众号:蓝海资料掘金营,微信deep3321