数据工程设计模式——微批

88 阅读14分钟

引言(Introduction)

本章将深入剖析微批(micro-batching) 设计模式,并让读者熟悉如何用该模式构建解决方案。内容将涵盖微批可解决的用例;还将基于开源技术讲解如何设计采用微批的系统,并通过示例应用与代码片段加以演示。此外,我们也会介绍微批模式在真实场景中的应用实例。

结构(Structure)

本章涵盖以下主题:

  • 微批的用例
  • 微批系统设计
  • 微批系统的技术选型
  • 真实案例

目标(Objectives)

在本章结束时,你将对微批模式有深入理解;也能够基于微批模式设计并构建数据管道,并编写代码落地该设计。你将了解应选择的技术栈,以及如何将各组件“缝合”起来构建端到端的管道。最后,你还将理解哪些真实业务场景适合采用该模式,并看到来自物流智慧工厂的示例。

微批的用例(Use cases for micro-batching)

微批设计模式可用于多种场景,例如:构建数据湖的数据摄取系统,以及准实时(near real-time)的数据分析。与实时系统相比,这些用例展示了微批如何在大幅降低成本的同时,仍然提供近实时的数据可用性。以下是适合微批模式的一些用例。

向数据湖进行数据摄取(Data ingestion into data lake)

正如前两章所示,批处理与实时系统在时延、吞吐与成本维度各有挑战,无法覆盖所有用例。批处理实现更简单、更低成本,但当场景需要低时延时难以满足;实时系统能提供极低时延,但实现复杂、成本更高。微批作为数据模式,能在二者之间架起桥梁:既吸收两类系统的优势,又规避其部分挑战;与实时系统相比,微批可用更简单、更经济的架构实现近实时能力。

在讨论用微批构建数据湖摄取管道前,先理解小文件问题(small file problem) 。随着以 S3/HDFS + Apache Spark 构建数据湖成为常见做法,当数据以大量极小文件存放与处理时,无论是存储系统(HDFS/S3)还是计算引擎(Spark)都会出现性能瓶颈——源于管理海量小文件的内存开销、并行度下降、存储低效与 I/O 成本上升。实时写入数据湖会即时产出大量小文件,从而触发该问题。传统批处理通过按天/按小时合并数据再处理来规避,但这会引入数据延迟

若数据湖后端为 HDFS,小文件会因每个文件的元数据占用导致NameNode 内存耗尽;且由于块大小为 128/256 MB,文件小于块时会浪费空间,产生无法回收的碎片。若后端为 S3,虽无块大小浪费,但 S3 的费用与文件的 GET/PUT 次数相关:分析时面对海量小文件会导致大量 GET/PUT 调用、处理成本升高。S3 的最佳实践也建议避免过小文件。下面的示例展示了将 10,000 条记录为一批的微批,相较于“逐条写入”,处理 10 亿条记录的高层费用节省(按 AWS 约 $0.005/1000 次 GET/PUT 计):

  • 无批处理成本 = 0.005×1B/1000=0.005 × 1B / 1000 = **5000**
  • 每 10,000 条一批的成本 = 0.005×1B/(1000×10,000)=0.005 × 1B / (1000 × 10,000) = **0.5**

下图展示了从事务型数据库实时摄取到数据湖的架构(图 5.1):

image.png

图 5.1:实时摄取(Real-time ingestion)

微批提供了中间道路:采用批处理架构,但缩短批窗口。这样既能实现近实时处理、避免产生小文件,又能确保数据几乎在产生后即刻可用,且后续分析不再受小文件开销困扰。可见,微批在一套方案中同时解决了实时系统的高成本与传统批处理的数据延迟问题。

下图展示了从事务数据库以微批方式摄取到数据湖的架构(图 5.2):

image.png

图 5.2:微批摄取(Micro-batch ingestion)

近实时数据分析(Near real-time data analysis)

近实时分析要求在事件发生后数秒内数据即可被分析。这不同于批处理(分钟到小时级延迟),也不同于实时系统(毫秒级可接受延迟)。一个示例是:让银行联络中心坐席在几秒内看到客户的交易数据。当客户交易失败后可能拨打热线,客户从操作到拨打中间天然有几秒人工延迟。此问题可由微批系统解决:每隔数秒将交易数据成批导入可被坐席访问的分析系统。

这类交易数据通常运行在大型机或传统 RDBMS(如 Oracle) 上,需要每隔数秒抽取并加载到分析系统。技术上可复用批处理时基于时间戳的抽取方式——无需 CDC 或 Kafka 这类需在变更发生瞬间捕获传输的流式技术,因为此处不要求“毫秒级”摄取。

下图展示了使用微批将数据搬运到分析系统的架构(图 5.3):

image.png

图 5.3:用于分析的微批(Micro-batching for analytics)

数据质量校验(Data quality validations)

为确保数据可用可信,任何数据工程系统都需要数据质量检查:依据预先定义的规则验证数据质量,并在偏离时发出告警。但数据质检往往计算开销大(规则复杂、校验困难)。若在实时路径上做质检,系统复杂度与算力成本都会极高;而若延到日终批再做,期间业务可能已消费了劣质数据,带来损失。

折中方案是利用微批执行质检:依据业务对数据新鲜度的要求,以较短批间隔运行。
示例:金融机构的新客户开户场景。客户地址需录入,但地址常常非标准,城市名可能拼写错误,需要在数据分发至下游系统前规范化。此验证若等到日终批已为时过晚;而地址标准化涉及模糊匹配等昂贵计算,不适合实时执行。于是可用每隔数分钟运行的微批作业,对最近几分钟的新客户做校验,及时发现并修复问题后再下发。

下图展示了客户开户如何受益于微批模式(图 5.4):

image.png

图 5.4:客户开户中的微批应用(Customer onboarding leveraging micro-batching)

设计微批系统(Designing micro-batching system)

本节我们将构建一个微批处理系统,完成以下工作:

  • 从事务型 MySQL 数据库读取数据
  • 使用 Apache Spark 以微批方式处理数据
  • 将数据加载至 ClickHouse 数据库

Spark 作业通过 cron 定时,每若干秒运行一次(如下所示)。此示例与前文帮助理解批处理系统的示例相似,但关键差别在于:这里不是运行日终作业,而是演示每 5 秒运行一次的微批作业。

如下图展示了一个使用微批的简单 ETL 作业(图 5.5):

image.png

图 5.5:使用微批的 ETL(ETL with micro-batching)

系统包含一张名为 sales_table 的 MySQL 表,存放当月销售数据。表中有 transaction_ts 列,记录包含日期在内的交易时间戳。Spark 程序按天成批读取数据,完成处理后,加载到目标 ClickHouse 表以供分析。

该程序需要 MySQLClickHouse 的 JDBC 驱动(可从各自的软件下载页面获取)。

还需要在系统中安装 Spark,可使用以下命令:

brew install apache-spark

在创建 MySQL 表之前,需要先安装并启动 MySQL 服务。相关步骤超出本书范围,可参阅 MySQL 文档。类似地,在创建 ClickHouse 数据库与表之前,需要本地安装或在云端开通 ClickHouse,具体如下。

首先创建 MySQL 数据库:

CREATE DATABASE sales_db;

连接到 sales_db 并创建用于存放月度交易数据的 MySQL 表:

USE sales_db;
CREATE TABLE sales_table (
    transaction_id INT AUTO_INCREMENT PRIMARY KEY,
    transaction_amount DECIMAL(10, 2) NOT NULL,
    product_id INT NOT NULL,
    location_id INT NOT NULL,
    transaction_ts TIMESTAMP NOT NULL
);

向 MySQL 载入示例数据:

LOAD DATA INFILE '/tmp/sales_table_january_2024.csv' INTO TABLE sales_table
FIELDS TERMINATED BY ',' ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 LINES
(transaction_id, transaction_amount, product_id, location_id, transaction_ts);

在 ClickHouse 上创建数据库:

CREATE DATABASE sales_db;

连接到 sales_db,创建用于分析的 ClickHouse 表:

USE sales_db;
CREATE TABLE sales_table (
    transaction_id UInt32,
    transaction_amount Decimal(10, 2),
    product_id UInt32,
    location_id UInt32,
    transaction_ts Timestamp
)
ENGINE = MergeTree()
ORDER BY transaction_id;

用于高频微批搬运数据的 Spark 作业示例(原文示例注释为“按日”,此处用于微批演示):

from pyspark.sql import SparkSession

# Create a SparkSession
spark = SparkSession.builder \
    .appName("MySQL to Clickhouse") \
    .getOrCreate()

# Setup the MySQL JDBC connection properties
mysql_url = "jdbc:mysql://localhost:3306/sales_db"
mysql_properties = {
    "user": "root",
    "password": "MyPassword",
    "driver": "com.mysql.cj.jdbc.Driver"
}

# Query to batch query the day's data in MySQL
query = "(SELECT transaction_id, transaction_amount, product_id, location_id, transaction_ts FROM sales_table where  transaction_ts> DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 10 SECOND)

# Read from MySQL daily data table into DataFrame
mysql_df = spark.read.jdbc(url=mysql_url, table=query, properties=mysql_properties)

# Setup ClickHouse JDBC connection properties
clickhouse_url = "jdbc:clickhouse://yywbylab29.ap-south-1.aws.clickhouse.cloud:8443/default"
clickhouse_properties = {
    "user": "default",
    "password": "MyPassword",
    "ssl": "true",
    "driver": "com.clickhouse.jdbc.ClickHouseDriver"
}

# Write daily data from MySQL to Clickhouse in a batch
mysql_df.write \
    .mode("append") \
    .jdbc(url=clickhouse_url, table="sales_table", properties=clickhouse_properties)

# Stop the SparkSession
spark.stop()

通过 cron 运行 Spark 作业:

crontab -e
* * * * * spark-submit --jars "/tmp/jdbc_dir/mysql-connector-j-9.0.0.jar,/tmp/jdbc_dir/clickhouse-jdbc-0.7.0.jar" spark_program.py

微批系统的技术选型(Technologies for micro-batching systems)

微批与批处理在技术上差异不大,主要区别在于批次频率。批处理系统通常一次处理大量数据;而微批系统单次处理的数据量较小。因此,多数用于批处理的技术也可用于微批,只是规模更小。

历史上,Hadoop MapReduce 常用于批处理:其作业能可靠消费与处理海量数据,并以 HDFS 作为临时与持久化存储。然而,MapReduce 启动与执行时延较高,往往不适合运行微批作业(启动时间可能超过批窗口,导致延迟)。

下面是针对微批在 MapReduceSpark 之间的技术选型指南:

表 5.1:技术选型指南

技术(Technology)启动时延(Startup latency)可从失败点重启(Restartability)作业复杂度(Job complexity)性能(Performance)数据规模(Data size)
MapReduce中到高中到低中到高
Spark低到中低到中

因此,Apache Spark 非常适合微批作业:其从失败点恢复的能力,使其成为更高性能且更可靠的批处理执行框架。

存储层面,对象存储正日益取代 HDFS,成为批处理的默认存储系统。鉴于 S3 提供的低时延访问,它非常适合作为微批作业的暂存区:数据可在 S3 中按批次累积,在批窗口结束时被快速读取处理。

Apache Airflow 也可用于微批作业的调度与编排,其对多系统的适配性以及 DAG(有向无环图) 建模能力使之成为强有力的选择。对于不需要复杂编排的简单微批,Linux Cron 是便捷的调度工具;企业级调度器如 Control-M 也常用于微批计划。

在云端,微批模式非常常见。依托无服务器(serverless)能力(如 Lambda),可在极低计算成本下弹性启动/停止微批作业;数据先累积在低成本对象存储上,按需启用弹性算力运行微批。与运行 Kafka 等实时系统相比,这种组合能以更低成本提供近实时处理能力。

真实世界示例(Real-world examples)

本节将讨论物流IoT 领域中若干微批处理的真实案例,展示相较于实时数据模式,微批如何以更低成本构建可用的解决方案。

物流中的车辆轨迹追踪(Vehicle tracking in logistics)

当下,在物流与交通等行业,车辆位置追踪已成为非常常见的用例。每个网约车应用都会提供实时位置共享以查看出租车的当前位置;在物流场景中,易腐或高价值货物在运输全程都需要被追踪,以确保安全且准时送达。这类追踪得益于车载设备发送的 GPS 数据

车辆行进时 GPS 位置持续变化;若将每一次微小位置变化都持续、实时发送至服务器并处理(以在地图中呈现),将导致带宽与算力消耗巨大。为优化资源,更高效的做法是:仅按足够小的周期性间隔发送 GPS 信号,使用户仍能看到车辆移动与当前定位,而无需对每一次细微变化进行“直播”。如此即可显著降低数据带宽计算成本。发送间隔只要足够小,既能表现车辆移动、也能显示当前位置——这正是网约车“位置共享”与物流“货物追踪”的目标。这两类用例并不需要逐秒级的精确位置。

可见,该问题既可用实时系统解决,也可用微批系统解决;两者都能满足“车辆追踪”的业务诉求,但实时系统在成本与资源上通常远高于微批。实时方案会把每一次位置变化立刻流式传输并立即处理;微批方案则将若干秒内的 GPS 变化合批后再发送与处理。微批引入的数秒延迟并不会影响地图应用的可用性,却能大幅降低成本

下图展示了在“实时位置追踪”用例中使用微批的方式(图 5.6):

image.png

图 5.6:物流领域的微批(Micro-batching in logistics)

物联网(IoT)

微批同样非常适合工业 IoT。现代工厂部署成百上千的传感器以监测车间设备的早期故障迹象。传感器持续发送数据,系统对其进行分析以识别潜在异常并定位可能失效的设备;运维人员据此进行检修或更换,从而将停机时间最小化,确保工厂持续运转,并帮助更好地规划维护计划、降低运营成本。

典型的工业架构中,工厂端有 IoT 网关持续接收各传感器数据(多为 MQTT 格式),再将数据发送至云端进行集中采集、存储与处理。云端的 ML 算法基于历史标注的故障数据进行训练,并对新数据进行检测以识别潜在故障。实现路径可以是:

  • 实时流式:传感器 → 网关 → 云端,全链路实时流式处理每个指标;或
  • 日终批处理:将数据收集一整天后再统一分析。

然而,这两种方式都并非最优:要实现全实时,需要昂贵的 Kafka 集群MQTT 连接器等基础设施,但现场维护人员并非实时响应,收益有限;而按天批处理又可能发现不够及时,错过最佳处置时机。

折中的最优解微批:将数据按数秒或数分钟收集后再处理。这样既能足够及时地向维护人员告警,便于其采取修复/更换动作,又能通过避免 Kafka 等实时消息系统将成本降到更低。

微批在此处的另一优势是:更高效地使用 ML 模型。多数模型更擅长批量打分而非单请求打分;利用微批可降低模型推断成本(如预测设备故障)。

下图展示了工厂如何以较低成本利用微批处理传感器数据(图 5.7):

image.png

图 5.7:IoT 应用中的微批(Micro-batching in IoT application)

结语(Conclusion)

本章介绍了微批系统的常见用例,并设计了一个在不引入实时系统高成本的情况下,将 MySQL 数据近实时搬运至 ClickHouse 的微批方案;我们看到该模式如何借助与批处理相同的多种技术落地实现。同时,我们学习了物流与 IoT 领域的微批案例,理解其高层架构,并看到微批如何在这些领域简化方案、降低成本

下一章将介绍 Lambda 架构,并回顾采用该架构的系统在各行业中的用例与实践。