在数据仓库建设中,ETL(Extract-Transform-Load)系统承担着数据管道的关键角色。一个成熟的企业级ETL架构需要兼顾效率、稳定性和可维护性。我们设计的这套系统基于Kettle工具实现,整体架构分为五个核心层级:
数据接入层:负责从校园卡系统、教务系统等12个业务系统抽取原始数据,采用JDBC直连和文件接口两种方式。特别针对高并发场景设计了"分时段轮询"机制——将每天8:00-22:00划分为6个时段,不同系统分配不同时段采集,避免集中抽取造成源系统压力。
缓冲存储层:使用MySQL建立ODS(Operational Data Store)中间库,所有原始数据在此层保留至少180天。这里采用了"时间分区+业务编码"的双重存储策略,例如校园卡消费流水按YYYYMMDD分区,同时以SCHOOL_CODE作为前缀区分不同校区的数据。
数据处理层:核心转换逻辑在此完成,包含:
MERGE INTO语法)数据服务层:加工后的数据装载到数据仓库(DWD/DWS层)和专题数据集(如学生行为分析)。针对不同数据特点采用差异化的加载策略:
watermark机制)调度监控层:通过Linux crontab实现作业调度,配套完善的监控体系:
ps -ef|grep kitchen.sh)tail -f crontab.log)SELECT MAX(biz_date) FROM etl_info)关键设计原则:所有表必须包含
etl_time(处理时间)和biz_date(业务日期)字段,确保数据可追溯。增量表必须配置etl_info元数据表记录水位线。
采用数据库资源库模式(非文件资源库),配置步骤如下:
sql复制CREATE DATABASE kettle_repo DEFAULT CHARSET utf8mb4;
GRANT ALL ON kettle_repo.* TO 'etl_user'@'%' IDENTIFIED BY 'Complex@Password123';
bash复制# 上传repo-config.xml到/root/.kettle
scp repo-config.xml root@prod-server:/root/.kettle
chmod 600 /root/.kettle/repo-config.xml
code复制/home/admin/sharetl
├── ETL.zip # 主程序包
├── logs # 日志目录
│ ├── ods_$(date +%Y%m%d).log
│ └── dwd_$(date +%Y%m%d).log
└── archive # 备份目录
└── $(date +%Y%m%d)
**首次全量加载作业(FIRST_JOB)**采用三阶段式设计:
锁文件检查阶段:
/home/admin/sharetl/etl_process.lock是否存在touch命令)并行执行阶段:
mermaid复制graph TD
A[ODS_FIRST_JOB] -->|校园卡消费| B(ODS_TRANS_DETAIL)
A -->|学生成绩| C(ODS_STU_SCORE)
D[BIG_FIRST_JOB] -->|增量表| E(DWD_STU_CONSUME)
D -->|全量表| F(DIM_STU_INFO)
收尾处理阶段:
**日常增量作业(AUTO_JOB)**关键设计:
etl_info表查询上次处理截止时间sql复制SELECT MAX(biz_date) FROM etl_info WHERE table_name='ods_trans_detail';
sql复制SELECT * FROM source_table
WHERE update_time > '${LAST_ETL_TIME}'
AND update_time <= '${CURRENT_TIME}'
高效数据加载采用组合方案:
表输出组件配置:
大数据量表使用"表输出+批量加载":
bash复制# MySQL批量加载命令
mysqlimport --local --compress --ignore-lines=1 \
--fields-terminated-by='|' db_name table_name.txt
增量控制实现方案:
在转换开始阶段获取业务日期:
javascript复制var max_date = sql("SELECT MAX(biz_date) FROM etl_info");
parent_job.setVariable("LAST_DATE", max_date);
在转换结束阶段更新水位线:
sql复制INSERT INTO etl_info
VALUES('ods_trans_detail', '${MAX_BIZ_DATE}', NOW())
ON DUPLICATE KEY UPDATE watermark='${MAX_BIZ_DATE}';
通过crontab设置分层调度:
bash复制# 每天23:30执行日常作业
30 23 * * * /home/admin/etl/run_auto_job.sh >> /var/log/etl.log 2>&1
# 每周六凌晨全量刷新
0 3 * * 6 /home/admin/etl/run_full_refresh.sh
调度脚本关键逻辑:
bash复制#!/bin/bash
LOCK_FILE="/home/admin/etl/.lock"
if [ -f $LOCK_FILE ]; then
echo "$(date) - Error: Lock file exists" | mail -s "ETL Alert" admin@example.com
exit 1
fi
touch $LOCK_FILE
/opt/kettle/kitchen.sh -file=/jobs/main.kjb
if [ $? -ne 0 ]; then
# 错误处理逻辑
fi
rm -f $LOCK_FILE
基础监控项:
进程存活检查:
bash复制pgrep -f 'kitchen.sh' || alert "Kettle process died"
作业耗时监控:
sql复制SELECT job_name, TIMESTAMPDIFF(MINUTE, start_time, end_time)
FROM etl_job_log
WHERE DATE(start_time)=CURDATE();
数据质量检查:
记录数波动检测(同比/环比):
sql复制SELECT table_name, COUNT(*)
FROM dwd_stu_consume
WHERE biz_date='${DATE}'
HAVING COUNT(*) < 0.7*(
SELECT AVG(record_count)
FROM etl_stats
WHERE table_name='dwd_stu_consume'
AND biz_date BETWEEN '${DATE-7}' AND '${DATE-1}'
)
空值率检查:
sql复制SELECT
SUM(CASE WHEN student_id IS NULL THEN 1 ELSE 0 END)/COUNT(*) AS null_rate
FROM ods_stu_score
WHERE biz_date='${DATE}'
索引策略:
trans_id)student_id + biz_date)gender)分区方案:
sql复制-- 事实表按日期范围分区
ALTER TABLE dwd_stu_consume PARTITION BY RANGE (TO_DAYS(biz_date)) (
PARTITION p_202301 VALUES LESS THAN (TO_DAYS('2023-02-01')),
PARTITION p_202302 VALUES LESS THAN (TO_DAYS('2023-03-01'))
);
-- 维表按哈希分区
ALTER TABLE dim_stu_info PARTITION BY HASH(school_id) PARTITIONS 8;
JVM参数调整:
ini复制# 在spoon.sh中修改
OPT="-Xmx8G -Xms8G -XX:MaxMetaspaceSize=1G -Djava.awt.headless=true"
组件级优化:
排序合并连接:
表输入组件:
/*+ INDEX(col) */查询提示文件输出:
可重试错误:
不可重试错误:
etl_error_log表错误邮件内容:
code复制主题:[ETL-ERROR] ${JOB_NAME} failed at ${TIMESTAMP}
详情:
- 作业: ${JOB_PATH}
- 错误: ${ERROR_MSG}
- 堆栈: ${STACK_TRACE}
处理建议:
1. 检查锁文件: ls -l /home/admin/etl/.lock
2. 查看日志: tail -n 100 /var/log/etl.log
3. 确认数据库连接: mysql -u${DB_USER} -p${DB_PASS}
成功通知:
sql复制INSERT INTO etl_notification
SELECT
'${JOB_NAME}' AS job_name,
COUNT(*) AS record_count,
MIN(biz_date) AS start_date,
MAX(biz_date) AS end_date,
NOW() AS complete_time
FROM dwd_stu_consume
WHERE etl_date='${CURRENT_DATE}';
建议增加以下元数据表:
sql复制CREATE TABLE etl_metadata (
table_name VARCHAR(50) PRIMARY KEY,
source_system VARCHAR(30),
refresh_frequency ENUM('daily','hourly','weekly'),
retention_days INT,
owner_email VARCHAR(100)
);
CREATE TABLE etl_column_lineage (
target_table VARCHAR(50),
target_column VARCHAR(50),
source_table VARCHAR(50),
source_column VARCHAR(50),
transform_rule TEXT
);
在转换中使用Set Variables组件记录血缘信息:
javascript复制// 在转换开始时记录
var lineage = {
"transformation": "ods_to_dwd_stu",
"source": "ods.stu_info",
"target": "dwd.stu_dim",
"mapping": [
{"from": "student_no", "to": "stu_id"},
{"from": "CONCAT(first_name, last_name)", "to": "full_name"}
]
};
parent_job.setVariable("LINEAGE", JSON.stringify(lineage));
这套ETL体系在某高校数据平台稳定运行三年,日均处理数据量超过2000万条,支持了学生行为分析、教学质量评估等12个主题域的数据服务。核心经验在于:合理的分层设计、完善的监控机制、规范化的运维流程。对于需要处理历史遗留系统的场景,建议增加数据比对模块(如CRC32校验),确保迁移数据的完整性。