引言(Introduction)
在本章中,我们将深入理解批处理(batch)模式的细节,让读者熟悉如何用批处理模式构建解决方案。内容将覆盖批处理模式能够解决的用例;我们还会讨论如何使用开源技术设计批处理系统,并通过示例应用与代码片段进行演示。此外,还会介绍批处理模式在真实场景中的应用实例。
结构(Structure)
本章涵盖以下主题:
- 批处理系统的用例
- 批处理系统的设计
- 批处理系统的技术选型
- 真实案例
目标(Objectives)
在本章结束时,你将对批处理模式有深入理解;你也能够基于批处理模式设计并搭建数据管道,并编写代码落地该设计。你将了解应选择的技术栈,以及如何将各组件“缝合”起来构建端到端的管道。最后,你还将理解哪些真实业务场景适合采用该模式,并看到来自银行与零售行业的示例。
批处理系统的用例(Use cases for batch systems)
批处理模式可用于多种场景,从构建 ETL 管道到数据归档策略。以下是适合采用批处理模式的一些用例。
1) 面向数据仓库的 ETL 管道
数据仓库用于承载企业最关键的分析与报表需求,通常按日、周、月运行计划报表。数据仓库中的数据来源于组织内的多种系统。将这些来源的数据经过转换后装载进数据仓库的过程称为 ETL(Extract, Transform, Load) 。
企业中的交易源系统全年无休、持续地产生数据。然而,报表并不总是需要实时:多数用例的 SLA 以“天”为单位,而不是毫秒或秒。因此,无需把源系统的数据实时搬入数据仓库。多数数据仓库的数据移动需求天然是批式的:只需保证在计划报表启动前数据可查询即可。ETL 任务可以按固定周期调度,将源系统在一段时间内产生的数据成批抽取、转换,再装载到数据仓库中。
当然,也可以用实时数据工程模式达成同样目标。但在数据仓库的 ETL 场景中,实现实时系统的成本通常显著高于批处理。例如,实时系统需要 CDC 技术即时捕获交易库变更,并通过如 Kafka 的消息总线传输到数据仓库。同时,ETL 中的转换通常较为复杂,实时执行这些复杂转换不仅困难,还需要在计算资源上投入大量基础设施。
下图展示了数据仓库 ETL 系统的设计示意(Figure 3.1):
图 3.1:数据仓库 ETL 的架构
2) 数据归档管道(Data archival pipelines)
数据归档是将数据复制到冷、低成本存储以做长期保存的过程。通常通过从活动系统中定期运行“抽取+复制”作业,写入廉价的归档存储。由于归档是周期性执行、将一定量的数据成批地进行处理,因此非常适合批处理模式。
归档的常见用法之一,是当某些数据不再被频繁用于 BI 时,将数据仓库中最老的数据回滚/迁移到冷对象存储(如 S3 Glacier)。例如,可以用数据库的原生导出工具,将最老的日期分区导出到 S3 Glacier。
举例:若需要每月将 Snowflake 中某表的数据归档到 S3 Glacier,可按如下步骤:
创建指向 S3 的 stage:
CREATE STAGE my_s3_stage URL='s3://unload/files/' STORAGE_INTEGRATION = s3_int;
创建在每月最后一天运行、将表数据复制到 S3 的任务:
CREATE TASK monthly_task
SCHEDULE = 'USING CRON 0 0 31 * *'
AS
COPY INTO @my_s3_stage/t1 FROM (SELECT * FROM t1);
上述任务会每月将该表的所有记录成批复制到 S3。
3) 为 BI 预计算聚合(Building precomputed aggregates for BI)
BI 工作负载通常需要对一段时期内的数据进行分析,且常常依赖各种聚合。当分析跨度很长时,参与聚合的数据量会非常大;若每次查询都现算聚合,计算代价会很高。为此,许多分析系统会创建预计算聚合来直接服务查询,避免每次都重新计算。
例如,一个常见 BI 问题是:按城市与产品品类统计销售额。直接查询:
SELECT CITY, PRODUCT, SUM(SALES)
FROM SALES_TABLE
GROUP BY CITY, PRODUCT;
这会在每次查询时聚合一次,造成计算浪费。更好的方式是创建物化视图来保存预聚合结果。Oracle 示例:
CREATE MATERIALIZED VIEW sales_materialized_view ON DEMAND AS
SELECT CITY, PRODUCT, SUM(SALES)
FROM SALES_TABLE
GROUP BY CITY, PRODUCT;
主表 SALES_TABLE 可以持续更新,但物化视图可在每天结束时批量刷新一次(将当天的变更成批合入)。对主表的每次更新都去同步更新物化视图通常在计算上并不高效。
在 Oracle 中,可用如下语句刷新物化视图:
EXEC DBMS_MVIEW.refresh('SALES_MATERIALIZED_VIEW');
4) 训练机器学习模型(Training ML models)
模型训练非常占用内存。当训练数据很大时,无法一次性将所有数据放入内存。为此,训练 API 通常支持批量训练:每处理完一批训练数据,就更新一次模型权重,直至消费完全部训练数据。比如,使用 SGD(随机梯度下降) 在处理每个 batch 后计算损失并更新权重。诸如 PyTorch 等训练框架允许在数据加载器中指定 batch size。
设计批处理与数据摄取系统(Designing batch processing and ingestion system)
本节我们将构建一个批处理与摄取系统:从事务型 MySQL 数据库读取数据,使用 Apache Spark 以批的方式处理,然后将数据加载进 ClickHouse 数据库。
Spark 作业通过 cron 定时任务每天运行一次,如下图所示:
图 3.2:批量数据摄取(Batch data ingestion)
系统中包含一张名为 sales_table 的 MySQL 表,用于存放当月销售数据。表中有 transaction_date 列(记录交易日期)。Spark 程序每次按天成批读取数据,进行处理后,加载到目标 ClickHouse 表中以供分析。
该程序需要为 MySQL 和 ClickHouse 都准备 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_date DATE 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_date);
在 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_date Date
)
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_date FROM sales_table where dayofmonth(transaction_date)=dayofMONTH(CURDATE()) limit 5) AS tmp"
# 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
0 22 * * * 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 batch systems)
批处理系统通常一次处理较大量的数据:先在一个批次窗口内收集数据,然后集中处理。因此,常用技术需要能在不出现性能瓶颈的情况下处理大规模数据,并能随数据量的“横向扩展”而扩展。
历史上,Hadoop MapReduce 是常见的批处理方式:其作业可以可靠地消费并处理海量数据,临时与持久化数据通常依托 HDFS。但在构建现代系统时,工程师们发现 MapReduce 的资源利用效率不够理想,逐步转向 Apache Spark 来执行批处理作业。Spark 具备从失败点恢复的能力,整体性能与可靠性更佳。存储层面,对象存储 正日益取代 HDFS,成为批处理的默认存储系统。
批处理作业需要按批次频率进行调度与编排。Apache Airflow 以其对多种系统的良好适配能力,以及将工作流建模为 DAG(有向无环图) 的能力,成为强有力的技术选项。对于不需要复杂编排的简单场景,Linux 的 Cron 也是安排批处理作业的便捷工具。
真实世界示例(Real-world examples)
下面以银行业与**零售媒体网络(RMN)**两个领域的实例,说明批处理模式在实际中的应用。这些示例有助于你把本章讨论的模式迁移到特定领域的解决方案中,也帮助把“理论”与行业用例的“实践”衔接起来。
银行业中的批处理(Batch processing in banking)
由于金融交易的敏感性,银行业受严格监管。所有银行都必须在**日终(end-of-day)**向监管机构提交报告以确保合规。日终报告需要包含当天发生的各类交易相关数据点,例如:现金流量表、市场与外汇头寸敞口、可疑欺诈报告、外汇交易报告等。
银行系统十分复杂,往往整合多种技术与子系统。要生成上述报告,必须在日终前完成来自多个系统的数据整合,这就需要一个批处理系统在白天不断从各系统汇聚数据至中心分析系统,以便按时产出日终报表。典型的数据来源包括:支付系统、反欺诈系统、核心银行系统以及外汇系统。
下图(图 3.3)展示了如何使用批处理模式将银行系统的各组件整合起来以支持日终报表:
图 3.3:支持日终报表的批处理系统
零售媒体网络中的批处理(Batch processing in retail media networks)
零售媒体网络(Retail Media Networks, RMN)是由零售商在其自有数字资产(如 App 或网站)上运营的广告平台。品牌可以在 RMN 上投放广告,直接在购买决策点触达消费者,从而提高成交概率。
批处理在 RMN 中的用法如下:广告主在 RMN 上发布广告,用户使用零售 App 时看到这些广告。平台会追踪用户在 App 上的行为,如页面浏览、购物车事件、广告互动、转化率等。这些数据需要回传给广告主,帮助其更精准地定向投放并提升转化率。
不过,广告主并不需要逐条的个体交互记录,而是需要一定周期内的聚合信息。因此,RMN 通常会将用户交互在整天范围内进行批量汇总,并在日终将聚合结果发送给广告主,以体现其广告活动的效果。
下图展示了一个采用批处理模式收集并整合用户行为以供分析的架构:
图 3.4:RMN 中的批处理模式
结语(Conclusion)
本章首先介绍了批处理系统的一些常见用例,随后设计了一个将事务系统(如 MySQL)中的数据按周期搬运到分析系统(如 ClickHouse)的批处理方案,并用 Apache Spark 与 JDBC 展示了代码层面的实现。我们还讨论了银行业与零售媒体领域的实际批处理案例,理解了其高层次架构。此外,我们回顾了批处理系统常用的技术栈及其应用方式。
在下一章中,我们将转向实时系统的概念与设计,回顾其实用场景,并给出多个领域的实时系统真实案例。