4. 数据源选型:关系型数据库、消息队列、文件系统
数据源选型,说白了就是决定你的批处理系统从哪读数据、往哪写数据。我见过不少团队,一上来就选最火的组件,结果跑起来才发现吞吐上不去、数据对不齐、运维成本爆炸。今天咱们就把关系型数据库、消息队列、文件系统这三类数据源掰开揉碎,讲讲各自的适用场景和坑。
4.1 关系型数据库:MySQL vs PostgreSQL
关系型数据库在批处理里通常扮演两个角色:一是作为数据源,二是作为结果存储。我个人习惯把 MySQL 和 PostgreSQL 放在一起对比,因为它们虽然都是 SQL 数据库,但批处理场景下的表现差异很大。
| 维度 | MySQL | PostgreSQL |
|---|---|---|
| 批量读取性能 | 单表千万级,分页查询较慢 | 支持游标、流式读取,大表友好 |
| 批量写入性能 | LOAD DATA INFILE 极快 | COPY 命令高效,但略慢于 MySQL |
| 并发控制 | 默认 RR 隔离,间隙锁容易死锁 | MVCC 实现更成熟,读写不冲突 |
| 分区表支持 | 支持,但分区裁剪有限 | 声明式分区,性能稳定 |
| 扩展性 | 主从复制成熟,分库分表方案多 | 逻辑复制灵活,但分片方案较少 |
举个例子。我曾经接手过一个日处理 5000 万订单的批处理系统,数据源是 MySQL。每次跑全量同步都要 6 小时,而且经常因为间隙锁导致业务库卡死。后来我们把数据源切到了 PostgreSQL,用了游标流式读取,全量时间降到了 2 小时以内。嗯,这里要注意:MySQL 的 InnoDB 引擎在 RR 隔离级别下,SELECT ... FOR UPDATE 会加间隙锁,批处理任务如果长时间持有事务,很容易引发连锁死锁。
核心建议:批处理场景优先选 PostgreSQL。如果团队 MySQL 生态成熟,务必使用 READ COMMITTED 隔离级别,并配合 LIMIT ... OFFSET 分页或基于主键的游标分页。
4.2 消息队列:Kafka vs RabbitMQ
消息队列在批处理架构里,主要用来做数据管道——上游生产数据,下游批量消费。很多人觉得消息队列就是用来做实时消息的,其实它在批处理里也有重要位置。
先看一个对比表格:
| 特性 | Kafka | RabbitMQ |
|---|---|---|
| 设计定位 | 分布式日志流 | 消息代理 |
| 消息持久化 | 磁盘顺序写,天生持久化 | 内存+磁盘,可配置持久化 |
| 消费模式 | 拉模式(Pull),支持批量拉取 | 推模式(Push),也支持拉 |
| 吞吐量 | 百万级/秒 | 万级/秒 |
| 消息顺序 | 分区内严格有序 | 单队列有序,多消费者无序 |
| 重试机制 | 需自行实现死信队列 | 内置死信交换器,重试友好 |
我个人习惯这样选:
- 批处理数据管道:无脑选 Kafka。它的分区机制天然支持并行消费,批量拉取能大幅减少网络开销。我曾经用 Kafka 做过一个日志批处理系统,单分区每秒能拉 5000 条消息,配合批量提交 offset,消费端几乎零压力。
- 任务调度与重试:选 RabbitMQ。它的死信队列和延迟队列非常成熟,适合做批处理任务的失败重试和定时触发。我记得有个项目需要把失败的任务延迟 5 分钟重试,RabbitMQ 的
x-delay插件一行配置就搞定了。
避坑指南:我曾经在 Kafka 里遇到过消费端处理太慢导致 rebalance 的问题。原因是批处理任务处理时间超过了 max.poll.interval.ms(默认 5 分钟)。解决方案有两个:一是增大这个超时时间,二是把批处理拆成更小的批次,配合 pause() 和 resume() 控制消费节奏。
4.3 文件系统:HDFS vs S3
文件系统是批处理的「老家」。不管是日志文件、数据导出文件还是中间结果,最终都会落到文件系统上。HDFS 和 S3 是两大主流选择,但它们的定位完全不同。
| 维度 | HDFS | S3 |
|---|---|---|
| 存储模型 | 块存储,数据本地化 | 对象存储,扁平命名空间 |
| 读写模式 | 一次写入,多次读取 | 支持追加、覆盖、版本控制 |
| 吞吐量 | 高吞吐,适合大文件顺序读写 | 高吞吐,但小文件性能差 |
| 扩展性 | 水平扩展,但运维复杂 | 无限扩展,完全托管 |
| 成本 | 硬件成本高,运维成本高 | 按量付费,存储成本低 |
| 数据一致性 | 强一致性 | 最终一致性(部分操作) |
你想想看,如果你的批处理任务跑在 Hadoop/Spark 集群上,HDFS 的数据本地性优势非常明显——计算节点直接读取本地数据,网络开销几乎为零。但如果你用的是云原生架构,S3 配合 EMR 或 Databricks 更合适。
小技巧:S3 对文件数量敏感。我曾经遇到一个任务,往 S3 写了 100 万个 1KB 的小文件,结果 List 操作花了 10 分钟。解决方案是合并小文件,或者使用 S3 Inventory 来替代 List 操作。
4.4 数据源选型决策树
说了这么多,到底怎么选?我画了一张决策图,帮你快速定位:
4.5 组合选型策略
实际项目中,很少只用一种数据源。我总结了几种常见组合:
- 传统数仓方案:MySQL(业务库)→ Kafka(数据管道)→ HDFS(存储)→ Spark/Hive(计算)。适合已有 MySQL 业务库、需要构建离线数仓的团队。
- 云原生方案:PostgreSQL(业务库)→ Kafka(数据管道)→ S3(存储)→ EMR/Athena(计算)。适合上云团队,弹性伸缩,运维成本低。
- 轻量级方案:MySQL(业务库)→ RabbitMQ(任务队列)→ 本地文件系统(临时存储)→ 应用处理。适合中小团队,数据量不大,追求快速交付。
我的经验:选型时不要只看技术指标,还要考虑团队能力。如果团队对 Kafka 运维不熟,硬上 Kafka 反而会出问题。我见过一个团队用 RabbitMQ 处理每天 2 亿条日志,虽然性能勉强够,但运维成本比 Kafka 高得多。说白了,选型是技术、成本、团队三者的平衡。
4.6 避坑总结
最后,把我在项目中踩过的坑列出来,你遇到类似情况可以绕开:
- MySQL 大表全量扫描:不要用
SELECT *一次性读取,用游标或分页,配合WHERE id > ?条件。 - Kafka 消费端处理超时:批处理任务处理时间不可控时,增大
max.poll.interval.ms,或者把任务拆小。 - S3 小文件问题:合并小文件到 64MB 以上,或者使用 S3 Multipart Upload 并行写入。
- PostgreSQL 连接数耗尽:批处理任务使用连接池,控制最大连接数,避免长事务占用连接。
- RabbitMQ 消息堆积:设置队列最大长度和 TTL,防止内存溢出。