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 数据源选型决策树

说了这么多,到底怎么选?我画了一张决策图,帮你快速定位:

数据源选型决策树 数据源选型 关系型数据库 消息队列 文件系统 MySQL PostgreSQL Kafka RabbitMQ HDFS S3 数据量 < 1亿 团队熟悉 MySQL 数据量 > 1亿 需要复杂查询 高吞吐管道 日志/事件流 任务调度/重试 低延迟消息 自建 Hadoop 集群 数据本地性要求高 云原生架构 按量付费

4.5 组合选型策略

实际项目中,很少只用一种数据源。我总结了几种常见组合:

  • 传统数仓方案:MySQL(业务库)→ Kafka(数据管道)→ HDFS(存储)→ Spark/Hive(计算)。适合已有 MySQL 业务库、需要构建离线数仓的团队。
  • 云原生方案:PostgreSQL(业务库)→ Kafka(数据管道)→ S3(存储)→ EMR/Athena(计算)。适合上云团队,弹性伸缩,运维成本低。
  • 轻量级方案:MySQL(业务库)→ RabbitMQ(任务队列)→ 本地文件系统(临时存储)→ 应用处理。适合中小团队,数据量不大,追求快速交付。

我的经验:选型时不要只看技术指标,还要考虑团队能力。如果团队对 Kafka 运维不熟,硬上 Kafka 反而会出问题。我见过一个团队用 RabbitMQ 处理每天 2 亿条日志,虽然性能勉强够,但运维成本比 Kafka 高得多。说白了,选型是技术、成本、团队三者的平衡。

4.6 避坑总结

最后,把我在项目中踩过的坑列出来,你遇到类似情况可以绕开:

  1. MySQL 大表全量扫描:不要用 SELECT * 一次性读取,用游标或分页,配合 WHERE id > ? 条件。
  2. Kafka 消费端处理超时:批处理任务处理时间不可控时,增大 max.poll.interval.ms,或者把任务拆小。
  3. S3 小文件问题:合并小文件到 64MB 以上,或者使用 S3 Multipart Upload 并行写入。
  4. PostgreSQL 连接数耗尽:批处理任务使用连接池,控制最大连接数,避免长事务占用连接。
  5. RabbitMQ 消息堆积:设置队列最大长度和 TTL,防止内存溢出。