概述
系列定位说明
本文是**“分布式数据架构与存储选型”系列的第五篇。在前四篇文章中,我们深入拆解了分库分表**(水平扩展的骨架)、读写分离(读写流量的分流器)、多租户数据隔离(租户边界的守护者)以及全文搜索与数据分析引擎选型(检索与分析的引擎矩阵)。现在,我们将视角从“如何扩展”转移到“如何降本”——冷热分离是数据架构成本控制的核心手段,也是数据全生命周期管理的工程落地。当数据量从 GB 级增长到 TB/PB 级,存储成本从“可忽略”变为“关键决策因素”,冷热分离不再是一种锦上添花的优化,而是大规模系统在成本约束下的必选项。
总结性引言
在一个日增 100GB 日志的监控平台中,如果所有数据都存储在 SSD 上,每月的存储成本将高达数万元。但统计显示,90% 的查询只访问最近 3 天的数据,30 天前的数据几乎无人问津。冷热分离将热数据(近 7 天)保留在 SSD 高性能存储上,冷数据(>30 天)迁移到 HDD 或对象存储(如 S3/MinIO),在几乎不影响查询体验的前提下,存储成本降低 90% 以上。
MySQL 的 pt-archiver 通过分批游标式迁移,在不锁表的前提下将冷数据从热库归档到冷库;Elasticsearch 的 ILM 通过 Hot-Warm-Cold-Delete 四阶段自动流转,将冷索引从本地 SSD 迁移到 S3 可搜索快照,存储成本降低 80%;Redis 的 allkeys-lru 淘汰策略配合 EXPIRE 过期时间,让冷数据自动从内存中消失,热数据始终常驻;ClickHouse 的 TTL 机制将 30 天前的分区自动搬迁到冷存储卷,零外部工具依赖。当 pt-archiver 的 --max-lag=5 检测到从库延迟超过 5 秒自动暂停归档,当 ES ILM 的 searchable_snapshot 将冷索引挂载到 S3 上、首次搜索延迟从 10ms 升到 500ms 但成本降低 80%,当 Spring Batch 的迁移 Job 在凌晨 2 点自动执行并在失败后从断点继续——这些机制的背后,是对**“数据温度如何度量、迁移如何零停机、成本如何量化”**的深度设计。
本文将从冷热的定义与策略出发,到五种工具的完整迁移机制,再到可量化的存储成本对比,最终以系统设计实战和面试题收尾,完整拆解冷热分离存储架构的工程内核。
核心要点
- 冷热定义:热数据(SSD,高频访问,近 7 天)→ 温数据(SSD/HDD,低频访问,7-30 天)→ 冷数据(HDD/对象存储,极少访问,>30 天)→ 归档数据(对象存储/磁带,合规保留,>90 天)。
- 成本驱动:SSD 1GB/月 ≈ 0.5-1 元,HDD 1GB/月 ≈ 0.1-0.3 元,S3 1GB/月 ≈ 0.02-0.05 元。冷热分离后存储成本可降低 80-95%。
- 三种策略:时间驱动(简单,适合时效性数据)、访问频率驱动(精细,适合无明确时效的热点数据)、混合驱动(时间 + 频率双重判断)。
- MySQL:
pt-archiver在线归档(不锁表 +--max-lag安全保护)+ 表分区 + 冷表索引精简。 - Elasticsearch:ILM 四阶段(Hot-Warm-Cold-Delete)+
shrink+forcemerge+searchable_snapshot可搜索快照。 - Redis:
maxmemory-policy内存淘汰(allkeys-lru/allkeys-lfu)+EXPIRE过期 + 多级缓存(Caffeine L1 + Redis L2 + MySQL L3)。 - ClickHouse:TTL 自动分区搬迁(
TO VOLUME 'cold')+ 多存储卷。 - Spring Batch:迁移流水线(
ItemReader/ItemWriter/Tasklet)+ 断点续传 + 定时调度。 - 跨系列关联:与分库分表叠加(第1篇)、多租户冷热策略(第3篇)、ES Segment 合并(ES系列第3篇)、Redis 淘汰算法(Redis系列第6篇)。
文章组织架构图
flowchart TD
1[1. 冷热分离核心概念] --> 2[2. 三种冷热分离策略]
2 --> 3[3. MySQL 冷热分离: pt-archiver + 表分区]
2 --> 4[4. Elasticsearch 冷热分离: ILM]
2 --> 5[5. Redis 冷热分离: maxmemory-policy + EXPIRE]
2 --> 6[6. ClickHouse 冷热分离: TTL + 多存储卷]
3 --> 7[7. Spring Batch 数据迁移流水线]
4 --> 7
5 --> 7
6 --> 7
7 --> 8[8. 存储成本量化对比]
8 --> 9[9. 面试高频专题]
架构图说明
总览说明:全文 9 个模块从冷热分离的基础概念与策略出发,逐步深入到五种工具的迁移机制、Spring Batch 编排、成本量化对比,最后以面试题收尾。模块 1-2 建立前置知识,模块 3-6 是四大存储引擎的冷热分离实现,模块 7 是统一的迁移流水线,模块 8 是成本量化决策层,模块 9 面试巩固。
关键结论:冷热分离的核心是“存储成本 vs 查询性能”的权衡。MySQL 的 pt-archiver 不锁表分批归档适合在线业务,ES ILM 的 searchable_snapshot 适合日志与搜索场景的冷数据存储,Redis 的 maxmemory-policy 适合内存冷热自动淘汰,ClickHouse TTL 适合大规模 OLAP 数据的自动生命周期管理。Spring Batch 提供了统一的迁移编排与断点续传能力。存储成本从 SSD 到 S3 可降低 95%,是大数据量场景下必须考虑的架构手段。
1. 冷热分离核心概念:冷热定义 + 成本驱动 + 架构模式 + 与分库分表叠加
1.1 冷热数据的四个温度等级
数据温度是数据访问活跃度的度量,通常以“最后一次访问时间”和“访问频率”为指标。我们将数据分为四个等级:
- 热数据 (Hot Data):近 7 天内创建或被频繁读写的业务数据。典型如最近几天的订单、实时交易流水、当前热门文章。存储于 SSD 或 内存,访问延迟 < 1ms (内存) / < 10ms (SSD),成本最高 (0.5~1 元/GB/月)。对应的存储系统:Redis、MySQL 热分区、ES Hot 节点。
- 温数据 (Warm Data):7~30 天内的数据,查询频率明显降低,但仍有分析或偶尔回溯需求。可存储于 SSD 或 HDD,访问延迟 10~50ms,成本中等 (0.1~0.3 元/GB/月)。对应的存储系统:MySQL 温分区、ES Warm 节点。
- 冷数据 (Cold Data):30 天以上的历史数据,极少被访问,但必须保留以备审计或合规查询。存储于 HDD 或对象存储,访问延迟 100ms~秒级,成本低廉 (0.02~0.05 元/GB/月)。对应的存储系统:MySQL 冷分区/归档表、ES Cold/Frozen 节点、S3/MinIO。
- 归档数据 (Archive Data):超过 90 天或 1 年,仅用于法律合规或数据备份,几乎从不查询。存储于 对象存储或磁带,访问延迟分钟级甚至小时级,成本极低 (0.01 元/GB/月以下)。通常为只读不可变。
flowchart LR
A[热数据<br/>SSD/内存<br/><10ms<br/>0.5-1元/GB/月] -->|7天后| B[温数据<br/>SSD/HDD<br/>10-50ms<br/>0.1-0.3元/GB/月]
B -->|30天后| C[冷数据<br/>HDD/对象存储<br/>100ms-秒级<br/>0.02-0.05元/GB/月]
C -->|90天/1年后| D[归档数据<br/>对象存储/磁带<br/>分钟-小时<br/><0.01元/GB/月]
图1-冷热数据生命周期图
图表结构说明:本图横向展示了数据从产生到归档的完整生命周期,从左至右温度逐步降低,分别标注了各阶段的典型存储介质、访问延迟和单位存储成本。
核心流程解析:数据根据时间或访问频率自动在四个等级间流转。阈值可根据业务自定义(如热->温 7天,温->冷 30天)。流转动作由 pt-archiver、ILM、TTL 等机制自动触发。
关键技术细节:热数据层通常需要高并发写入和查询优化(如 MySQL 索引、ES 高频 refresh);温数据层可适度降低索引密度;冷数据层主要保证读吞吐,可接受较高延迟;归档数据层则要求不可变性和极低存储成本。
性能与成本分析:若 1TB 数据全部按热数据存储,月成本约 500~1000 元。若按 10% 热、20% 温、70% 冷存储,加权月成本可降至约 50~100 元,节省 90%。这种分层正是冷热分离的核心价值。
1.2 核心驱动力——存储成本 vs 查询性能
冷热分离的本质是在存储成本和查询性能之间寻求帕累托最优。绝对性能(全 SSD)意味着极高成本,绝对低成本(全 S3)意味着不可接受的查询延迟。我们需要根据业务查询模式,将合适的数据放在合适的介质上。
以 1TB 数据保留 90 天为例,假设热数据 100GB,温数据 200GB,冷数据 700GB:
- 全热存储 (SSD):1000GB × 0.8 元/GB/月 × 3 个月 = 2400 元(累计)。
- 冷热分离 (热SSD + 温HDD + 冷S3):100GB×0.8 + 200GB×0.2 + 700GB×0.03 = 80 + 40 + 21 = 141 元/月,3 个月约 423 元,节省 82%。
更关键的是,数据量随时间线性增长,全热方案的成本会持续飙升,而冷热分离方案的成本增长几乎只与冷存储有关,斜率极低。
1.3 两种架构模式
- 单集群内冷热分层:同一 MySQL/ES 集群内,通过表分区或索引生命周期管理,将热数据和冷数据分布到不同存储节点或表空间上。优点是管理统一,查询可以透明跨分区;缺点是集群内节点角色复杂,冷数据占用在线集群资源(如 MySQL 冷分区仍占用实例内存)。
- 跨存储介质冷热分离:热数据在 MySQL/ES,冷数据导出到 S3/HDFS 等外部存储。查询时需应用层路由(根据时间条件判断查热库还是冷库)或联邦查询引擎(如 Presto/Trino 统一查询入口)。优点是热集群轻量化,冷存储成本极低;缺点是跨系统联合查询复杂,需处理异构数据源,且冷数据查询延迟高。
应用层路由示例伪代码:
public List<Order> queryOrders(Date start, Date end) {
if (start.after(coldThreshold)) {
return hotDB.query(start, end);
} else if (end.before(coldThreshold)) {
return coldDB.query(start, end);
} else {
List<Order> hot = hotDB.query(start, coldThreshold);
List<Order> cold = coldDB.query(coldThreshold, end);
return merge(hot, cold);
}
}
联邦查询则利用 Presto 的 Catalog 连接多个数据源,一条 SQL 自动分发并合并结果。
1.4 冷热分离与分库分表叠加
冷热分离可与分库分表正交叠加(参考本系列第1篇)。假设已经按 user_id 分 4 个库,每个库再按 create_time 分为热表 (orders_hot) 和冷表 (orders_cold)。路由规则为:
- 分片键
user_id % 4定位物理库。 - 时间范围路由到具体表:若查询范围完全在热区间,只查热表;完全在冷区间,只查冷表;跨区间则合并。
通过 ShardingSphere 的 Hint 路由 或 复合分片算法,可以将 user_id 和时间同时作为分片条件。热分片实例使用高配 SSD 机器,冷分片使用低配 HDD 机器,实现精细的成本控制。
2. 三种冷热分离策略:时间驱动 / 访问频率驱动 / 混合驱动
2.1 时间驱动
原理:以数据创建时间或更新时间作为唯一判断标准。例如 created_at < DATE_SUB(NOW(), INTERVAL 30 DAY)。
优点:规则简单,执行高效,不依赖外部监控,易于自动化。
缺点:无法区分“旧但仍频繁访问”的数据(如热门老文章),可能误迁热数据。一旦数据被迁移,再次访问需穿透到冷存储,可能引起性能抖动。
适用场景:时效性强的数据(订单、日志、监控指标、账单流水)。
实现示例:MySQL 分区 PARTITION BY RANGE (TO_DAYS(created_at)) 自动将数据写入对应分区;定时任务每日修改分区定义或直接 TRUNCATE 旧分区。
2.2 访问频率驱动
原理:追踪数据的访问次数(access_count)或最后访问时间(last_access_time)。例如 last_access_time < DATE_SUB(NOW(), INTERVAL 7 DAY) AND access_count < 5。
优点:精细,能准确识别真正的冷数据,避免热数据误迁。可适应突然的热点复活(如旧文章被重新推荐),通过重新加热机制恢复性能。
缺点:需要额外的访问计数器或监控。频繁更新 access_count 可能成为写入热点,需要使用 Redis 计数器 + 定期落库,或使用 HyperLogLog 等近似算法。迁移逻辑复杂,需考虑计数器重置和衰减。
适用场景:无明确时间边界的热点数据(文章、帖子、用户资料、商品详情)。
访问计数器设计:每个数据项在 Redis 中维护 access:<id> 计数器,每次读取时 INCR。每天定时将计数器刷入 MySQL 并重置。迁移任务根据 MySQL 中的 weekly_access 和 last_access_time 判断。
2.3 混合驱动
原理:先按时间初筛(如 created_at > 90 days),再按访问频率确认(access_count < 3),双重判断后才迁移。可有效避免将“古老但突然爆火”的数据误迁。
优点:兼顾安全与精细,最大限度平衡性能与成本。
缺点:逻辑最复杂,需同时维护时间与频率两个维度的元数据,并设计合理的衰减模型。
适用场景:超大流量平台(社交 Feed 流、电商商品)、成本敏感且访问模式多变。
策略判决伪代码:
def should_migrate(item):
days_since_creation = (now() - item.created_at).days
if days_since_creation < 30:
return False # 热数据
if days_since_creation > 90 and item.access_count < 2:
return True # 长期冷数据
if item.last_access_time and (now() - item.last_access_time).days > 30:
return True
return False
2.4 选型决策表
| 策略 | 复杂度 | 准确性 | 延迟敏感 | 额外开销 | 典型场景 |
|---|---|---|---|---|---|
| 时间驱动 | 低 | 中 | 不敏感 | 无 | 日志、IoT 时序 |
| 频率驱动 | 中 | 高 | 较敏感 | 计数器维护 | 内容平台、社交网络 |
| 混合驱动 | 高 | 最高 | 敏感 | 较高 | 电商、大规模用户平台 |
3. MySQL 冷热分离:pt-archiver 在线归档 + 表分区 + 冷表索引精简
3.1 pt-archiver 深度原理
pt-archiver 是 Percona Toolkit 中的在线归档利器,其核心是基于索引的游标分批处理。它首先获取一批行的主键值,然后在同一个事务中执行目标库 INSERT 和源库 DELETE,提交后继续下一批。
详细流程:
- 从源表按索引(通常是主键)升序
SELECT id, col1, col2 ... WHERE ... ORDER BY id LIMIT 1000,记录本批最大 id 作为游标位置。 - 对这 1000 行,在目标表执行
INSERT INTO dest (...) VALUES (...)(使用--replace可转为 REPLACE INTO)。 - 在同一事务中,执行
DELETE FROM source WHERE id IN (id1, id2, ...)或DELETE FROM source WHERE id >= batch_start AND id <= batch_end(--bulk-delete)。 - 提交事务,根据
--sleep休眠,再重复步骤 1,从上一批最大 id 的下一个开始。
安全性保证:
- 不锁表:普通
SELECT和DELETE均在主键索引上操作,不会升级为表锁。InnoDB 的行锁和间隙锁仅影响正在处理的行,并发 DML 几乎无影响。 - 从库保护:
--max-lag每批后检查SHOW SLAVE STATUS的Seconds_Behind_Master,若大于阈值则休眠等待,防止归档产生的 binlog 冲击从库。 - 断点续传:由于按主键递增处理,即使进程崩溃,重启后可从上次最大 id 继续(需加
--resume或记下进度)。
3.2 完整生产级命令与参数解读
pt-archiver \
--source h=192.168.1.10,P=3306,D=order_db,t=orders,u=archive_user,p=xxx \
--dest h=192.168.2.20,P=3306,D=cold_db,t=orders_archive,u=cold_user,p=yyy \
--where "created_at < DATE_SUB(NOW(), INTERVAL 30 DAY)" \
--limit 2000 \
--txn-size 2000 \
--bulk-delete \
--sleep 0.05 \
--max-lag 3 \
--check-slave-lag h=192.168.1.11,P=3306 \
--progress 50000 \
--statistics \
--dry-run # 正式运行时去掉
参数深度解析:
--bulk-delete:使用DELETE FROM source WHERE id >= ? AND id <= ?范围删除,比IN列表效率高,减少事务日志。--txn-size应与--limit一致,确保一个批次为一个事务。--sleep:控制批次间隔,0.05 秒可在吞吐和负载间取得平衡。若主库写入压力大,可调至 0.2。--max-lag 3:此处 3 秒为推荐值,可根据从库承载能力调整。--check-slave-lag:明确指定检查哪个从库,支持多个从库。--dry-run:仅输出 SQL 而不执行,用于验证。
冷表索引精简:冷数据查询场景通常只有按主键或时间范围。归档后可删除二级索引:
ALTER TABLE orders_archive DROP INDEX idx_user_status;
ALTER TABLE orders_archive DROP INDEX idx_order_date;
-- 只保留主键和可能需要的时间索引
索引空间可减少 50%~70%。
3.3 表分区方案
若选择单集群内冷热分层,可使用 MySQL 分区表,将不同分区部署到不同表空间(通过 DATA DIRECTORY 或存储策略):
CREATE TABLE orders (
id BIGINT NOT NULL,
user_id BIGINT,
created_at DATETIME NOT NULL,
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (TO_DAYS(created_at)) (
PARTITION p202601 VALUES LESS THAN (TO_DAYS('2026-02-01')),
PARTITION p202602 VALUES LESS THAN (TO_DAYS('2026-03-01')),
-- 每月增加分区
PARTITION p_future VALUES LESS THAN MAXVALUE
);
pt-archiver 也可用来定期 TRUNCATE 或 DROP 最老的分区,释放空间。
3.4 MySQL pt-archiver 在线归档流程图
sequenceDiagram
participant App as 应用服务
participant Master as 主库(热库)
participant Slave as 从库
participant Archiver as pt-archiver
participant Cold as 冷库
Archiver->>Master: SELECT id, ... FROM orders<br/>WHERE created_at < NOW()-INTERVAL 30 DAY<br/>ORDER BY id LIMIT 2000
Master-->>Archiver: 返回2000行,记录最大id=52000
Archiver->>Cold: INSERT INTO orders_archive (...) VALUES (...)
Cold-->>Archiver: OK
Archiver->>Master: DELETE FROM orders WHERE id BETWEEN 50001 AND 52000
Master-->>Archiver: OK
Archiver->>Master: COMMIT
Archiver->>Slave: SHOW SLAVE STATUS
Slave-->>Archiver: Seconds_Behind_Master = 2 (小于3,继续)
Archiver->>Archiver: SLEEP 0.05s
Archiver->>Master: SELECT ... WHERE id > 52000 ORDER BY id LIMIT 2000
Note over Archiver,Slave: 循环直到无数据返回
Archiver-->>App: 归档完成
图2-MySQL pt-archiver 在线归档流程图
图表结构说明:时序图展示了 pt-archiver 与主库、冷库、从库的交互过程,体现了分批游标、事务保护、延迟检查等核心机制。
核心流程解析:每一批都是独立事务,包含“读源库、写目标库、删源库”,提交后检查延迟并休眠。游标基于主键升序,确保每次处理的数据不重叠,且能断点续传。
关键技术细节:--bulk-delete 使用范围删除比 IN 列表更高效,尤其是当批大小较大时。--sleep 和 --max-lag 协同工作,既保障主库负载可控,又防止从库延迟过高。
性能与成本分析:在 SSD 实例上,2000 行/批、0.05 秒间隔,吞吐量约为 20,000 行/秒,对主库 CPU 占用约 5%~10%,从库延迟可控制在 1 秒内。归档完成后可通过 OPTIMIZE TABLE 回收表空间,但需在维护窗口执行。
4. Elasticsearch 冷热分离:ILM 四阶段 + shrink + forcemerge + searchable_snapshot
4.1 ILM 策略设计与节点角色
ES 的 Index Lifecycle Management (ILM) 通过自动化策略将索引在四个阶段间转移,每个阶段对应特定的节点角色和存储介质。
节点角色规划(以 10 节点集群为例):
- 3 个 Hot 节点 (
data_hot):NVMe SSD,负责写入和近期搜索,硬件要求高 IOPS。 - 4 个 Warm 节点 (
data_warm):SATA SSD 或 HDD,存储只读索引。 - 2 个 Cold 节点 (
data_cold):HDD,挂载可搜索快照,本地缓存部分数据。 - 1 个 Frozen 节点(可选):仅存元数据,搜索需全量从快照拉取。
配置 elasticsearch.yml:
node.roles: [ data_hot ]
path.data: /data/ssd
4.2 完整 ILM 策略示例
PUT _ilm/policy/logs_policy
{
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_primary_shard_size": "50gb",
"max_age": "1d"
},
"set_priority": {
"priority": 100
}
}
},
"warm": {
"min_age": "3d",
"actions": {
"allocate": {
"number_of_replicas": 1,
"require": { "data": "warm" }
},
"shrink": {
"number_of_shards": 1
},
"forcemerge": {
"max_num_segments": 1
},
"set_priority": {
"priority": 50
}
}
},
"cold": {
"min_age": "7d",
"actions": {
"allocate": {
"number_of_replicas": 0,
"require": { "data": "cold" }
},
"searchable_snapshot": {
"snapshot_repository": "s3_backup",
"force_merge_index": true
},
"set_priority": {
"priority": 0
}
}
},
"delete": {
"min_age": "90d",
"actions": {
"delete": {}
}
}
}
}
关键动作解读:
- Warm 阶段:
allocate将分片转移到 warm 节点,shrink将分片数缩减为 1(假设原为 4),减少内存占用;forcemerge强制合并为 1 个段,加速搜索并清除删除标记。 - Cold 阶段:
allocate转移到 cold 节点,副本数设为 0(由快照保证可靠性),searchable_snapshot将数据上传到 S3,本地只保留部分缓存。
4.3 shrink 与 forcemerge 原理
- shrink:通过硬链接方式创建新的索引,将源分片合并。要求源索引只读,且所有分片必须在同一节点。ILM 会自动完成迁移和只读设置。
- forcemerge:执行 Lucene 段合并,消耗大量 IO 和 CPU,因此仅在 Warm 阶段(低峰)执行,合并后搜索只需打开一个段,延迟显著降低。
4.4 可搜索快照深度解析
可搜索快照(searchable snapshot)将索引数据物化为快照存储于对象存储(S3、MinIO 等),本地节点上仅保留索引元数据和部分热点数据缓存。搜索时:
- 如果所需数据块在本地缓存,直接返回(毫秒级)。
- 如果不在,从 S3 拉取,首次请求延迟增加数百毫秒,但后续缓存命中后恢复正常。
存储成本:快照数据成本仅为 S3 存储费 + 少量 GET 请求费,本地可只保留 10%~20% 的缓存磁盘,大幅降低冷数据存储成本。
4.5 ILM 四阶段流转状态图
stateDiagram-v2
[*] --> Hot: 新建索引
Hot --> Warm: min_age=3d
Warm --> Cold: min_age=7d
Cold --> Delete: min_age=90d
Delete --> [*]
state Hot {
[*] --> Active: 写入活跃
Active --> Rollover: max_size=50GB/max_age=1d
Rollover --> [*]
}
state Warm {
[*] --> Shrink: 分片合并
Shrink --> Forcemerge: 段合并
Forcemerge --> [*]
}
state Cold {
[*] --> Snapshot: 创建快照
Snapshot --> SearchableSnapshot: 挂载为可搜索快照
SearchableSnapshot --> [*]
}
图3-ES ILM 四阶段流转状态图
图表结构说明:状态图展示了 ILM 策略下索引从创建到删除的生命周期,并分解了各阶段内部的核心动作。
核心流程解析:索引创建后处于 Hot 阶段处理写入,达到 rollover 条件后滚动新索引;旧索引进入 Warm 只读并执行优化;之后转入 Cold 阶段通过快照存放到 S3;达到 90 天删除。
关键技术细节:shrink 和 forcemerge 必须在索引只读状态下执行,ILM 会自动先设置索引为只读。Cold 阶段的 searchable_snapshot 可配置 shared_cache 模式,允许指定缓存大小。
性能与成本分析:1TB 原始数据(经压缩后约 300GB)全 SSD 存储月成本约 240 元,使用 ILM 后 Cold 阶段 70% 数据存储在 S3,月成本仅约 30 元,整体降至约 100 元,节省 58%。
5. Redis 冷热分离:maxmemory-policy 淘汰 + EXPIRE + 多级缓存
Redis 的冷热分离是基于内存容量的自动淘汰,热数据常驻内存,冷数据被逐出或过期。核心机制包括:
5.1 maxmemory-policy 策略详解
| 策略 | 适用场景 | 注意事项 |
|---|---|---|
noeviction | 不允许淘汰,内存满时报错 | 不适用冷热分离 |
allkeys-lru | 热点数据集中在部分 key,最近未访问的优先淘汰 | 对偶发扫描敏感,可能挤出真正的热 key |
allkeys-lfu | 访问频率差异大,低频 key 优先淘汰,抗扫描能力强 | 需要计算频率衰减,内存和 CPU 开销稍高 |
volatile-lru | 仅淘汰设置了 TTL 的 key,永久 key 不受影响 | 适合混合永久和临时数据场景 |
volatile-lfu | 仅淘汰带 TTL 的 key 中访问频率最低的 | 同上 |
近似算法实现:Redis 使用采样近似,默认随机采样 5 个 key,淘汰其中最符合策略的。可通过 maxmemory-samples 10 提高精度,但增加 CPU 开销。
5.2 EXPIRE 主动淘汰
对于时效性强的数据,设置过期时间:
SETEX session:token:abc123 7200 "user_id:1001" # 2小时过期
Redis 惰性删除(访问时检查)+ 定期扫描(每秒10次,随机抽取带过期 key 删除),保证冷数据及时清除。
5.3 多级缓存冷热架构
在应用层,缓存通常分为三级(详见本系列第6篇):
- L1 本地缓存(Caffeine):极热数据,微秒级,容量 100MB~1GB。
- L2 分布式缓存(Redis):热数据,毫秒级,容量数十 GB。
- L3 持久化存储(MySQL/ES):温/冷数据,磁盘级。
当数据被 Redis 淘汰后,下次请求 miss,从 L3 加载并重新放入 Redis(加热)。这种机制天然实现了“按访问频率自动冷热分离”。
5.4 Redis 淘汰机制流程图
flowchart TD
A[请求 Key] --> B{内存中命中?}
B -->|Y| C[返回数据]
B -->|N| D[查询后端 DB]
D --> E[数据写入 Redis]
E --> C
F[内存达到 maxmemory] --> G{淘汰策略}
G -->|allkeys-lru| H[采样N个Key, 淘汰 idle 最长的]
G -->|allkeys-lfu| I[采样N个Key, 淘汰频率最低的]
G -->|volatile-*| J[仅在带 TTL 的 Key 中采样淘汰]
H --> K[Key 被逐出]
I --> K
J --> K
图4-Redis maxmemory-policy 淘汰机制图
图表结构说明:流程图展示了 Redis 处理缓存请求和内存淘汰的两条路径。
核心流程解析:当内存满时,Redis 根据策略从样本中选出要淘汰的 key 并删除。被淘汰的 key 如果后续被请求,会再次从数据库加载,重新成为热数据。
关键技术细节:LRU 和 LFU 均基于对象的 lru 字段(24 bits),记录了访问时间或频率计数。LFU 使用对数计数器并随时间衰减,防止历史高频键永不淘汰。
性能与成本分析:通过淘汰策略,Redis 可以用 8GB 内存支撑 100GB+ 数据集的访问热度,只要大部分请求命中热数据。相比全量存储于内存,成本降低 90% 以上。
6. ClickHouse 冷热分离:TTL 自动搬迁 + 多存储卷
6.1 TTL 机制与存储策略配置
ClickHouse 的 TTL(Time To Live)可作用于表或列,配合多磁盘存储策略自动搬迁数据。首先定义存储策略:
<storage_configuration>
<disks>
<hot_disk>
<path>/data/hot/</path>
</hot_disk>
<cold_disk>
<path>/data/cold/</path>
</cold_disk>
</disks>
<policies>
<hot_cold_policy>
<volumes>
<hot_volume>
<disk>hot_disk</disk>
</hot_volume>
<cold_volume>
<disk>cold_disk</disk>
</cold_volume>
</volumes>
</hot_cold_policy>
</policies>
</storage_configuration>
创建表时指定:
CREATE TABLE logs (
event_time DateTime,
level String,
message String
) ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(event_time)
ORDER BY (event_time, level)
TTL event_time + INTERVAL 30 DAY TO VOLUME 'cold_volume',
event_time + INTERVAL 90 DAY DELETE
SETTINGS storage_policy = 'hot_cold_policy';
6.2 TTL 搬迁原理
后台 Merge 线程会定期检查每个 Part 中的最小和最大时间,如果 Part 内所有行都满足 TTL 搬迁条件,则整个 Part 被移动到冷卷;如果只有部分满足,则会发生内部拆分和移动。DELETE TTL 若整个 Part 过期,直接删除文件。
性能影响:搬迁是文件系统级别的移动(rename),速度极快,不阻塞读写。写入只往热卷写,查询可自动跨卷读取,对用户透明。
6.3 与 pt-archiver 对比
| 特性 | ClickHouse TTL | MySQL pt-archiver |
|---|---|---|
| 实现机制 | 内置引擎异步自动 | 外部工具 + 定时任务 |
| 搬迁粒度 | Part 级(文件移动) | 行级(SELECT + DELETE) |
| 对写入的影响 | 无阻塞 | 轻微负载,可通过 sleep 控制 |
| 查询透明度 | 完全透明,自动合并多卷数据 | 需应用路由或视图 |
| 支持删除 | Part 级快速删除,无 Binlog 压力 | 逐行删除,产生大量 Binlog |
| 适用场景 | 时序数据、日志分析 | 在线业务订单、事务数据 |
6.4 ClickHouse TTL 搬迁流程图
flowchart TD
A["写入"] --> B["Hot Volume<br/>SSD"]
B --> C{"后台 Merge 检查 TTL"}
C -- "Part 内全过期" --> D["直接删除 Part"]
C -- "满足搬迁条件" --> E["文件移动至 Cold Volume<br/>HDD"]
C -- "未触发" --> B
E --> F["数据仍可查询,延迟稍高"]
D --> G["数据消失"]
classDef decision fill:#f1f5f9,stroke:#334155,stroke-width:2px,color:#0f172a
classDef process fill:#f8fafc,stroke:#64748b,stroke-width:2px,color:#1e293b
class C decision
class A,B,D,E,F,G process
图5-ClickHouse TTL 多存储卷搬迁图
图表结构说明:流程图展示了 ClickHouse 数据写入热卷,以及后台 Merge 依据 TTL 进行搬迁或删除的过程。
核心流程解析:数据分区被 TTL 规则驱动,自动在热卷和冷卷间流转。查询时,ClickHouse 会并行读取热卷和冷卷的 Parts,合并结果返回。
关键技术细节:可以通过 ALTER TABLE ... MODIFY TTL 在线调整策略。使用 system.parts 表可监控 Part 所在磁盘和 TTL 信息。
性能与成本分析:冷卷使用 HDD 存储,成本为 SSD 的 1/5。对于 90% 以上查询只覆盖热数据的情况,性能几乎不受影响。大规模日志分析场景的存储成本可节省 70% 以上。
7. Spring Batch 数据迁移流水线:Job/Step/Tasklet + 断点续传 + 定时调度
当跨存储介质迁移(如 MySQL 到 Hive,或 MySQL 到 S3)需要复杂转换时,Spring Batch 提供了强大的批处理框架。
7.1 迁移 Job 设计架构
一个典型的 MySQL 热库到冷库(或其他存储)的迁移 Job 分为三个 Step:
- Step1 (数据读取与写入):
JdbcPagingItemReader从热库分页读取满足条件的记录,JdbcBatchItemWriter批量写入冷库。 - Step2 (数据清理):
Tasklet执行 DELETE 操作,清理热库已迁移的数据(可多次执行,如每次删 10 万行)。 - 可选 Step3 (验证):
Tasklet进行计数校验,确保数据不丢失。
断点续传机制:Spring Batch 的 JobRepository 将每个 Step 的 commit_count、last_commit_position 等状态持久化到 BATCH_STEP_EXECUTION 表。当 Job 失败后重启,框架会自动从上次成功的位置继续读取(前提是 Reader 实现了 ItemStream 且数据库支持游标)。
7.2 完整代码示例(Spring Batch 5.x, JDK 17)
@Configuration
@EnableBatchProcessing
public class ColdMigrationJobConfig {
@Autowired private JobRepository jobRepository;
@Autowired private PlatformTransactionManager transactionManager;
@Autowired private DataSource hotDataSource;
@Autowired private DataSource coldDataSource;
@Bean
public Job coldMigrationJob() {
return new JobBuilder("coldMigrationJob", jobRepository)
.start(readWriteStep())
.next(cleanupStep())
.next(verifyStep())
.build();
}
// Step1: 读取热数据并写入冷库,使用 Chunk 模式
@Bean
public Step readWriteStep() {
return new StepBuilder("readWriteStep", jobRepository)
.<Order, Order>chunk(1000, transactionManager)
.reader(reader(null))
.writer(writer())
.faultTolerant()
.retryLimit(3)
.retry(DataAccessException.class)
.build();
}
@Bean
@StepScope
public JdbcPagingItemReader<Order> reader(
@Value("#{jobParameters['cutoffDate']}") String cutoffDate) {
return new JdbcPagingItemReaderBuilder<Order>()
.name("hotOrderReader")
.dataSource(hotDataSource)
.selectClause("SELECT id, user_id, amount, created_at")
.fromClause("FROM orders")
.whereClause("WHERE created_at < :cutoffDate ORDER BY id ASC")
.parameterValues(Map.of("cutoffDate", cutoffDate))
.sortKey("id")
.pageSize(1000)
.rowMapper(new OrderRowMapper())
.saveState(true) // 开启状态保存,支持断点续传
.build();
}
@Bean
public JdbcBatchItemWriter<Order> writer() {
return new JdbcBatchItemWriterBuilder<Order>()
.dataSource(coldDataSource)
.sql("INSERT INTO orders_archive (id, user_id, amount, created_at) " +
"VALUES (:id, :userId, :amount, :createdAt)")
.beanMapped()
.build();
}
// Step2: 清理热库数据,采用 Tasklet 循环删除,避免大事务
@Bean
public Step cleanupStep() {
return new StepBuilder("cleanupStep", jobRepository)
.tasklet(cleanupTasklet(null), transactionManager)
.build();
}
@Bean
@StepScope
public Tasklet cleanupTasklet(
@Value("#{jobParameters['cutoffDate']}") String cutoffDate) {
return (contribution, chunkContext) -> {
JdbcTemplate jdbc = new JdbcTemplate(hotDataSource);
int deletedTotal = 0;
int batchSize = 10000;
int deleted;
do {
// 使用 LIMIT 分批删除,避免长事务
deleted = jdbc.update(
"DELETE FROM orders WHERE created_at < ? LIMIT ?",
cutoffDate, batchSize);
deletedTotal += deleted;
// 可适当休眠
Thread.sleep(100);
} while (deleted >= batchSize);
System.out.println("清理完成,共删除:" + deletedTotal + " 条");
return RepeatStatus.FINISHED;
};
}
// Step3: 数据校验 Tasklet
@Bean
public Step verifyStep() {
return new StepBuilder("verifyStep", jobRepository)
.tasklet(verifyTasklet(), transactionManager)
.build();
}
@Bean
public Tasklet verifyTasklet() {
return (contribution, chunkContext) -> {
// 简单计数对比逻辑
return RepeatStatus.FINISHED;
};
}
}
断点续传细节:JdbcPagingItemReader 在每次 chunk 提交时,会将当前 id 的最大值作为读取位置保存在 ExecutionContext 中。重启时,Reader 会从该位置继续分页,从而避免重复读取或遗漏。
定时调度:
@Scheduled(cron = "0 0 2 * * ?")
public void performMigration() throws Exception {
JobParameters params = new JobParametersBuilder()
.addString("cutoffDate", LocalDate.now().minusDays(30).toString())
.addLong("time", System.currentTimeMillis())
.toJobParameters();
jobLauncher.run(coldMigrationJob(), params);
}
监控与告警:通过 Actuator 端点 /actuator/batch 可获取 Job 执行状态、失败异常等。可结合 Prometheus 导出指标,配置报警规则。
8. 存储成本量化对比:SSD vs HDD vs S3 的成本计算模型与实例
8.1 存储介质单位成本(云环境参考)
| 存储类型 | 典型单价 (元/GB/月) | IOPS / 吞吐能力 | 适用数据温度 |
|---|---|---|---|
| 高性能 SSD | 0.8 ~ 1.2 | 高 IOPS,低延迟 | 热数据 |
| 普通 SSD | 0.5 ~ 0.8 | 中高 IOPS | 热/温数据 |
| HDD | 0.1 ~ 0.3 | 低 IOPS,顺序读好 | 温/冷数据 |
| 对象存储S3 | 0.02 ~ 0.05 | 高吞吐,延迟高 | 冷/归档数据 |
| 归档存储 | 0.005 ~ 0.01 | 极低,分钟级取回 | 归档数据 |
8.2 实际案例成本计算
假设:日志平台日增 200GB 原始数据,压缩率 0.3(实际存储 60GB/天)。数据保留 90 天。热数据定义 3 天内,占总查询 90%。
全热存储方案:90 天总数据量 = 60GB/天 × 90 = 5400GB。全部用高性能 SSD,月成本 5400 × 0.8 = 4320 元/月。
冷热分离方案(ES ILM):
- 热数据(3天):60GB × 3 = 180GB,SSD 成本 180 × 0.8 = 144 元/月。
- 冷数据(87天):60GB × 87 = 5220GB。使用 S3 存储,成本 5220 × 0.03 = 156.6 元/月。
- 冷节点本地缓存:预留 10% 缓存 522GB × 0.3 ≈ 157 元(HDD)。
- 总成本约 144 + 157 + 156.6 ≈ 457.6 元/月,节省 89.4%。
8.3 存储成本对比图
barChart
title 存储成本对比 (日增200GB日志, 90天保留)
"全SSD" : 4320
"热SSD+冷S3 (ILM)" : 458
"热SSD+冷HDD" : 1200
"全S3" : 162
图6-存储成本对比图
图表结构说明:柱状图直观对比了四种存储方案下的月成本,突出冷热分离的成本优势。
核心流程解析:随着冷数据从 SSD 向 S3 迁移,成本呈指数级下降。全 S3 虽然成本最低,但查询性能无法满足热数据要求。
关键技术细节:实际还需考虑数据传出费用(S3 GET)、API 调用费、跨区域传输等,但通常不超过存储费的 15%。
性能与成本分析:冷热分离方案在几乎不牺牲热数据查询性能的前提下,使整体存储成本降低 80%~95%,是海量数据系统的必然选择。
9. 面试高频专题
Q1: 什么是冷热分离?热、温、冷、归档数据分别如何定义和存储? 一句话回答:冷热分离是根据数据访问频率和时效性将数据分层存储的策略,热数据存 SSD/内存,温数据存 HDD/SSD,冷数据存 HDD/对象存储,归档数据存对象存储/磁带。 详细解释:数据温度通过时间(创建/修改)和访问频率衡量。热数据(近7天高频)对性能敏感,使用 SSD 或 Redis;温数据(7-30天低频)对成本略敏感,可迁至 HDD;冷数据(>30天极少访问)追求极低成本,放至 S3/MinIO;归档数据(>90天合规保留)几乎不查询,使用冷存储或磁带。分层后存储成本逐级降低一个数量级。 多角度追问:
- 为什么不用全 SSD?海量数据下成本不可承受,且大部分数据很少访问,全 SSD 浪费。
- 冷热分离对应用透明吗?取决于实现:ES ILM/ClickHouse TTL 透明;MySQL 归档表或 S3 需应用路由或联邦查询。
- 如何保证归档数据安全?使用对象存储的多版本、WORM 锁定、跨区域复制。 加分回答:可以结合数据价值衰减曲线动态调整阈值,实现更精细的分层。
Q2: 冷热分离有哪三种策略?时间驱动和访问频率驱动各适合什么场景?
一句话回答:时间驱动、访问频率驱动、混合驱动。时间驱动适合日志/订单等时效性数据,频率驱动适合文章/帖子等无明确时间边界的热点数据。
详细解释:时间驱动简单可靠,按 created_at 迁移,但不能区分旧热点。频率驱动按访问次数或最后访问时间判断,更精准,但需额外监控(如 Redis 计数器)和衰减模型。混合驱动先时间初筛再频率确认,兼顾安全与精准,适用于大流量平台。
多角度追问:
- 如何实现频率驱动?在应用层或缓存层统计,定期写入数据库,迁移任务读取频率字段。
- 如果旧数据突然变热,频率驱动如何响应?可设计“回温”机制:当冷数据被访问时,从冷存储加载并更新访问计数,下次迁移任务可重新识别。
- 混合驱动中频率阈值如何确定?分析 P99 访问分布,结合业务容忍度设定。 加分回答:引入时间衰减因子,使频率计数随时间指数下降,更能反映近期热度。
Q3: MySQL 的 pt-archiver 如何实现在线归档?--max-lag 参数起什么作用?
一句话回答:pt-archiver 通过主键游标分批迁移、每批独立事务实现不锁表归档;--max-lag 监控从库延迟并自动暂停归档,保护主从同步。
详细解释:pt-archiver 循环执行 SELECT ... LIMIT → INSERT 目标 → DELETE 源表(同一事务)。--max-lag=5 在每批后检查 Seconds_Behind_Master,若超过 5 秒则 sleep 直至恢复,防止归档产生的 binlog 冲击从库读服务。此外 --sleep 控制批间间隔,降低主库负载。
多角度追问:
- 无主键表如何归档?指定唯一键作为游标,或使用
--no-delete归档后手动清理。 - 归档后源表空间不释放怎么办?需在低峰期执行
OPTIMIZE TABLE,会锁表,应谨慎。 - 如何校验归档完整性?
pt-table-checksum或对比源表与目标表行数。 加分回答:使用--bulk-delete和--limit2000,事务大小可控且高效。
Q4: ES 的 ILM 四阶段各自做什么?searchable_snapshot 原理是什么?
一句话回答:Hot 写入和滚动,Warm 合并分片和段,Cold 迁至可搜索快照,Delete 删除。searchable_snapshot 将索引存为 S3 快照并按需本地缓存。
详细解释:Hot 阶段用 SSD,通过 rollover 控制索引大小;Warm 阶段索引只读,执行 shrink(减少分片)和 forcemerge(合并段)优化资源;Cold 阶段执行 searchable_snapshot,将数据上传到对象存储,本地只保留元数据和热点缓存,查询时从 S3 拉取缺失块,首次延迟高但成本降低 80%;Delete 阶段到期删除。
多角度追问:
forcemerge为何在 Warm 做?因为该阶段索引已无写入,且只读后可安全合并,减少后续搜索开销。- 可搜索快照对内存有要求吗?需要本地缓存磁盘,一般建议 10% 的索引大小,使用 SSD 或 HDD。
- Cold 阶段搜索延迟能否接受?对于日志分析偶尔查询可接受秒级延迟,频繁查询场景不适合。 加分回答:ES 7.12+ 的 frozen tier 更进一步,本地几乎不缓存数据,查询时全量从快照下载,成本更低。
Q5: Redis maxmemory-policy 有哪几种?allkeys-lru 和 allkeys-lfu 区别?
一句话回答:allkeys-lru 淘汰最久未使用的键,适合热点集中场景;allkeys-lfu 淘汰使用频率最低的键,适合访问频率差异明显、需抗扫描的场景。
详细解释:allkeys-lru 近似 LRU,只关心最近访问时间,实现简单,但在“批量冷数据扫描”时可能误淘汰热数据。allkeys-lfu 近似 LFU,通过对数计数器记录频率并随时间衰减,更能保留真正的高频键,但消耗更多内存和 CPU。volatile-lru/lfu 仅淘汰带 TTL 的键。
多角度追问:
- 什么是 LFU 衰减?防止历史高频键永不淘汰,系统定期减少计数,使最近不访问的键逐渐降温。
volatile-lru何时使用?当有永久常驻的配置 key 时,只淘汰临时数据。- 如何监控淘汰情况?
INFO stats中evicted_keys,INFO memory中used_memory。 加分回答:可调整maxmemory-samples平衡精度和 CPU 开销,LFU 模式下建议增加到 10。
Q6: ClickHouse TTL 如何实现冷热分离?TO VOLUME 语法如何工作?
一句话回答:TTL event_time + INTERVAL 30 DAY TO VOLUME 'cold' 将过期分区从热卷移至冷卷,由后台 Merge 线程异步执行。
详细解释:ClickHouse MergeTree 表定义 TTL,后台 Merge 检查 Part 内所有行是否满足 TTL 条件,满足则整个 Part 移动到另一存储卷(文件 rename)。若部分满足,则拆分并移动。DELETE TTL 满足则直接删除 Part,效率极高。多存储卷需在存储策略中定义磁盘和卷。
多角度追问:
- TTL 搬迁是原子性的吗?文件移动是原子性操作,不会出现中间态。
- 搬迁对查询的影响?查询会自动合并热卷和冷卷数据,用户透明。
- 如何监控 TTL 进度?查询
system.parts表,看disk_name和ttl_info。 加分回答:列级 TTL 可自动删除或移动特定列,节省更多空间。
Q7: Spring Batch 如何编排冷热分离迁移任务?断点续传如何实现?
一句话回答:通过 Job/Step/Tasklet 定义迁移流水线,JdbcPagingItemReader 分页读取,JdbcBatchItemWriter 批量写入;断点续传依赖 JobRepository 保存读取位置(分页游标),重启后从上次处继续。
详细解释:Step 使用 Chunk 模式,每处理一个 chunk 提交事务并记录 commit_count。Reader 基于主键排序分页,每页读取后保存当前最大 id 到 ExecutionContext。失败重启时,Reader 从 ExecutionContext 恢复上次位置继续分页,确保不重不漏。
多角度追问:
- 如果清理 Step 失败但数据已写冷库怎么办?保证冷库写入幂等(如使用 REPLACE INTO),清理重试。
- 如何并行加速迁移?使用
Partitioner按 id 范围分区,启动多线程并发执行 Step。 - 如何监控迁移任务?Actuator 端点或自定义监听器记录日志和指标。 加分回答:可结合 Spring Cloud Task 实现分布式执行,记录每个 Job 实例的状态。
Q8: 冷热分离后冷数据如何查询?联邦查询引擎解决什么问题? 一句话回答:可通过应用层路由(代码判断时间查热/冷库)或联邦查询引擎(Presto/Trino)统一 SQL 查询多个异构源。 详细解释:应用层路由简单,但耦合度高;联邦查询引擎提供统一接口,连接 MySQL 热库和 Hive/S3 冷数据,一条 SQL 自动下推查询并合并,对应用完全透明。代价是查询延迟可能较高,尤其跨源 JOIN 时需拉取大量数据。 多角度追问:
- Trino 如何优化查询?利用分区裁剪、谓词下推,只扫描需要的数据。
- 跨库事务怎么办?冷数据通常只读,无需事务;若需强一致,只能应用层保证。
- S3 上的数据如何组织?通常以列存格式(Parquet)存储,按日期分区。 加分回答:可构建冷热分离视图,使用 ShardingSphere 虚拟库隐藏后端异构源。
Q9: 如何量化冷热分离的成本节省?1TB 数据全热 vs 冷热分离年化差异? 一句话回答:1TB 全 SSD 热存月成本约 800 元,冷热分离(热SSD+冷S3)月成本约 107 元,年化节省约 8300 元,降幅 86%。 详细解释:成本=数据量×月单价。假设热数据 100GB,冷数据 900GB。全 SSD 成本 1000×0.8=800 元/月。冷热分离:100×0.8 + 900×0.03 = 107 元/月。年节省 (800-107)×12=8316 元。数据量越大,节省越显著。 多角度追问:
- 对象存储 API 费用计入吗?GET 请求费用约占存储费 5%~10%,通常可忽略。
- 压缩率如何影响?成本基于压缩后大小,ILM 快照和 ClickHouse 都有压缩,可减少存储量。
- 是否考虑冷节点运营成本?冷节点通常用低配机器,分摊后成本仍极低。 加分回答:使用云平台的成本计算器,可精确模拟包括数据传输在内的全部费用。
Q10: ES ILM 的 forcemerge 和 shrink 分别起什么作用?为什么只在 Warm 阶段做?
一句话回答:shrink 减少主分片数以节省内存,forcemerge 合并段加速搜索;两者均要求索引只读,故在 Warm 阶段执行。
详细解释:Hot 阶段索引持续写入,无法只读。Warm 阶段数据不再变化,可安全 shrink(将多个分片硬链接合并为一个)和 forcemerge(合并段为 1 个)。shrink 降低集群状态和堆内存占用;forcemerge 减少搜索需打开的段数,并物理清除删除文档,提升查询效率。
多角度追问:
forcemerge有何代价?高 IO/CPU,须在低峰期执行,ILM 会自动控制。- 若索引很小是否仍需?小于 5GB 可跳过,但合并仍有益处。
- 是否可以在 Cold 阶段 shrink?应在 Warm 阶段完成,Cold 阶段数据已作为快照,shrink 无意义。
加分回答:合并为 1 个段后,可设置
index.blocks.write: true防止后续 merge 破坏单段结构。
Q11: 冷热分离如何与分库分表叠加?热分片和冷分片实例配置有何不同?
一句话回答:在分库分表基础上引入时间维度,热分片使用高配 SSD 实例,冷分片使用低配 HDD 实例,通过复合分片算法按 user_id 和时间路由。
详细解释:分库分表键保持 user_id,同时增加 create_time 作为二级分片条件。例如 user_id % 4 定位物理库,再根据时间判断表后缀 _hot 或 _cold。热分片(8C16G SSD)负责近 30 天数据,冷分片(4C8G HDD)负责更早数据。数据迁移通过定时任务按用户维度归档到冷分片。
多角度追问:
- 跨时间查询如何合并?应用层分别查询热冷表并排序合并,或使用联邦引擎。
- 冷分片是否需要备份?需要,但备份频率可降低,恢复时间更长。
- 如何迁移特定用户数据?以
user_id分组,按批次迁移每个用户的冷数据,便于后续分片查询。 加分回答:可通过滚动分片实现无限扩容,每月新建热分片,历史分片自动变为温、冷分片。
Q12 (系统设计题): 日志监控平台冷热分离架构设计 题目:一个日志监控平台日增 200GB 日志,90% 的查询访问近 3 天数据,日志需保留 90 天(合规)。请设计: (1) 冷热分离策略设计(冷热时间边界、存储介质选型) (2) ES ILM 策略完整定义(Hot/Warm/Cold/Delete 各阶段配置与阈值) (3) 冷热分离后的查询路由方案(应用层路由 vs Presto 联邦查询) (4) 存储成本计算(全热存储 vs 冷热分离的年化成本对比) (5) 迁移失败的监控与告警方案。
答案详细展开:
(1) 冷热分离策略设计
- 时间边界:热数据 0-3 天,温数据 3-7 天,冷数据 7-90 天,超过 90 天删除。
- 存储介质:热节点使用本地 NVMe SSD,温节点使用 SATA SSD 或 HDD,冷节点使用 HDD 并挂载 S3 可搜索快照。
- 索引策略:按天创建索引
logs-2026.05.18,ILM 自动滚动,保证活跃索引保持较小的体积。
(2) ES ILM 策略完整定义
(与 4.2 节类似,调整阈值):
PUT _ilm/policy/logs_policy
{
"phases": {
"hot": {
"actions": {
"rollover": { "max_primary_shard_size": "50gb", "max_age": "1d" },
"set_priority": { "priority": 100 }
}
},
"warm": {
"min_age": "3d",
"actions": {
"allocate": { "number_of_replicas": 1, "require": { "data": "warm" } },
"shrink": { "number_of_shards": 1 },
"forcemerge": { "max_num_segments": 1 },
"set_priority": { "priority": 50 }
}
},
"cold": {
"min_age": "7d",
"actions": {
"allocate": { "number_of_replicas": 0, "require": { "data": "cold" } },
"searchable_snapshot": { "snapshot_repository": "s3_backup" },
"set_priority": { "priority": 0 }
}
},
"delete": {
"min_age": "90d",
"actions": { "delete": {} }
}
}
}
索引模板关联:
PUT _index_template/logs_template
{
"index_patterns": ["logs-*"],
"template": {
"settings": {
"number_of_shards": 4,
"number_of_replicas": 1,
"index.lifecycle.name": "logs_policy",
"index.lifecycle.rollover_alias": "logs-write"
}
}
}
(3) 查询路由方案
- 方案A(应用层路由):在 Kibana 或自研查询界面,用户选择时间范围,系统根据时间范围自动生成查询。若范围超出热数据,查询透明跨热和冷(因为索引命名统一,ES 自动跨索引搜索)。
- 方案B(联邦查询):如果冷数据导出到 S3 并以 Parquet 格式存储,可使用 Trino 连接 ES 热集群和 S3 冷数据,通过统一 SQL 查询,适合复杂分析。
- 推荐:利用 ES 的可搜索快照,冷数据仍在 ES 集群内(虽然数据在 S3),查询无需联邦,ES 本身支持跨热冷搜索,实现完全透明。只需确保冷节点缓存可覆盖常用冷数据即可。
(4) 存储成本计算
- 日增 200GB 日志,经压缩(LZ4)后约为 60GB。90 天需 5.4TB。
- 全热存储(高性能 SSD):5.4TB × 0.8 元/GB = 4320 元/月。年 5.18 万元。
- ILM 冷热分离:
- 热数据 3 天:0.18TB × 0.8 = 144 元。
- 温数据 4 天:0.24TB × 0.3 = 72 元。
- 冷数据 83 天:4.98TB × 0.03 (S3) ≈ 149.4 元。
- 冷节点本地缓存(20% of 冷数据):0.996TB × 0.3 ≈ 298.8 元。
- 总计约 664.2 元/月。年约 7970 元。节省 84.6%。
(5) 迁移失败的监控与告警
- ES ILM 监控:使用
GET _ilm/status查看操作模式,指标ilm.operation_mode应为RUNNING。监控索引停留在某阶段超时(如超过 4 天未进入 Warm),通过 Watcher 报警。 - 快照失败:监控
_snapshot/s3_backup/_status,检查state不为SUCCESS则报警。 - 冷节点磁盘水位:冷节点缓存磁盘使用率>80% 告警。
- 搜索延迟:监控 Cold 阶段索引的查询 P99 延迟,若>2s 需检查缓存命中率或扩容冷节点。
系统架构图(日志平台冷热分离)
flowchart TD
subgraph 数据输入
A[日志生产者]
end
subgraph 热层
B[Load Balancer] --> C[ES Hot 节点 x3]
C -->|rollover| D[索引模板: logs-*]
end
subgraph 温层
E[ES Warm 节点 x2]
end
subgraph 冷层
F[ES Cold 节点 x2]
G[对象存储 S3]
end
subgraph 查询层
H[Kibana / Grafana]
I[Trino 联邦查询]
end
A --> B
C -- ILM 3d --> E
E -- ILM 7d --> F
F -- 可搜索快照 --> G
H --> C
H --> F
I --> C
I --> G
图7-日志监控平台冷热分离架构图
架构图说明:日志通过负载均衡写入 Hot 节点,ILM 自动将索引滚动到 Warm 节点(HDD)然后到 Cold 节点(快照存 S3)。Kibana 直接查询 ES,可跨热冷;Trino 可选用于联邦查询。
业务流程图(迁移与查询)
sequenceDiagram
participant Producer as 日志生产
participant ES_Hot as ES Hot (写入)
participant ILM as ILM 引擎
participant ES_Warm as ES Warm
participant S3 as S3
participant ES_Cold as ES Cold (缓存)
participant User as 用户查询
Producer->>ES_Hot: 写入索引 logs-2026.05.18
ES_Hot->>ILM: 索引满足 rollover 条件
ILM->>ES_Hot: 创建新索引 logs-2026.05.19
ILM->>ES_Warm: 3 天后迁移 logs-2026.05.18 到 warm,shrink & forcemerge
ILM->>S3: 7 天后将索引作为快照上传
ILM->>ES_Cold: 挂载为可搜索快照
User->>ES_Hot: 查询近2天日志
ES_Hot-->>User: 快速返回
User->>ES_Cold: 查询10天前日志
ES_Cold->>S3: 缓存未命中,拉取数据块
S3-->>ES_Cold: 返回数据
ES_Cold-->>User: 返回结果 (延迟稍高)
图8-日志查询与时序图
时序图说明:展示了日志写入、ILM 自动流转,以及查询热数据和冷数据的完整交互过程。冷数据首次查询需从 S3 拉取,延迟上升。
通过上述设计,日志监控平台能够在满足合规要求的前提下,以极低的存储成本运行,同时保证近期数据的查询性能。
冷热分离存储架构速查表
| 工具/中间件 | 冷热策略 | 核心机制 | 关键配置/命令 | 成本优化点 | 适用场景 |
|---|---|---|---|---|---|
| MySQL | 时间驱动(pt-archiver / 分区) | 分批游标迁移 + 事务保护 + 从库延迟检查 | pt-archiver --limit 2000 --max-lag 5 --bulk-delete | 冷表索引精简;冷库使用 HDD/低配实例 | 订单、账单归档 |
| Elasticsearch | 时间驱动(ILM) | Hot→Warm→Cold→Delete 四阶段自动流转;shrink/forcemerge/可搜索快照 | ILM policy JSON;node.roles: data_hot/warm/cold | Cold 阶段快照存 S3,降低 80% 存储成本;Warm 阶段合并分片段 | 日志、安全事件、Metrics |
| Redis | 频率驱动(内存淘汰) | maxmemory 策略(LRU/LFU)+ TTL;热数据常驻,冷数据淘汰 | maxmemory-policy allkeys-lru;EXPIRE | 合理设定 maxmemory,避免内存浪费;配合本地缓存减少回源 | 热点缓存、Session、计数器 |
| ClickHouse | 时间驱动(TTL + 多卷) | 表/列 TTL 表达式,后台 Merge 按 Part 搬迁或删除 | TTL event_time + INTERVAL 30 DAY TO VOLUME 'cold' | 冷数据放 HDD,热数据 SSD;过期自动删除释放空间 | OLAP 分析、时序数据库 |
| Spring Batch | 混合/自定义 | Job/Step/Tasklet 编排;分页读取、批量写入、清理;断点续传 | JdbcPagingItemReader + JdbcBatchItemWriter + Tasklet | 自动化迁移降低运维成本;支持跨存储介质复杂迁移 | 跨系统数据同步、归档任务 |
延伸阅读:
- Percona Toolkit 官方文档:
pt-archiver详解 - Elasticsearch 官方 ILM 与 searchable snapshot 指南
- ClickHouse 官方 TTL 与 Storage Policies
- Spring Batch 参考指南
- 《The Data Warehouse Toolkit》第5章:数据生命周期管理
本文通过从策略到工具、从机制到成本的完整闭环,深入揭示了冷热分离作为数据架构成本控制核心手段的工程价值。在下一篇文章《缓存策略》中,我们将继续探讨如何用多级缓存进一步榨取性能。