简介:《Control-M作业配置手册》是一份面向企业调度平台Control-M(CTM)的作业定义与配置详解,适合初次接触CTM的运维人员与批处理开发工程师,也可作为有经验用户的速查参考。内容从CONTROL-M/EM与SERVER架构入手,系统讲解作业定义库、AJF自动作业流、Smart Table/Sub Table的定义方法,并通过三个完整实例演示作业调度、依赖关系、监控告警及最佳实践,同时归纳了作业定义过程中需考虑的时间、资源、依赖等关键因素。涵盖基础概念到高级工作流设计的全链路知识,可帮助读者快速掌握作业执行方式与定义要点,减少配置错误和排障时间。压缩包内共1个doc文档,大小3.05MB,图文配合、目录清晰,从概览、作业定义工具到实例逐步深入。该手册已有逾7.4万人次浏览学习,适合刚上手CTM的读者边学边查。
1. Control-M 作业配置到底在解决什么问题:先分清“脚本能跑”和“作业能调度”
如果你刚接手批量调度平台,第一次打开 Control-M 控制台时最容易犯的错,是把“配置作业”当成“写脚本”。脚本能不能跑只是第一步,真正麻烦的是另外三件事:谁先跑、谁在后边等、这一天到底该不该跑。Control-M 作业配置要解决的问题,就是把一批彼此有先后、有依赖、有日期规则的脚本,变成可追踪、可重跑、可回滚的调度流程。
这个方向不挑业务:日终报表、数据抽取、文件交换、批量加工,核心套路都一样。你不需要先背下一堆菜单,只需要先理解“作业 - 作业流 - 日历”三层模型。下面按建单作业、组作业流、配日历、传参数、排故障的顺序讲,最后落在配置治理和回滚习惯上。新人能照着搭出第一个可跑通的作业;已经维护了很多作业的人,也能在这里对照检查那些反复出现的“空跑、重复跑、日期跑错”的问题。
2. 从单个作业到作业流:先把最小可跑通的调度模型搭起来
2.1 作业、作业流、日历:三者边界先别混
Control-M 里的最小执行单位是“作业”,它至少包含作业名、运行用户、命令路径、归属目录,以及一串调度条件。作业本身不一定要放进作业流里,一个不带条件的作业可以被手工运行,但生产环境几乎不这么做,因为“跑一次”和“每天稳定跑一次”是两码事。
作业流解决的是顺序问题:A 成功之后 B 才启动,B 失败之后是补跑还是告警,C 和 D 能不能并发。需要提醒的是,这里说的作业流不是工作流引擎里那种自由态模型,而是带调度语义的执行链。每个作业在完成时会生成成功或失败状态,后续作业依靠这些状态决定是否启动、等待多久、超时后怎么处理。
日历解决的是“今天到底跑不跑”。如果你不给作业配日历,调度平台不会帮你猜一个执行日期。作业流放的日期组合通常来自日历表,而不是作业内部的时间判断。很多配置混乱的配置都出在同一个地方:把“哪天跑”写在日历里,又把“星期几”写在作业命令里,最后谁都说了算,谁都说不清。建议把边界切成三块:哪天跑交给日历,几点跑交给调度时间,跑完干什么交给作业顺序和条件。
2.2 用 GUI 或自动化 API 建第一个作业:最小字段与操作步骤
先建一个最简模型,验证你的脚本能被调度平台正常拉起。常见操作路径是登录 Control-M Web 控制台后,新建一个文件夹,再在文件夹里加入作业。文件夹在生产里建议按业务域命名,例如 DAILY_LOAD、MONTH_REPORT,避免两年后看到一百多个作业却不知道属于哪条链路。
搭建最小模型按五步走:
- 新建文件夹,命名如 DAILY_LOAD。
- 在文件夹里新建作业,作业类型选 Command。
- 填写作业名、命令、运行用户、所属文件夹。
- 先不配置任何条件、日历,直接用手工运行验证脚本。
- 在运行日志里确认实际命令、返回码、输出内容。
作业字段里最关键的几个值如下表:
| 字段 | 推荐写法 | 说明 |
|---|---|---|
| 作业名 | CUST_LOAD_DAILY | 尽量体现业务和周期,别用 test123 |
| 命令 | /apps/batch/load_customer.sh | 写绝对路径,不要写 cd /dir; sh xxx |
| 运行用户 | batch_user | 使用批处理专用账号,不用个人账号 |
| 作业类型 | Command | 最通用,也最容易排查 |
| 时间 | 02:30 | 尽量和同链路其他作业错开 |
命令最好别写成cd /tmp && sh run.sh,调度平台拉起作业时的工作目录不一定是你终端里的目录。下面是一个脚本示例,重点在于脚本本身要能接收参数、要具备幂等性:
#!/usr/bin/env bash # 最小可跑通的作业脚本:带业务日期参数,且重复执行不覆盖 set -euo pipefail # 参数一:业务日期,由调度平台传入,而不是在脚本里取 date PROC_DATE="${1:-$(date +%Y%m%d)}" BATCH_DIR="/data/batch/${PROC_DATE}" LOCK_FILE="${BATCH_DIR}/.job.lock" mkdir -p "${BATCH_DIR}" if [ -f "${LOCK_FILE}" ]; then echo "already processed: ${PROC_DATE}" exit 0 fi # 这里放你的实际业务逻辑 echo "start load customer, date=${PROC_DATE}" # 业务处理完成后留下完成标记 echo "${PROC_DATE}" > "${LOCK_FILE}"这段脚本的关键在最后一层:它把“是否已经处理过”的判断放到应用侧。调度平台负责什么时候拉起作业,应用侧负责重复拉起时不要重复写数据。PROC_DATE这个参数等第 4 章讲传参时再展开,现在你先理解:一个能被调度平台反复拉起、反复重跑而不会搞坏数据的脚本,才适合挂进生产作业流。
2.3 作业排序与条件依赖:不靠“写成一行”而是靠“前驱状态”
如果业务里已经有三个脚本,它们是按顺序执行的,最简单的做法是写一个大脚本从头跑到尾;但这样的缺点是:中间某一步断了,整段重跑,且看不出断在哪一步。Control-M 的推荐做法是把三个脚本拆成三个作业,用作业状态连接起来。
常见依赖配置如下表:
| 依赖类型 | 配置方式 | 典型场景 |
|---|---|---|
| 前驱成功后执行 | 作业 B 的条件设置为“作业 A 成功” | 抽取完成后才能装载 |
| 前驱失败后分支 | 作业 B 失败后触发告警或执行补偿作业 | 装载失败后清理临时表 |
| 文件到达后执行 | 使用文件触发作业等待指定路径 | 外部系统传到文件后开始处理 |
| 时间窗口 | 从 02:30 开始等,等到 03:30 未满足就超时 | 防止无限期等待 |
在界面里,不要手工去记复杂的条件表达式。选中后续作业,在条件选项卡里选择条件类型为“作业状态”,再指定前驱作业和期望的状态即可。这样配置的好处是,运行监控页能直接看到“B 正在等待 A 成功”这样的提示,排障时不需要翻代码。
我一般会建议把“失败后做什么”也配在作业上,而不是只靠报警。例如 C 装载数据库失败后,自动执行一个清理作业,把本次写入的半成品数据回滚,然后再发通知。这样值班人员收到消息时,看到的是已经被处理过的现场,而不是一堆脏数据。
2.4 作业流的最小实践:独立运行、试运行与排错
作业流配好后不要直接在生产里点“强制运行”。更稳的做法是复制出一个测试文件夹,在测试副本里把输出路径、目标表都改成测试区,确认条件链完整后,再手工运行整个作业流。
手工运行和按日历运行有差别。手工运行只是把作业拉到当前日期执行,它不会绕过依赖条件,也不会改变日历判断。换话说,如果作业挂在等待条件上,手工运行也不会立刻启动,这能帮助你保留真实依赖关系。试运行状态下,建议重点关注四个位置:
- 作业状态:等待、运行中、完成 OK、异常退出、强制终止。
- 运行日志:看命令在调度平台侧展开后的真实内容。
- 返回码:看脚本执行结果是否被平台正确识别。
- 条件列表:看当前作业到底在等哪个前驱。
排查时有一个好习惯:先看日志里展开后的命令,再回查脚本本身。很多作业失败不是因为逻辑写错,而是命令字段里的参数、引号、路径在平台侧被解析成了另一个样子。这一点也是后面传参和避坑章节的重点。
3. 生产日历:把跑批节奏和业务日期焊死在配置里
3.1 规则日历、工作日偏移与月末场景怎么选
日历是 Control-M 作业配置里最容易偷懒、也最容易出事的一层。很多人觉得日历就是“每周跑一次”“每天跑一次”的开关,实际上生产日历要处理的是月末、季末、法定节假日、行业特殊日。
规则日历适合周期固定的场景。比如每天跑、周一到周五跑、每月 5 号跑、每月最后一个工作日跑。特殊日历适合手工勾选日期的场景,比如大促日、关账日、每年临时加班的星期六。建议不要把所有日期规则都堆在一个日历里,而是建一个“通用工作日历”作为基底,再用特殊日表做加减法。
月末场景是最典型的翻车点。假设业务要求在每月最后一天完成对账,但最后一天恰好是星期六,平台不会自动把作业顺延到下一个工作日,除非你把它配置成“最后一个工作日”。如果业务还要求“最后一个工作日之后一天再跑下一个作业”,要注意区分自然日偏移和业务日偏移。写“+1 个业务日”和写“+1 天”结果完全不同,尤其是跨周五的情况下。
3.2 处理日期和运行日期:为什么作业里不要拿系统日期
批量作业里有两类日期很容易混:一是“当前调度系统认为今天是哪一天”,二是“这笔数据实际属于哪一天”。后者在行业里叫业务日期或处理日期。
举个例子:日终批在凌晨 00:30 运行,它处理的是前一天的交易数据。如果脚本里写date +%Y%m%d,跨月时 1 号凌晨算出来的日期是 1 号,但你要处理的是上月 31 号的数据。文件名、表分区、对账日期都会错一位。
还有更隐蔽的情况:假期后第一个工作日,系统把假期没跑的数据一次性补跑,这时服务器日期已经跳到了假期后,脚本如果只认系统日期,根本不知道自己在补哪天。正确做法是把业务日期作为参数,由调度平台在配置里传下去。你在日历上看到的“调度日期”和实际命令里要用的“处理日期”可以不同;这个偏移量应该在作业配置里显式表达,而不是让脚本去猜。
3.3 日期变量与替换:最小参数配置
把日期穿进作业命令,最可靠的方式是使用作业级变量。在作业属性里新建一个自定义变量,比如 PROC_DATE,类型选日期,格式设为 YYYYMMDD,然后在命令字段里引用它。界面表单里填入的调度日期,如果它也是用同一来源,那么命令中引用这个变量就不会出现脚本里的系统日期错误。
命令字段的写法类似这样:
/apps/batch/report_daily.sh "${PROC_DATE}"注意参数加了双引号。如果变量包含空格或者为空,不加引号会被 shell 拆成多个参数,脚本拿到的就不是一个完整日期。日志里如果看到命令变成/apps/batch/report_daily.sh 2025 01 01这种样式,基本可以断定是引号问题。另一个建议是,不要把变量替换结果再拼进路径里去做判断,比如if [ "${PROC_DATE}" == "20250101" ],这种逻辑放在调度侧很脆,迁移日期环境后很容易遗忘。
在作业运行后,到运行日志里找到“展开命令”或“实际命令”,确认变量是否被替换成你预期的值。这一步虽然很小,却能挡掉后续很多“明明脚本没问题,调度跑出来却不对”的疑难杂症。
3.4 需要调整的三个日历属性:过期处理、假日补跑、提前预推送
日历配置不是“放行或不放行”这么简单。还有三个属性生产上要专门过一遍。
第一个是过期处理。作业如果因为前一天的调度故障没有运行,第二天平台应该跳过期作业还是立即补跑?不同业务要求不同,但默认值不一定符合业务预期。每类作业都要明确写下来,避免事后扯“为什么昨天没人通知”。
第二个是假日补跑。法定节假日系统是不放行的,但很多报表在假日次日补跑时,业务日期要回填到假日当天。此时不能简单把日历改成每日,否则会把正常工作日也跑一遍;正确的做法是维护一个“特殊放行日”清单,并在补跑时手动指定业务日期参数。
第三个是提前预推送。有些下游系统需要提前一小时收到数据,但日历触发时间又希望保持不变。这个场景不要在作业命令里做 sleep,应该使用日历偏移或发布条件,让下游作业先启动等待。下面是一个日历参数对照表:
| 场景 | 建议配置 | 不要做的事 |
|---|---|---|
| 每月最后一天 | 规则日历 + 最后一个工作日 | 写死每月 30 或 31 号 |
| 周末不跑 | 通用工作日历 | 在脚本里判断星期几 |
| 假期不跑 | 特殊日历排除 | 靠人工临时改作业 |
| 月末加跑 | 增加特殊放行日 | 修改通用日历 |
4. 脚本接入与参数传递:从“能跑”到“能正确传参、能走分支”
4.1 作业类型选型:Command、文件触发、作业流调用
Control-M 里作业类型比较多,但平时最常用的是三类。Command 作业就是执行一条命令或一个脚本,适合百分之八十以上的场景;文件触发作业用来等待某个文件出现或消失,适合外部系统之间交换文件的场景;作业流调用则是把一个公共流程包成子流程,供多个主流程复用。
三类作业怎么选,有一个简单的判断标准:如果作业需要主动轮询一个文件是否存在,优先考虑文件触发;如果多个业务流都要执行同一套数据清洗逻辑,把清洗做成可调用的作业流分支;如果只是按顺序跑脚本,直接用 Command 作业,别绕弯子。
文件触发作业特别适合“上游文件凌晨传过来,传完才能抽数”的场景。用 Command 去定时检查文件路径,会带来大量空跑;文件触发作业由 Agent 端去监听文件出现,文件一到再启动后续作业,不仅省资源,依赖关系也清楚。
4.2 命令写法与运行用户:路径、环境变量、umask
作业命令的写法看起来简单,但生产事故有不少就出在这里。第一,命令必须写绝对路径。第二,运行用户要固定,不要让同一个作业今天用 batch_user 明天用 root。第三,脚本内要自己加载环境变量,不要依赖调度平台帮你初始化 shell 环境。
很多批处理作业喜欢在命令字段里做一堆拼接,比如/usr/bin/bash /apps/run.sh > /logs/run_$(date +%Y%m%d).log 2>&1。这种写法的问题在于,日志文件名里的日期来自系统日期,而不是业务日期;跨月和补跑时,日志会写到错误文件,排障时找不到。
更稳的命令写法是只保留业务连参数,其他环境变量交给脚本处理:
/usr/bin/env bash /apps/batch/load_customer.sh "${PROC_DATE}"这里显式用了/usr/bin/env bash,避免不同服务器默认 shell 不同导致语法不兼容。日志重定向放在平台侧或脚本内都可以,但一致性更重要:要么所有作业都在脚本里重定向,要么都交给平台收集。
运行用户的环境是个大坑。经常出现终端手动跑没问题、调度跑却报找不到命令,原因就是调度用户没加载/etc/profile或用户.bash_profile。建议脚本开头通过source /etc/batch_env.sh统一加载 PATH、PYTHONPATH、LD_LIBRARY_PATH,而不是依赖交互式 shell。
4.3 输出条件与文件条件的运用:让后续作业判断“是否继续”
作业成功执行并不代表数据正确。经常有场景:抽数作业返回码为 0,但生成的数据文件是空的,后续装载作业照样跑,最后报表对不上账。为了挡掉这种问题,用一条文件条件等着检查结果文件。
具体做法是在抽数作业之后插入一个文件触发作业,等待目标文件出现,并且指定最小文件大小或非空判断。文件触发作业能等多久,也要设置超时时间。如果文件一直没来,作业进入异常状态,然后触发告警。这样后续装载作业不会因为文件不存在而启动一个空流程。
另一种常见场景是失败分支。比如数据库装载作业失败后,先执行一个清理作业,把已经写入的半成品表清空,再通知值班人员。在作业配置里,B 作业的后续可以同时挂两条分支:成功走“通知完成”,失败走“清理和告警”。这样值班看到的状态是“已被处理”,而不是一条孤零零的失败红色。
4.4 重试、通知与恢复选项:配置失误更早被发现
自动重试是个好东西,但不要对所有作业一视同仁。数据装载类作业如果失败,首先要确认是不是已经写入了部分数据;这时盲目重试可能产生重复主键或重复金额。正确配置是区分业务幂等性:幂等作业可以自动重试两三次,非幂等作业失败后只告警、不自动重试。
通知也需要分级。失败的作业太多时,如果每个都发一条全组邮件,反而淹没了关键信息。更实用的做法是,关键链路作业失败后发即时通知,非关键作业只在超时未恢复时升级通知。还有一点:通知配置里要带上作业名、运行日期、失败原因摘要,否则值班人员收到消息后还得登录平台查一遍。
恢复选项上,建议统一约定一个“失败后操作”模板。例如:数据库装载作业失败后暂停后续作业、保留现场、通知数据组;报表生成作业失败后直接重试两次、再失败则通知主管。模板不要写得太多,否则维护成本会盖过收益。
5. Control-M 作业配置避坑记录:五个最容易翻车的点
5.1 现象:作业一直停在“等待条件”,没有死也没跑
这是最常看到的故障状态:作业不报错、不运行,一直挂在等待中。出现这种现象,先看作业的条件列表,而不是去查脚本。
原因通常有三种:一是设置了前驱成功条件,但前驱作业没有在当前调度日期放行,所以条件一直未满足;二是条件名写错,前驱作业实际生成的状态和后续作业等待的状态对不上;三是使用了跨文件夹条件,但跨文件夹条件没有在系统参数里激活或被过滤掉。
解决时要分两步。第一步,在监控页里展开作业的“等待条件”,确认它到底在等谁;第二步,用排除法临时禁用该条件,手工运行一次,如果作业能跑通,说明脚本没问题,问题一定出在条件关系上。把条件一个个加上去,直到复现出卡住那一层,就能定位到具体的前驱或条件名。
5.2 现象:假期后上班,批处理把同一批数据重复算了一遍
假期结束后的第一个工作日常常是事故高发时段。作业没有按日历跳过假期,导致假期当天遗留作业和假期后正常作业一起跑,数据文件互相覆盖,数据库表重复写。
原因不外乎两个方向。日历侧,生产者没有提前维护假期特殊日历,法定假日仍是放行日;脚本侧,作业命令里用了系统日期,补跑时数据落到了错误的业务日期分区,后续作业基于错误分区重复处理。
解决的办法是:节日来临前,先在日历表里把所有假期标记为非放行日;补跑时不要一次性把所有假期作业全部拉起来,要在作业流级别查看依赖,按业务日期逐个手工指定。补跑前还要检查命令日志中的处理日期,确保脚本拿到的参数确实指向假期当天,而不是系统当前日期。
5.3 现象:参数变量传过去被截断或变成空串
比较典型的是命令字段里写了变量,日志里却看不到完整值。例如日期变量传过去变成202501,少了两天;又比如变量值包含空格,传给脚本时被拆成两个位置参数,后面所有参数整体错位。
原因通常是变量类型定义不对。比如在变量定义里把日期类型设成了数字,前导 0 被去掉;或者是命令字段没有加引号,变量展开后包含空格,被 shell 重新分词;还有一种情况是变量名拼写错误,平台没有替换,直接把原样字符串传给了脚本。
解决时要养成一个习惯:每次作业运行后,第一时间打开日志里展开后的实际命令。确认引号是否存在、变量是否换成了预期值。不要只盯着脚本输出看,调度平台传递进命令字段的内容,才是脚本真正收到的东西。参数问题排查到具体位置后,通常改一处引号或修一处变量类型就能解决,但前置条件是你要先看展开日志。
5.4 现象:重跑作业流时重复执行了同一批任务
故障重跑本身是为了恢复数据,但经常反被重跑坑一把:作业流从第一个作业开始全部重跑,把已经成功执行过的任务又执行了一遍,数据库里出现重复数据。
原因往往是重跑时选择了“重新运行整个作业流”,而不是“从失败作业继续”。只要前几个作业已经成功写入了数据,重跑就会造成脏数据。另一个更深层的原因是应用侧没有批次幂等设计,同一个业务日期可以被重复写入。
解决方式分两层。调度层配置时,明确重跑策略:只有第一个作业从头开始,后续作业如果没有失败就跳过。应用层则要加上批次唯一键或完成标记,例如数据库表里使用业务日期加作业类型作为唯一约束,脚本在执行前先检查结果是否存在。把调度重跑和应用幂等同时做好,才不会一到补跑就提心吊胆。
5.5 现象:并发高峰作业排队,明明集群资源还有余量
凌晨 2 点整有一大批作业同时启动,控制台上看到作业在等待 Agent 执行,但服务器 CPU 和内存都不高。这个问题常见于配置初期没有做运行时间平滑。
原因有两个角度。一是所有作业都配了完全一致的开始时间,Agent 端线程池被短时占满,后面作业只能排队;二是作业中设置了资源锁,资源名相同但最大并发设置过小,导致不相关的作业互相锁死。
解决方式是在日历或作业流级别做错峰:同一台机器上运行时间建议错开 5 到 15 分钟,尤其是高压力任务。资源锁的属性键可以用业务线做隔离,不要一个大资源名管住整个集群。如果 Agent 数量充足,还要检查单台 Agent 的并发线程数配置,别让 Agent 成为隐形瓶颈。
6. 把作业配置当代码治理:模板、导出、回滚和一点使用习惯
6.1 配置模板与基线:比“记住按钮”可靠
作业配置多了以后,最大的问题不是某一个作业怎么配,而是所有作业是不是按同一套规范配。如果十个作业用十种写法,后续排障和交接都会很难。
建议把作业配置模板化。日常操作时,不要每次都从空白作业开始建,而是复制一个“标准作业模板”,再修改作业名、命令、日历和通知。复制模板这个动作比新建更安全,因为模板里已经预设好运行用户、超时时间、日志策略这些容易漏的字段。
每周或每次发布前,可以导出作业定义做一个配置基线。比对工具不一定要多高级,只要能看出“哪个作业的时间被改了、哪个日历被换掉了”就行。这样做的好处是,作业被谁改了配置,什么时候改的,一目了然。不要相信某个人说“我只改了一个参数”,配置基线会替你记住。
6.2 回滚与重跑的基本动作
回滚不是把作业定义改回旧版本就结束,生产上要按三步走。
第一步是暂停后续作业。发现当前作业流异常时,先暂停下游作业,避免错误数据继续向后扩散,这一步比改配置更重要。第二步是修正参数或脚本,用导出的旧配置对照差异,确认是日历问题、参数问题还是依赖问题。第三步是从断点重跑。重跑时不要动已经成功的作业,只重跑失败及后续未完成的作业。
我现在配置任何作业时,都会刻意检查四件事:作业名是否能看出业务和周期,命令是否带绝对路径和明确参数,日历是否引用共享日历而不是写死日期,重跑策略是否与应用幂等性一致。这四件事看起来琐碎,但几乎能规避百分之八十的批处理故障。
这一行做得久了,你会发现 Control-M 配置并不玄,真正让人头疼的永远是那些“当时以为没问题”的细节。把每个作业当成长期维护的代码来写,给后人留清楚字段关系和日期处理逻辑,回报远超当时的几分钟操作,希望帮到你。
本文还有配套的精品资源,点击获取