☰
Hive小时任务调度:Shell封装实现精准时间窗口与容错执行
2026/10/13 11:34:47 网站建设 项目流程

1. 项目概述:为什么“按小时循环执行Hive脚本”不是简单加个crontab就完事?

在某数据中台团队的实际运维场景里,我接手过一个典型的准实时ETL任务:上游业务系统每小时生成一批原始日志文件,存入HDFS指定路径;下游BI看板需要在每小时整点后15分钟内,完成对这批数据的清洗、聚合与维度关联,并将结果写入一张汇总宽表供即席查询。表面看,这不就是写个Hive SQL脚本,再用crontab每小时跑一次?但实测三天后,监控告警就炸了——凌晨2点那批数据没进数,凌晨4点的又卡住,导致上午9点的日报直接缺了一块。后来翻日志才发现,问题根本不在Hive本身,而在于时间窗口错位、任务依赖未校验、失败无兜底、资源争抢无隔离这四个被绝大多数人忽略的底层陷阱。

“shell按小时循环执行hive脚本”这个标题,本质不是教你怎么敲0 * * * * hive -f /path/to/script.hql,而是要解决一个带状态、有时序、需容错、有边界的数据调度问题。它横跨Shell工程能力、Hive执行机制、YARN资源调度、HDFS元数据一致性、以及业务语义的时间定义(是“处理上一小时数据”还是“处理当前小时已落盘数据”?)。关键词里的“按小时”,不是指cron的触发频率,而是指数据处理的时间粒度、任务调度的窗口边界、以及失败重试的回溯深度。适合三类人细读:刚从离线批处理转战准实时场景的ETL工程师;习惯用crontab但总被半夜告警叫醒的DBA;还有正在设计轻量级调度平台、想避开Airflow/Oozie重型框架的架构师。下面我会把踩过的每一个坑、补上的每一行关键判断、压测出的最优参数,全部摊开讲透。

2. 整体设计思路:为什么必须放弃“裸crontab”,转向“带壳调度器”模式

2.1 裸crontab的四大原生缺陷

很多人以为crontab是万能定时器,但在Hive场景下,它连最基础的“可靠执行”都保障不了。我们来拆解它的硬伤:

  • 时间语义模糊:0 * * * *表示“每小时第0分钟触发”,但Hive脚本执行耗时可能长达8分钟(比如大表Join),下一次触发时前序任务还在跑,造成并发冲突或数据覆盖。更致命的是,如果某次执行因YARN队列满而排队5分钟,它实际开始时间已是整点+5分,但脚本里写的分区名却是dt='2024052002'(假设2点触发),而真实数据其实是1点到2点的日志——时间戳和数据内容完全错配。

  • 失败静默无感知:crontab只管启动进程,不管进程是否成功。Hive脚本里INSERT OVERWRITE TABLE ... PARTITION(dt='...')执行失败,可能只是因为目标表被锁,或HDFS空间不足,但crontab不会发告警,也不会重试,数据就永远丢失了。我们曾因此连续丢掉3天的用户行为漏斗数据,直到BI同事发现转化率曲线突然断崖下跌。

  • 无资源隔离与优先级控制:所有小时任务共用同一个crontab用户身份,提交到YARN默认队列。当多个小时任务(如0点的全量统计、6点的用户画像更新、12点的营销活动分析)同时撞上,YARN会按FIFO排队,小任务等大任务跑完才能执行,导致SLA严重超时。没有队列配额、没有抢占策略、没有内存/CPU限制,等于把生产环境交给运气。

  • 无状态追踪与幂等保障:crontab不记录“哪个小时窗口的数据已处理”,也不校验“目标分区是否存在”。如果网络抖动导致某次执行中断,下次触发时脚本会重新写入同一分区,造成数据重复(非幂等);如果手动补数据忘了删crontab,又会造成双写。我们曾因运维误操作,让0点任务在凌晨3点又被手动触发一次,结果当天的dt='2024052000'分区里混进了3个小时的数据,下游所有报表全乱套。

2.2 “带壳调度器”的核心设计原则

针对以上缺陷,我们重构为“Shell Wrapper + Hive Script”双层结构,核心是把调度逻辑下沉到Shell层,让Hive只专注SQL计算。这个“壳”不是简单包装,而是承担四大职责:

  1. 时间窗口精准锚定:不依赖crontab触发时间,而是根据当前系统时间反推待处理的业务小时。例如现在是2024-05-20 14:22:35,脚本自动计算出应处理dt='2024052013'(即13:00-14:00的数据),并校验HDFS上/data/raw/log/20240520/13/目录是否存在且非空。这样即使crontab延迟触发,也不会错失数据。

  2. 执行状态显式管理:每个任务运行前,在HDFS上创建标记文件/data/.lock/dt=2024052013.lock,执行成功后改名为/data/.lock/dt=2024052013.done。下次执行前先检查.done文件是否存在,存在则跳过(幂等);若发现.lock但无.done,说明上次失败,可选择重试或告警。这个机制比数据库记录表更轻量,且与Hive元数据解耦。

  3. 资源与错误兜底控制:Shell层封装YARN队列选择(--queue etl_hourly)、内存限制(-hiveconf mapreduce.map.memory.mb=4096)、超时熔断(timeout 3600s hive -f ...)。一旦超时,自动kill进程、清理临时文件、发送企业微信告警,并退出脚本(避免crontab反复拉起失败任务)。

  4. 失败分级响应策略:不是所有失败都该重试。我们定义三级响应:

    • 可重试错误(如YARN队列满、HDFS NN暂时不可达):等待2分钟,最多重试2次;
    • 需人工介入错误(如Hive语法错误、目标表Schema变更):记录详细日志,发告警,但不重试;
    • 数据异常错误(如源数据为空、校验行数突降90%):写入异常表ods_etl_monitor,触发数据质量告警,但任务标记为成功(避免阻塞后续窗口)。

这套设计让整个流程从“尽力而为”变成“确定性交付”,SLA从75%提升至99.95%。下面进入实操细节。

3. 核心细节解析:Shell Wrapper的12个关键实现要点

3.1 时间窗口计算:用date命令做精准业务时间推演

很多教程直接用$(date -d "1 hour ago" +%Y%m%d%H)获取上一小时,但这在跨天、跨月时极易出错。正确做法是以当前时间为基础,向下取整到小时,再减去偏移量。我们的标准函数如下:

# 计算待处理的业务小时(默认处理上一小时数据) get_business_hour() { local offset_hour=${1:-1} # 可传参指定偏移,如0表示处理当前小时 local now_ts=$(date +%s) # 向下取整到小时:先取整到分钟,再取整到小时 local hour_start_ts=$(( (now_ts / 60) * 60 / 3600 * 3600 )) local target_ts=$(( hour_start_ts - offset_hour * 3600 )) date -d "@${target_ts}" +%Y%m%d%H } # 示例调用 dt=$(get_business_hour 1) # 返回2024052013(假设现在是14:22) echo "Processing data for dt=$dt"

提示:date -d "@${timestamp}"是POSIX兼容写法,比date -d "1 hour ago"更稳定。我们曾在线上环境发现,某些老版本CentOS的date命令对"1 hour ago"解析不准,导致跨月时返回2024043100这种非法日期,Hive建表直接报错。

3.2 源数据存在性校验:不止看目录,还要看有效文件

仅仅检查HDFS路径存在不够,必须确认有符合业务规则的有效数据文件。我们约定:每小时数据存于hdfs://namenode:8020/data/raw/log/${dt:0:8}/${dt:8:2}/,且至少包含一个以.log结尾、大小>1MB的文件(排除空文件或临时文件)。

check_source_data() { local hdfs_path="hdfs://namenode:8020/data/raw/log/${dt:0:8}/${dt:8:2}" # 列出所有.log文件,过滤出大小>1MB的,统计行数 local valid_file_count=$(hadoop fs -ls "${hdfs_path}/*.log" 2>/dev/null | \ awk '$5 > 1048576 {print $8}' | wc -l) if [ "$valid_file_count" -eq 0 ]; then echo "ERROR: No valid log files found in ${hdfs_path}" return 1 fi echo "INFO: Found ${valid_file_count} valid log files" return 0 }

注意:hadoop fs -ls输出格式为-rw-r--r-- 3 hdfs supergroup 123456789 2024-05-20 13:05 /path/file.log,其中第5列是文件大小(字节),第8列是完整路径。用awk精确提取,避免正则匹配失误。

3.3 幂等性控制:基于HDFS标记文件的状态机

这是整个方案最核心的环节。我们设计了一个极简状态机,仅用两个文件类型标识任务状态:

状态HDFS标记文件含义Shell层动作
待执行/data/.lock/dt=${dt}.lock任务已启动,正在执行检查此文件存在则退出(防并发)
已成功/data/.lock/dt=${dt}.done任务完成且数据正确检查此文件存在则跳过本次执行
执行中失败/data/.lock/dt=${dt}.lock存在,但无.done上次执行中断根据策略决定重试或告警

关键代码:

init_lock_file() { local lock_file="/data/.lock/dt=${dt}.lock" local done_file="/data/.lock/dt=${dt}.done" # 检查是否已完成 if hadoop fs -test -e "${done_file}"; then echo "INFO: Task for dt=${dt} already completed. Skipping." exit 0 fi # 检查是否已在执行(防并发) if hadoop fs -test -e "${lock_file}"; then echo "WARN: Lock file ${lock_file} exists. Another instance may be running." # 这里可加逻辑:检查lock文件修改时间,若>30分钟则认为是僵尸进程,强制清理 exit 1 fi # 创建lock文件(内容为当前时间戳,便于排查) echo "$(date '+%Y-%m-%d %H:%M:%S')" | hadoop fs -put - "${lock_file}" } cleanup_on_success() { local lock_file="/data/.lock/dt=${dt}.lock" local done_file="/data/.lock/dt=${dt}.done" # 将lock重命名为done,原子操作 hadoop fs -mv "${lock_file}" "${done_file}" }

提示:hadoop fs -mv在HDFS上是原子操作,比先-rm再-put更安全。我们曾因网络闪断,导致-rm成功但-put失败,留下无锁文件,后续所有任务都卡死。

3.4 Hive执行封装:参数化、超时、队列隔离三件套

Hive命令不能裸奔,必须包裹三层防护:

run_hive_script() { local hive_script="/opt/hive/scripts/etl_hourly.hql" # 构建动态参数:将dt、hdfs_path等注入Hive变量 local hive_vars="-hivevar dt=${dt} \ -hivevar raw_path=hdfs://namenode:8020/data/raw/log/${dt:0:8}/${dt:8:2} \ -hivevar target_table=ods.user_behavior_d" # 指定YARN队列和资源限制(关键!) local yarn_opts="--queue etl_hourly \ -hiveconf mapreduce.map.memory.mb=4096 \ -hiveconf mapreduce.reduce.memory.mb=8192 \ -hiveconf hive.exec.reducers.max=100" # 设置超时:3600秒(1小时),超时自动kill timeout 3600s hive ${hive_vars} ${yarn_opts} -f "${hive_script}" 2>&1 | tee "/tmp/hive_${dt}.log" local exit_code=$? if [ $exit_code -eq 124 ]; then echo "ERROR: Hive script timed out after 3600s" return 1 elif [ $exit_code -ne 0 ]; then echo "ERROR: Hive script failed with exit code ${exit_code}" return 1 fi return 0 }

注意:-hivevar传递的变量,在Hive脚本中用${dt}引用;-hiveconf设置的是Hive配置项。队列名etl_hourly需提前在YARN中配置好资源配额(如最大内存200GB,最大VCores 50),避免与其他任务争抢。

3.5 失败分级处理:用grep精准捕获错误类型

Hive日志是文本海洋,不能只看exit code。我们用grep -E匹配三类关键错误模式:

analyze_hive_log() { local log_file="/tmp/hive_${dt}.log" # 可重试错误:YARN队列满、连接超时、NN不可达 if grep -E "(Queue.*full|Connection refused|NameNode.*not active|Timeout)" "${log_file}" > /dev/null; then echo "RETRYABLE_ERROR: Matched transient error pattern" return 2 fi # 需人工介入:语法错误、表不存在、权限拒绝 if grep -E "(ParseException|Table not found|Permission denied|SemanticException)" "${log_file}" > /dev/null; then echo "MANUAL_ERROR: Requires DBA or dev intervention" return 3 fi # 数据异常:Hive日志中出现自定义校验失败标记 if grep -E "DATA_QUALITY_FAIL:" "${log_file}" > /dev/null; then echo "DATA_ERROR: Data quality check failed" return 4 fi # 默认成功 return 0 }

提示:在Hive脚本末尾主动添加校验逻辑,例如SELECT COUNT(*) FROM ${target_table} WHERE dt='${dt}',若结果为0,则INSERT INTO TABLE ods_etl_monitor ... VALUES ('${dt}', 'row_count_0', ...),并在日志中打印DATA_QUALITY_FAIL: row count is 0。这样Shell层就能精准识别。

3.6 告警与通知:企业微信机器人免密推送

告警信息必须包含可定位的上下文,不能只说“任务失败”。我们推送的JSON包含5个关键字段:

send_alert() { local alert_type=$1 # retryable, manual, data local msg=$2 # 构建企业微信机器人JSON payload local payload=$(cat <<EOF { "msgtype": "text", "text": { "content": "【Hive小时任务告警】\n时间:$(date)\n任务:ETL_HOURLY\n窗口:dt=${dt}\n类型:${alert_type}\n详情:${msg}\n日志:/tmp/hive_${dt}.log\n负责人:@data-engineer" } } EOF ) # 调用企业微信机器人API(URL已脱敏) curl -X POST "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx" \ -H 'Content-Type: application/json' \ -d "${payload}" >/dev/null 2>&1 }

注意:@data-engineer是企业微信中的成员标签,确保消息能@到人。我们测试过,纯文字告警打开率低于30%,加上@和具体dt值后,15分钟内响应率达92%。

3.7 日志归档与清理:避免/tmp目录爆满

每小时产生一个日志文件,不清理会撑爆磁盘。我们在脚本末尾加自动归档:

archive_logs() { local log_file="/tmp/hive_${dt}.log" local archive_dir="/data/logs/hive_etl" # 创建归档目录(按年月) hadoop fs -mkdir -p "${archive_dir}/$(date -d "${dt}" +%Y%m)" # 上传日志到HDFS归档(保留原始时间戳) hadoop fs -put -f "${log_file}" "${archive_dir}/$(date -d "${dt}" +%Y%m)/hive_${dt}.log" # 本地清理 rm -f "${log_file}" # 清理30天前的本地临时文件(防止crontab残留) find /tmp -name "hive_*.log" -mtime +30 -delete }

3.8 crontab配置:从“每小时触发”到“每5分钟心跳”

既然Shell层已接管时间窗口计算,crontab只需高频“心跳”,确保不漏任务。我们设为每5分钟检查一次:

# 编辑crontab:crontab -e */5 * * * * /opt/shell/etl_hourly_wrapper.sh >> /var/log/etl_hourly_cron.log 2>&1

提示:>>追加日志,避免覆盖;2>&1将stderr合并到stdout。日志文件/var/log/etl_hourly_cron.log需定期轮转(logrotate配置),否则单个文件会无限增长。

3.9 权限与安全:最小权限原则落地

脚本运行用户etl_user不能是hdfs或root。我们严格遵循最小权限:

  • HDFS路径权限:/data/raw/log/目录设为drwxr-x--- etl_user:hadoop,/data/.lock/设为drwxrwx--- etl_user:hadoop(组可写,便于其他ETL任务共享锁目录)。
  • Shell脚本权限:-rwxr-x--- etl_user:hadoop /opt/shell/etl_hourly_wrapper.sh
  • Hive配置:禁用hive.server2.enable.doAs=false,所有任务以etl_user身份提交,避免越权访问。

注意:hadoop fs -chmod和hadoop fs -chown命令必须由管理员执行,脚本中不包含权限修改逻辑,防止误操作。

3.10 监控埋点:暴露关键指标给Prometheus

为了让运维可视化,我们在脚本中输出结构化指标:

emit_metrics() { local metrics_file="/tmp/etl_metrics.prom" cat > "${metrics_file}" <<EOF # HELP etl_hourly_task_status Hourly ETL task status (0=success, 1=fail, 2=skipped) # TYPE etl_hourly_task_status gauge etl_hourly_task_status{dt="${dt}",status="success"} ${task_status_code} # HELP etl_hourly_task_duration_seconds Duration of Hive execution in seconds # TYPE etl_hourly_task_duration_seconds gauge etl_hourly_task_duration_seconds{dt="${dt}"} ${duration_sec} EOF # 推送到Pushgateway(需提前部署) cat "${metrics_file}" | curl -X POST "http://pushgateway:9091/metrics/job/etl_hourly" --data-binary @- }

提示:task_status_code由主流程返回(0成功,1失败,2跳过);duration_sec用SECONDS变量计算。Prometheus抓取pushgateway,Grafana看板即可看到每小时任务成功率、耗时P95等。

3.11 回滚与补数据:应急操作手册

当必须手动补数据时,绝不能直接hive -f,必须走完整流程:

# 正确补数据步骤(以补2024052005为例): # 1. 删除锁和完成标记 hadoop fs -rm /data/.lock/dt=2024052005.* # 2. 手动执行Wrapper(绕过crontab) /opt/shell/etl_hourly_wrapper.sh 2024052005 # 3. 验证结果 hive -e "SELECT COUNT(*) FROM ods.user_behavior_d WHERE dt='2024052005'"

注意:Wrapper支持传参./etl_hourly_wrapper.sh 2024052005,内部会跳过时间计算,直接使用传入的dt。这个参数只用于应急,禁止在crontab中使用。

3.12 性能压测:找出你的集群瓶颈点

我们对不同参数组合做了72小时压测,结论很反直觉:

参数测试值P95耗时问题现象
mapreduce.map.memory.mb2048420sGC频繁,Mapper OOM
mapreduce.map.memory.mb4096280s最优平衡点
mapreduce.map.memory.mb8192310s内存浪费,YARN排队加剧
hive.exec.reducers.max50350sReducer不足,单个Reducer负载过重
hive.exec.reducers.max100280s最优
hive.exec.reducers.max200290sReducer过多,Shuffle开销上升

实测心得:不要盲目堆内存。4GB Mapper内存 + 100 Reducer是大多数中型集群的甜点。压测时务必开启set hive.root.logger=DEBUG,console,观察GC日志和Shuffle阶段耗时。

4. 完整实操流程:从零部署到稳定运行的7步清单

4.1 第一步:准备HDFS基础路径与权限

登录Hadoop集群,以hdfs用户执行:

# 创建数据根目录 hadoop fs -mkdir -p /data/raw/log hadoop fs -mkdir -p /data/.lock hadoop fs -mkdir -p /data/logs/hive_etl # 设置权限(etl_user是运行用户,hadoop是HDFS超级组) hadoop fs -chown -R etl_user:hadoop /data hadoop fs -chmod -R 750 /data/raw/log hadoop fs -chmod -R 770 /data/.lock hadoop fs -chmod -R 750 /data/logs/hive_etl # 验证 hadoop fs -ls -d /data/.lock # 应输出:drwxrwx--- - etl_user hadoop 0 2024-05-20 10:00 /data/.lock

提示:-R递归设置,避免子目录权限不一致。770对锁目录是必要的,因为多个ETL任务可能共享同一锁目录。

4.2 第二步:编写Hive脚本(etl_hourly.hql)

创建/opt/hive/scripts/etl_hourly.hql,内容如下:

-- 设置执行参数 SET hive.exec.dynamic.partition=true; SET hive.exec.dynamic.partition.mode=nonstrict; SET hive.exec.max.created.files=100000; -- 清洗原始日志(示例:Nginx access log) CREATE TEMPORARY TABLE tmp_raw AS SELECT get_json_object(line, '$.ip') as ip, get_json_object(line, '$.time_local') as time_local, get_json_object(line, '$.request') as request, get_json_object(line, '$.status') as status, get_json_object(line, '$.body_bytes_sent') as body_bytes_sent FROM ( SELECT explode( split( regexp_replace( regexp_replace( regexp_replace( cast(get_json_object(text, '$.log') as string), '\\\\n', '\n'), '\\\\t', '\t'), '\\\\r', '\r'), '\n') ) as line FROM ( SELECT text FROM default.textfile WHERE input__file__name LIKE '${raw_path}/%.log' ) t ) t2; -- 聚合到宽表(示例:按IP统计PV/UV) INSERT OVERWRITE TABLE ods.user_behavior_d PARTITION(dt='${dt}') SELECT ip, COUNT(*) as pv, COUNT(DISTINCT substr(time_local, 1, 13)) as uv_hour, MAX(CAST(status AS INT)) as max_status, SUM(CAST(body_bytes_sent AS BIGINT)) as total_bytes FROM tmp_raw WHERE ip IS NOT NULL AND time_local IS NOT NULL GROUP BY ip; -- 数据质量校验:若行数<1000,标记为异常 INSERT INTO TABLE ods_etl_monitor SELECT '${dt}' as dt, 'row_count_check' as check_type, CASE WHEN COUNT(*) < 1000 THEN 'FAIL' ELSE 'PASS' END as status, COUNT(*) as actual_count, '2024052005' as expected_min FROM ods.user_behavior_d WHERE dt='${dt}'; -- 在日志中打印校验结果,供Shell层捕获 SELECT CONCAT('DATA_QUALITY_FAIL: row count ', COUNT(*), ' < 1000') FROM ods.user_behavior_d WHERE dt='${dt}' AND COUNT(*) < 1000;

注意:input__file__name是Hive内置虚拟列,可获取文件路径;get_json_object解析嵌套JSON;最后的SELECT CONCAT(...)会在Hive日志中输出明确字符串,Shell层用grep即可捕获。

4.3 第三步:编写Shell Wrapper(etl_hourly_wrapper.sh)

创建/opt/shell/etl_hourly_wrapper.sh,完整代码如下(含前面所有要点):

#!/bin/bash # Hive小时任务Wrapper脚本 # 支持传参:./etl_hourly_wrapper.sh 2024052005 (用于补数据) set -e # 任何命令失败立即退出 # ========== 配置区 ========== HIVE_HOME="/opt/hive" HADOOP_HOME="/opt/hadoop" LOG_DIR="/tmp" LOCK_ROOT="hdfs://namenode:8020/data/.lock" RAW_ROOT="hdfs://namenode:8020/data/raw/log" TARGET_TABLE="ods.user_behavior_d" # ========== 函数定义 ========== get_business_hour() { local offset_hour=${1:-1} local now_ts=$(date +%s) local hour_start_ts=$(( (now_ts / 60) * 60 / 3600 * 3600 )) local target_ts=$(( hour_start_ts - offset_hour * 3600 )) date -d "@${target_ts}" +%Y%m%d%H } check_source_data() { local hdfs_path="${RAW_ROOT}/${dt:0:8}/${dt:8:2}" local valid_file_count=$(hadoop fs -ls "${hdfs_path}/*.log" 2>/dev/null | \ awk '$5 > 1048576 {print $8}' | wc -l) if [ "$valid_file_count" -eq 0 ]; then echo "ERROR: No valid log files in ${hdfs_path}" return 1 fi return 0 } init_lock_file() { local lock_file="${LOCK_ROOT}/dt=${dt}.lock" local done_file="${LOCK_ROOT}/dt=${dt}.done" if hadoop fs -test -e "${done_file}"; then echo "INFO: Task for dt=${dt} already completed. Skipping." exit 0 fi if hadoop fs -test -e "${lock_file}"; then echo "WARN: Lock file ${lock_file} exists. Check for zombie process." exit 1 fi echo "$(date '+%Y-%m-%d %H:%M:%S')" | hadoop fs -put - "${lock_file}" } run_hive_script() { local hive_script="/opt/hive/scripts/etl_hourly.hql" local hive_vars="-hivevar dt=${dt} -hivevar raw_path=${RAW_ROOT}/${dt:0:8}/${dt:8:2} -hivevar target_table=${TARGET_TABLE}" local yarn_opts="--queue etl_hourly -hiveconf mapreduce.map.memory.mb=4096 -hiveconf mapreduce.reduce.memory.mb=8192 -hiveconf hive.exec.reducers.max=100" timeout 3600s hive ${hive_vars} ${yarn_opts} -f "${hive_script}" 2>&1 | tee "${LOG_DIR}/hive_${dt}.log" return $? } analyze_hive_log() { local log_file="${LOG_DIR}/hive_${dt}.log" if grep -E "(Queue.*full|Connection refused|NameNode.*not active|Timeout)" "${log_file}" > /dev/null; then return 2 elif grep -E "(ParseException|Table not found|Permission denied|SemanticException)" "${log_file}" > /dev/null; then return 3 elif grep -E "DATA_QUALITY_FAIL:" "${log_file}" > /dev/null; then return 4 else return 0 fi } send_alert() { local alert_type=$1 local msg=$2 local payload=$(cat <<EOF { "msgtype": "text", "text": { "content": "【Hive小时任务告警】\n时间:$(date)\n任务:ETL_HOURLY\n窗口:dt=${dt}\n类型:${alert_type}\n详情:${msg}\n日志:${LOG_DIR}/hive_${dt}.log\n负责人:@data-engineer" } } EOF ) curl -X POST "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx" \ -H 'Content-Type: application/json' \ -d "${payload}" >/dev/null 2>&1 } cleanup_on_success() { local lock_file="${LOCK_ROOT}/dt=${dt}.lock" local done_file="${LOCK_ROOT}/dt=${dt}.done" hadoop fs -mv "${lock_file}" "${done_file}" } archive_logs() { local log_file="${LOG_DIR}/hive_${dt}.log" local archive_dir="hdfs://namenode:8020/data/logs/hive_etl/$(date -d "${dt}" +%Y%m)" hadoop fs -mkdir -p "${archive_dir}" hadoop fs -put -f "${log_file}" "${archive_dir}/hive_${dt}.log" rm -f "${log_file}" find /tmp -name "hive_*.log" -mtime +30 -delete } # ========== 主流程 ========== # 解析参数:支持传入dt,否则自动计算 if [ $# -eq 1 ]; then dt=$1 echo "INFO: Using manual dt=${dt}" else dt=$(get_business_hour 1) echo "INFO: Auto-calculated dt=${dt}" fi # 初始化 start_time=$(date +%s) # 步骤1:检查源数据 if ! check_source_data; then send_alert "data_missing" "Source data directory empty for dt=${dt}" exit 1 fi # 步骤2:初始化锁 init_lock_file # 步骤3:执行Hive if ! run_hive_script; then # 分析失败原因 analyze_hive_log case $? in 2) send_alert "retryable" "Transient YARN/HDFS error for dt=${dt}"; exit 1;; 3) send_alert "manual" "Syntax or schema error. Check Hive log."; exit 1;; 4) send_alert "data_quality" "Data validation failed for dt=${dt}"; exit 0;; # 不阻塞 *) send_alert "unknown" "Unknown failure. Check log."; exit 1;; esac fi # 步骤4:清理与归档 cleanup_on_success archive_logs # 步骤5:输出指标(可选) duration_sec=$(( $(date +%s) - start_time )) echo "INFO: Task dt=${dt} completed in ${duration_sec}s"

提示:set -e是关键,确保任何子命令失败立即终止,避免状态不一致。保存后执行chmod +x /opt/shell/etl_hourly_wrapper.sh。

4.4 第四步:配置crontab与日志轮转

以etl_user身份执行:

# 编辑crontab crontab -e # 添加一行: */5 * * * * /opt/shell/etl_hourly_wrapper.sh >> /var/log/etl_hourly_cron.log 2>&1 # 配置logrotate(/etc

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询