3. Flink DataStream API 核心算子实战:Source、Transformation、Sink
在贷后预警系统中,数据从产生到最终落库或触发告警,需要经过完整的 ETL 管道。本章聚焦 Flink DataStream API 的三大核心环节:Source(数据源)、Transformation(转换) 与 Sink(数据输出)。我们将以贷后预警场景为例,实战演练 Kafka 实时流接入、MySQL CDC 变更捕获、核心算子链式处理,以及结果写入 Kafka 和 MySQL JDBC 的完整流程。
3.1 数据源(Source)实战
贷后预警系统需要同时处理实时事件流与数据库变更流。Flink 提供了丰富的 Source 连接器,本节聚焦最常用的两种:Kafka Source 与 MySQL CDC Source。
3.1.1 Kafka Source:实时事件流接入
Kafka 是贷后预警系统接收还款行为、逾期状态变更等实时事件的核心通道。使用 Flink Kafka Connector 可以精确一次(Exactly-Once)地消费 Kafka 主题。
// 构建 Kafka Source
DataStream<String> kafkaStream = env.fromSource(
KafkaSource.<String>builder()
.setBootstrapServers("localhost:9092")
.setTopics("loan-repayment-events")
.setGroupId("flink-warning-group")
.setStartingOffsets(OffsetsInitializer.latest())
.setValueOnlyDeserializer(new SimpleStringSchema())
.build(),
WatermarkStrategy.noWatermarks(),
"Kafka Source"
);
| 配置项 | 说明 | 贷后预警推荐值 |
|---|---|---|
| setBootstrapServers | Kafka 集群地址 | 生产环境配置多个 Broker 地址 |
| setTopics | 订阅的主题名 | 按事件类型分主题(如还款、逾期、调额) |
| setGroupId | 消费者组 ID | 每个预警作业使用独立 Group ID |
| setStartingOffsets | 起始消费位置 | 首次启动用 earliest,重启用 latest |
3.1.2 MySQL CDC Source:数据库变更捕获
贷后预警需要实时感知数据库中的客户信息、合同状态、还款计划等变更。Flink CDC(Change Data Capture)基于 Debezium 引擎,能够将 MySQL Binlog 实时解析为变更事件流。
// 构建 MySQL CDC Source
DataStream<RowData> cdcStream = env.fromSource(
MySQLSource.<RowData>builder()
.hostname("localhost")
.port(3306)
.databaseList("loan_db")
.tableList("loan_db.contract_info", "loan_db.repayment_plan")
.username("cdc_user")
.password("cdc_pass")
.deserializer(new JsonDebeziumDeserializationSchema())
.startupOptions(StartupOptions.initial())
.build(),
WatermarkStrategy.noWatermarks(),
"MySQL CDC Source"
);
startupOptions.initial() 会先做一次全量快照,再无缝切换到增量 Binlog 监听,非常适合贷后预警系统初始化时加载存量数据。
3.2 转换(Transformation)核心算子
Flink DataStream API 提供了丰富的转换算子,用于对数据流进行过滤、映射、分组等操作。贷后预警场景中最常用的三个算子是 map、filter 和 keyBy。
3.2.1 map:数据格式转换与字段提取
map 算子对数据流中的每个元素执行一对一转换。在贷后预警中,常用于将 JSON 字符串解析为 Java POJO,或提取关键字段。
// 将 JSON 字符串解析为 RepaymentEvent 对象
DataStream<RepaymentEvent> eventStream = kafkaStream
.map(json -> JsonUtil.parseObject(json, RepaymentEvent.class));
3.2.2 filter:条件过滤与数据清洗
filter 算子根据布尔条件保留或丢弃元素。贷后预警中,常用于过滤掉无效数据或只保留特定类型的事件。
// 只保留逾期天数大于 0 的逾期事件
DataStream<OverdueEvent> overdueStream = eventStream
.filter(event -> event.getOverdueDays() > 0);
3.2.3 keyBy:按业务键分组
keyBy 算子根据指定的 Key 对数据流进行逻辑分区,后续的窗口、状态操作都基于分区执行。贷后预警中,通常按 合同号 或 客户 ID 进行分组。
// 按合同号分组,用于后续计算每个合同的累计逾期天数
KeyedStream<OverdueEvent, String> keyedStream = overdueStream
.keyBy(OverdueEvent::getContractId);
keyBy 后的数据分布取决于 Key 的哈希值。如果 Key 分布不均匀(如热点合同),会导致数据倾斜。建议在 Key 中拼接随机后缀或使用 rebalance() 算子进行负载均衡。
3.3 数据输出(Sink)实战
贷后预警结果需要同时写入多个目标系统:实时告警消息发送到 Kafka 主题,持久化预警记录写入 MySQL 数据库。Flink 提供了 Kafka Sink 和 JDBC Sink 两种主流输出方式。
3.3.1 Kafka Sink:实时告警消息输出
将预警结果发送到 Kafka 告警主题,下游的告警服务或通知系统可以实时消费。
// 构建 Kafka Sink
warningStream.sinkTo(
KafkaSink.<WarningResult>builder()
.setBootstrapServers("localhost:9092")
.setRecordSerializer(KafkaRecordSerializationSchema.builder()
.setTopic("loan-warning-results")
.setValueSerializationSchema(new JsonSerializationSchema<>())
.build()
)
.setDeliveryGuarantee(DeliveryGuarantee.AT_LEAST_ONCE)
.build()
);
3.3.2 MySQL JDBC Sink:预警记录持久化
使用 Flink JDBC Connector 将预警结果写入 MySQL 表,便于后续查询、报表和审计。
// 构建 JDBC Sink
warningStream.addSink(
JdbcSink.sink(
"INSERT INTO warning_record(contract_id, customer_id, warning_type, warning_level, create_time) VALUES (?, ?, ?, ?, ?)",
(statement, record) -> {
statement.setString(1, record.getContractId());
statement.setString(2, record.getCustomerId());
statement.setString(3, record.getWarningType());
statement.setString(4, record.getWarningLevel());
statement.setTimestamp(5, record.getCreateTime());
},
JdbcExecutionOptions.builder()
.withBatchSize(100)
.withBatchIntervalMs(200)
.build(),
new JdbcConnectionOptions.JdbcConnectionOptionsBuilder()
.withUrl("jdbc:mysql://localhost:3306/warning_db")
.withUsername("flink_user")
.withPassword("flink_pass")
.build()
)
);
| Sink 类型 | 适用场景 | 一致性保证 | 贷后预警用途 |
|---|---|---|---|
| Kafka Sink | 实时消息推送 | 至少一次 / 精确一次 | 告警通知、下游系统联动 |
| MySQL JDBC Sink | 持久化存储 | 至少一次(幂等写入) | 预警记录归档、报表查询 |
3.4 贷后预警管道完整链路
将上述 Source、Transformation、Sink 串联起来,形成一条完整的贷后预警实时处理管道。下面的 SVG 图展示了数据从 Kafka 和 MySQL CDC 流入,经过核心算子处理,最终输出到 Kafka 和 MySQL 的全流程。
3.5 完整代码示例:贷后预警管道
以下代码将上述所有环节整合为一个完整的 Flink 作业,实现从 Kafka 消费还款事件、过滤逾期记录、按合同分组、评估预警规则,最终将结果同时写入 Kafka 和 MySQL。
public class LoanWarningPipeline {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(4);
// 1. Kafka Source:消费还款事件
DataStream<String> kafkaSource = env.fromSource(
KafkaSource.<String>builder()
.setBootstrapServers("localhost:9092")
.setTopics("loan-repayment-events")
.setGroupId("flink-warning-group")
.setStartingOffsets(OffsetsInitializer.latest())
.setValueOnlyDeserializer(new SimpleStringSchema())
.build(),
WatermarkStrategy.noWatermarks(),
"Kafka Source"
);
// 2. map:JSON 解析为 POJO
DataStream<RepaymentEvent> eventStream = kafkaSource
.map(json -> JsonUtil.parseObject(json, RepaymentEvent.class));
// 3. filter:只保留逾期事件
DataStream<OverdueEvent> overdueStream = eventStream
.filter(event -> event.getOverdueDays() > 0);
// 4. keyBy:按合同号分组
KeyedStream<OverdueEvent, String> keyedStream = overdueStream
.keyBy(OverdueEvent::getContractId);
// 5. process:预警规则评估(状态 + 定时器)
DataStream<WarningResult> warningStream = keyedStream
.process(new WarningRuleProcessFunction());
// 6. Kafka Sink:发送告警消息
warningStream.sinkTo(
KafkaSink.<WarningResult>builder()
.setBootstrapServers("localhost:9092")
.setRecordSerializer(KafkaRecordSerializationSchema.builder()
.setTopic("loan-warning-results")
.setValueSerializationSchema(new JsonSerializationSchema<>())
.build()
)
.setDeliveryGuarantee(DeliveryGuarantee.AT_LEAST_ONCE)
.build()
);
// 7. MySQL JDBC Sink:持久化预警记录
warningStream.addSink(
JdbcSink.sink(
"INSERT INTO warning_record(contract_id, customer_id, warning_type, warning_level, create_time) VALUES (?, ?, ?, ?, ?)",
(statement, record) -> {
statement.setString(1, record.getContractId());
statement.setString(2, record.getCustomerId());
statement.setString(3, record.getWarningType());
statement.setString(4, record.getWarningLevel());
statement.setTimestamp(5, record.getCreateTime());
},
JdbcExecutionOptions.builder()
.withBatchSize(100)
.withBatchIntervalMs(200)
.build(),
new JdbcConnectionOptions.JdbcConnectionOptionsBuilder()
.withUrl("jdbc:mysql://localhost:3306/warning_db")
.withUsername("flink_user")
.withPassword("flink_pass")
.build()
)
);
env.execute("Loan Warning Pipeline");
}
}
withBatchSize 和 withBatchIntervalMs 控制写入频率,避免频繁数据库连接开销。
3.6 本章小结
通过本章实战,你已掌握 Flink DataStream API 中 Source、Transformation、Sink 三大核心环节的选型与编码方法。在贷后预警场景中:
- Source 层:Kafka Source 接入实时事件流,MySQL CDC Source 捕获数据库变更,两者互补覆盖全量数据源。
- Transformation 层:map 做格式转换,filter 做条件过滤,keyBy 做业务分组,process 做复杂状态计算。
- Sink 层:Kafka Sink 实现实时告警推送,MySQL JDBC Sink 实现持久化存储,双路输出保障数据不丢失。