基础及入门(3)-批数据处理机制

一、源端数据读取格式与机制

1、Connector 抽象层

Flink SQL 通过 Connector 机制屏蔽底层存储差异。你的源表配置的是 'connector' = 'iceberg',Flink 在运行时会:
  1. 加载 FlinkIcebergTableSource 实现类
  2. 通过 REST Catalog(http://192.168.5.143:9007)获取表的 schema、partition 信息、snapshot 元数据
  3. 根据 Iceberg 的 metadata 定位 S3 上的实际数据文件路径(Parquet/ORC/Avro 格式)
  4. 调用 S3FileIO 从 MinIO(http://192.168.3.62:9000)读取数据文件
Flink TaskManager
     │
     ▼
Iceberg Source Connector
     │── REST Catalog → 获取 snapshot/manifest
     │── S3FileIO     → 读取 Parquet 数据文件
     ▼
内部行格式:RowData(二进制紧凑格式)

2、内部数据格式:RowData

数据进入 Flink 引擎后, 不是 String/Map,而是 RowData 对象,每一列对应一个强类型的槽位(slot):
Flink SQL 类型
内部存储
STRING
StringData(UTF-8 字节数组)
INT
原始 int
BIGINT
原始 long
TIMESTAMP_LTZ(3)
TimestampData(epoch毫秒 + 纳秒部分)
DECIMAL
DecimalData
这意味着 类型必须在进入引擎前就已确定,Iceberg schema 与 Flink DDL 的类型必须兼容,否则在 Source 阶段就会报错。

二、计算引擎对数据的处理过程

1、算子拆解

[Source: IcebergSource]
       │  RowData 流
       ▼
[Calc(投影 + 表达式计算)]
       │  转换后的 RowData 流
       ▼
[Sink: DorisSink]
Calc 算子是核心,它完成所有列的映射和表达式求值,逐行处理, 无 Shuffle、无聚合

2、内置函数(Built-in Functions)

你脚本里用到的都是 Flink SQL 内置函数,运行在 Calc 算子内部,JVM 本地执行,无网络开销:
SQL 表达式
Flink 内置函数
说明
CAST(x AS TIMESTAMP(3))
CastRule
类型转换,编译期确定转换逻辑
CAST(x AS BIGINT)
CastRule
String → long
CAST(x AS DATE)
CastRule
String → 天数整数
CAST(x AS TINYINT)
CastRule
String → byte
NULLIF(x, '-')
NullIfFunction
等价于 CASE WHEN x='-' THEN NULL ELSE x END
COALESCE(a, b)
CoalesceFunction
返回第一个非 NULL 值
重点NULLIF(S.HOSPITAL_DAY, '-') 先把 '-' 变成 NULL,再 CAST 为 BIGINT。如果不做这一步,直接 CAST '-' 为 BIGINT 会抛出运行时异常。这是你这个脚本里的一个关键防御性写法。

3、批处理执行模型

批模式下( execution.runtime-mode = BATCH):
  • 数据不是流式逐条处理,而是按批次(buffer) 拉取
  • Iceberg Source 会并行读取多个 data file,每个 split 对应一个 SubTask
  • 无 Watermark、无 Checkpoint,失败需整体重跑

三、数据写入 Doris 的机制

1、DorisSink 的写入流程

Flink Doris Connector 的写入链路:
Calc 算子输出 RowData
       │
       ▼
DorisRowDataSerializer(RowData → JSON bytes)
       │  你配置了 'sink.properties.format' = 'json'
       ▼
DorisBatchWriter 内存缓冲区
  ├── 满 10000 行  → flush(sink.buffer-flush.max-rows)
  └── 超 5000ms   → flush(sink.buffer-flush.interval)
       │
       ▼
HTTP Stream Load 请求 → FE(192.168.2.111:30031)
       │
       ▼
Doris BE 接收数据

2、Doris 收到数据后的处理

因为你配置了 'sink.properties.partial_update' = 'true',Doris 会执行部分列更新:
Stream Load JSON → Doris BE
    │
    ├── 解析 JSON,按列名匹配
    ├── 查找目标表已有行(通过 Key 列)
    │       ├── 存在 → 只更新本次写入的列,其他列保持不变
    │       └── 不存在 → 插入新行
    └── 写入列存文件(Segment)

这里需要注意:Doris 的 partial_update 依赖目标表是 Unique Key 模型,Key 列必须包含在每次写入数据中(你的脚本中 inp_no 和 inst_code 应是 Key 列)。

四、为什么必须显式类型转换?何时发生?

1、根本原因:类型系统不互通

层次
类型系统
Iceberg 表
Iceberg 类型(string, int, timestamptz...)
Flink 内部
Flink SQL 类型(STRING, INT, TIMESTAMP_LTZ...)
Doris 表
Doris 类型(VARCHAR, BIGINT, DATETIME, TINYINT...)
Flink DDL 声明了源表和目标表的 schema, 但并不保证列与列之间类型天然匹配。例如:
  • 源表 HOSPITAL_DAY 是 STRING(存的是 "5" 或 "-")
  • 目标表 actual_inp_day_count 是 BIGINT
Flink 不会自动隐式转换 STRING → BIGINT(不像某些数据库会),必须你 显式声明意图

2、类型转换发生的时间点

编译期(Planner 阶段):
  └── 验证 CAST 是否合法(STRING→BIGINT 是否支持)
  └── 生成 CodeGen 代码(将 CAST 编译成具体的 Java 方法调用)

运行期(TaskManager 执行阶段):
  └── 逐行执行 CAST,对每条 RowData 的对应列做转换
  └── 若转换失败(如 CAST('abc' AS BIGINT)),抛出运行时异常

3、常见转换失败场景(便于你排查问题)

错误
现象原因
解法
NumberFormatException
STRING 列有非数字字符直接 CAST 为数值类型
先 NULLIF(col, '-') 再 CAST
DateTimeParseException
时间字符串格式不符合 yyyy-MM-dd HH:mm:ss
用 TO_TIMESTAMP(col, 'format') 指定格式
NullPointerException
NOT NULL 列写入了 NULL
用 COALESCE 提供默认值
Doris Stream Load 报列不匹配
JSON 字段名大小写与 Doris 列名不一致
检查列名,Doris 列名默认小写

五、完整数据流转总结图

MinIO(S3)Parquet 文件
       │
       │ S3FileIO 读取
       ▼
Iceberg Source(SubTask × N 并行)
       │ RowData(强类型,二进制)
       ▼
Calc 算子(逐行处理)
  ├── NULLIF:脏数据转 NULL
  ├── COALESCE:NULL 填默认值
  ├── CAST:类型强制转换
  └── 列重命名映射
       │ 转换后 RowData
       ▼
DorisBatchWriter(内存缓冲)
  └── 缓冲满 / 定时 flush
       │ JSON 格式 HTTP POST
       ▼
Doris FE → BE
  └── partial_update Unique Key Merge
       │
       ▼
Doris 列存(zoestd_hdc.hdc_case_homepage_inp)

六、快速排查错误的思路

掌握上述机制后,遇到报错可按这个顺序定位:
  1. Source 阶段报错 → Iceberg schema 与 Flink DDL 类型不匹配,或 S3/REST Catalog 连接问题
  2. Calc 阶段报错(运行时) → CAST 失败,检查源数据脏值,补 NULLIF 或 TRY_CAST
  3. Sink 阶段报错 → JSON 序列化问题、Doris Stream Load 返回错误码、partial_update Key 列缺失
  4. 数据写入但结果不对 → 检查 NULLIF 的过滤值是否覆盖所有脏值格式(比如除了 '-' 还有 '' 空串)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ben@dw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值