文件批次为什么要有完成标记
文件类数据交换的触发方式很朴素:上游把文件放到约定目录,下游定时扫描目录、发现新文件就启动批次。问题在于「目录里出现了文件」不等于「文件已经写完」——大文件写到一半就会出现在目录里,此时读取只能拿到半截内容。所以可靠的文件触发必须依赖一个完成信号:要么上游先写临时文件再原子重命名到正式目录,要么整批文件写完后放置 _SUCCESS 或清单文件作为批次结束标记。
基本信息
| 适用平台 | Linux 服务器、HDFS 与 S3/OSS 对象存储、Windows 共享盘 |
|---|---|
| 触发对象 | 落地目录中的新文件或批次标记 |
| 完整性判定 | 原子重命名、_SUCCESS 标记、清单文件 |
| 实现方式 | 调度轮询 + 已处理清单,或对象存储事件通知 |
典型使用场景
- 外部供应商按天投递对账文件:约定凌晨放到指定目录,到达即触发入库
- Spark/Hive 批量作业产出后交付下游:用 _SUCCESS 表示输出目录已经写完
- 对象存储文件到达触发:S3 事件通知或 OSS 事件规则直接拉起处理任务
- 批量 CSV/Excel 手工导入数仓:先校验文件完整再解析,避免半包入库
- 跨网闸或跨安全域的文件摆渡:必须防半包、防重复消费
轮询扫描与已处理清单
轮询扫描与已处理清单
LAND=/data/landing/orders # 正式目录,上游原子重命名后文件才会出现在这里
DONE=/data/landing/.processed.list # 已处理清单,逐行记录文件名
cd $LAND || exit 1
for f in *.csv; do # 只扫描正式目录里的 csv
grep -qx "$f" $DONE && continue # 已在清单里就跳过,保证不重复消费
[ -f "_SUCCESS" ] || { echo "批次未完成,等下一轮"; break; } # 批次标记不存在则等待
/opt/etl/load_orders.sh "$f" || exit 1 # 处理失败直接退出,下一轮重试
echo "$f" >> $DONE # 成功后立刻登记,失败不登记
done完成标记方案对比
| 方式 | 原理 | 适用 | 风险点 |
|---|---|---|---|
| 临时目录 + 重命名 | 先写 _tmp 再 rename 到正式目录 | 单文件、同文件系统 | 跨文件系统 rename 不再原子 |
| _SUCCESS 标记 | 整批写完后放置空标记文件 | Spark/Hive 输出目录 | 标记与数据非同一事务 |
| 清单文件 | 批次末尾生成 manifest 列出文件 | 多文件大批次 | 清单写失败会整批漏触发 |
| 事件通知 | 对象存储文件创建事件推送 | 云对象存储 | 海量小文件易触发限流 |
Spark 写文件系统或 Hive 表完成后会在输出目录生成 _SUCCESS 文件,调度可以直接以它是否存在判断批次结束;历史目录里通常也留有 _SUCCESS,重跑前要先清理,否则会立刻被判定为完成。
FTP 与 SMB 共享目录上不能假设 rename 原子:Windows 共享盘与 Linux 之间的跨协议移动可能退化为「复制 + 删除」,此时应改用与数据文件同名的 .done 标记文件来宣告单个文件写完。