Azkaban Solo Server 部署、.job 任务依赖与定时调度实战
发布时间:2026/11/2 11:28:16 锦皓数字建站

简介azkaban-solo-server-0.1.0-SNAPSHOT.tar.gz 是面向大数据工作流调度初学者、小型团队与个人开发者的一款即用型 Azkaban 单机部署包。它基于 LinkedIn 开源的 Azkaban 工作流执行引擎将 Web 服务、工作流执行器与内嵌数据库集成在同一台机器上省去源码编译环节适合搭建轻量级调度环境、验证作业依赖与定时执行流程。压缩包体积约 42.28MB包内文件总数及类型明细上游暂无有效数据因此无法进一步拆分统计。已有 260 人学习或下载。借助该包读者可完整体验项目创建、作业定义、依赖关系编排、定时调度以及 Web 界面监控与日志查看理解 Azkaban 的核心使用模型同时需注意 SNAPSHOT 版本属于开发中的不稳定构建更适合学习测试而非大规模生产。总体而言它是一份便于快速上手工作流调度的单机实践材料。1. 从 tar.gz 到能登录的调度台solo 包适合谁拿到 azkaban-solo-server-0.1.0-SNAPSHOT.tar.gz 的人多半不是要上一套企业级调度平台而是想在一台测试机上把 azkaban 任务调度先跑通一个进程、一个内嵌 H2、一个 8081 端口解压就能登录建 flow。它把 web server 和 executor 塞进同一个 JVM省掉了集群模式那套 MySQL 加执行器互联代价是没有高可用也没有多执行器分担。适合写批处理脚本的数据开发、要给几个 shell 任务补上依赖关系和失败重试的后端以及想先摸清 .job 语法再决定要不要迁集群的人。不适合要求秒级触发、要求重启不丢排期、要求多团队隔离的生产环境。文件名里的 SNAPSHOT 说明它是从主干源码打出来的快照包不是正式发行版功能齐但边界要自己试。2. 解压到 8081 能打开目录结构和启动参数怎么定2.1 先认清四个目录后面九成的怪问题都出在它们身上解压之后别急着敲启动脚本先看一眼目录构成。这个包的结构是典型的 solo 布局bin/放启停脚本conf/放azkaban.properties、azkaban-users.xml、global.properties和log4j.propertieslib/是全部依赖 jarH2、Jetty、Quartz 都在里面plugins/是 job 类型插件的落点。真正需要你关心的是另外几个运行期目录projects/存上传的项目包executions/按执行 ID 一級一级堆日志h2/是内嵌库的数据文件logs/是服务端自身日志。这几个目录首次启动才会生成所以刚解压看不到不等于缺文件。# 在解压目录内执行脚本默认依赖相对路径别在别的目录用绝对路径调它 cd azkaban-solo-server # 先确认启动脚本到底叫什么不同打包方式名字不一样 ls -l bin/ # 看一眼配置目录后面所有改动都在这几个文件里 ls -l conf/上面第一步的cd不是可选项。solo 的启动脚本内部用相对路径去拼 classpath 和azkaban.home你在上级目录敲bin/start-solo.sh大概率会得到一个类找不到或者配置读不到的错误日志里只有一行FileNotFoundException很容易被误判成包损坏。2.2 azkaban.properties 里先改这七项别一次全改配置文件是整篇文章里最值得慢慢改的地方。我的习惯是第一次启动只改必要项每改一轮就启动验证一次改坏了立刻能定位到是哪个属性。时区和端口是两个必须动的H2 路径和项目目录保持默认就行邮件整段注释掉——空值的mail.sender会让部分版本在初始化通知模块时直接抛异常这是最常见的“配置看着没写错但就是起不来”。# conf/azkaban.properties # 页面左上角显示的名字多套环境靠它区分别都叫 Local azkaban.namedev-scheduler azkaban.labeldev-solo # 时区必须显式写不写就跟随 JVM容器里通常是 UTC定时会差 8 小时 default.timezoneAsia/Shanghai # Web 端口solo 默认 8081和别的服务冲突就在这里改 jetty.port8081 jetty.maxThreads50 jetty.minThreads10 # 元数据库solo 用内嵌 H2路径相对启动目录 database.typeh2 h2.path./h2 h2.create.tablestrue # 项目包落盘位置与 job 的全局属性文件 azkaban.project.dirprojects executor.global.propertiesconf/global.properties # 用户来自这个 XML默认管理员账号在这里面配 user.manager.classazkaban.user.XmlUserManager user.manager.xml.fileconf/azkaban-users.xml # 不用邮件通知就整段注释不要留空值 # mail.sender # mail.hostjetty.maxThreads控制的是 web 层并发连接数不是任务并发数很多人把它当并发上限调大结果任务该排队还是排队。h2.create.tablestrue只对第一次启动有意义建完表之后保留也无害但如果你手工往h2/目录塞过旧数据文件这一项会让你以为表被重建了实际是连到了另一个库文件。user.manager.xml.file建议写相对路径写绝对路径在换机器部署时是最容易忘的一处。2.3 启动、验证、停机三条命令加两个必看日志启动脚本在不同打包里可能叫start-solo.sh也可能叫azkaban-solo-start.sh。第一次我都用前台方式跑日志直接刷在终端上出问题不用去翻文件。JVM 内存通过JAVA_OPTS传最小 512M 起跑十几个 command 类型的 job 够用有的版本脚本里把-Xmx写死了那就直接编辑脚本里的那一行变量。# 第一次前台启动方便直接看报错 export JAVA_OPTS-Xmx1024m -Xms512m -Dfile.encodingUTF-8 bash bin/start-solo.sh # 另开一个终端验证端口和首页 ss -lntp | grep 8081 curl -sI http://127.0.0.1:8081/index | head -n 1 # 看服务端日志文件名视打包而定logs 目录下就那一个主日志 tail -n 80 logs/*.log # 正常停机走脚本不要 kill -9 bash bin/shutdown-solo.shcurl返回 200 或者 302 都算正常302 通常是重定向到登录页。日志里如果出现Database may be already in use说明上一次进程没退干净别急着删.lock.db文件先用ps -ef | grep azkaban确认没有残留 JVM否则删了锁文件下次还会撞。默认管理员账号在conf/azkaban-users.xml里第一次登录后我一般会立刻加一个自己的账号并把默认密码换掉这个包是给测试用的但端口一旦对公网开放弱口令就是明牌。3. 用 .job 描述任务从单命令到多依赖 flow3.1 command 类型 job 的最小模板以及每个参数真正的含义Azkaban 的调度单位是 flowflow 由一个个.job文件拼出来。最常用的类型是command本质就是起一个子进程执行你给的命令。写 job 文件有一个必须建立的直觉命令的工作目录不是项目根目录而是本次执行对应的executions/exec_id/下的临时目录项目文件会被链接过去。所以脚本路径、配置文件路径都要用相对项目结构的方式写或者干脆在命令里先cd再执行。# extract.job # 任务类型solo 包自带 command 类型不需要额外插件 typecommand # 执行的命令${dt} 是后面 flow 里传进来的参数 commandsh ./bin/extract.sh ${dt} # 失败重试 2 次间隔 60 秒用来兜住下游数据库抖动这类瞬时故障 retries2 retry.backoff60000 # 只在最终失败时发通知避免重试过程中反复刷邮件 notify.on.first.errorfalse notify.on.last.errortrue # 通知收件人邮件服务没配好时这几行去掉否则任务跑完会卡在发信环节 failure.emailsopsexample.com success.emailsopsexample.comretries和retry.backoff是最值得先想清楚的两个参数。重试次数不是越多越好脚本本身如果会写文件、写库重试之前必须保证幂等否则第二次执行会把第一次的中间结果再叠加一遍。retry.backoff单位是毫秒我一般给 60000给 1000 那种几乎等于立即重试对下游没有任何缓解作用。notify.on.first.error关掉这一点是老经验一个任务重试三次失败开着的话你会收到三封标题一样的邮件真正需要你处理的那封反而被淹没。属性名在不同版本之间会有出入改完一定要真跑一次在executions/对应目录的日志里搜Unknown property之类的告警看到了就说明这个属性在当前包里不生效别以为写了就管用。3.2 dependencies 串流程zip 怎么打、flow 名从哪来多个 job 之间的顺序靠dependencies声明值是别的 job 文件名去掉后缀。比如load.job依赖transform而transform依赖extract三行配置就把一条 ETL 链路串起来了中间任何一个失败下游直接变成CANCELLED不会继续往下跑。# extract.job typecommand commandsh ./bin/extract.sh ${dt} # transform.job typecommand commandsh ./bin/transform.sh ${dt} dependenciesextract retries1 retry.backoff120000 # load.job typecommand commandsh ./bin/load.sh ${dt} dependenciestransform打包上传是新手最容易翻车的一步。上传的 zip 里必须包含全部的.job文件以及 job 里引用到的脚本目录bin/、conf/这些子目录要一起打进去否则上传成功、执行时找不到脚本。Linux 下zip -r打出来的包有时会丢失可执行位所以我的做法是脚本里一律用sh ./bin/xxx.sh调用而不是./bin/xxx.sh省掉一轮权限排查。# 项目根目录下执行把 job 文件和脚本一起打进去 zip -r my_etl_flow.zip *.job bin/ conf/ # 打完之后自查一遍清单确认没有漏目录 unzip -l my_etl_flow.zip上传之后不要马上排期先去 Flow 列表里确认 flow 名。Azkaban 会给每个 job 生成一个单节点 flow而带依赖的那一簇会以最末端、没有下游依赖的那个 job 名作为 flow 名所以上面三个文件上传后你看到的调度入口应该是load。这个规则不同版本表现略有差异与其记规则不如上传完亲眼看一眼点进去确认节点顺序是不是你想要的。要改 flow 名就改对应的.job文件名别在界面上找重命名按钮。3.3 用 .flow 文件把参数和失败策略写进版本库job 文件只能表达单节点配置flow 级的参数、并发、通知策略得靠.flow文件。支持 Flow 2.0 的包可以直接写这种结构化的描述把config提到 flow 层节点里只留差异部分参数改一处全局生效。这对需要按天跑、按小时跑的两套排期特别有用——同一份 job 逻辑用两个 flow 传不同的时间参数即可。# etl_daily.flow config: # flow 级参数所有节点都能用 ${} 引用 dt: 20240101 failure.emails: opsexample.com # 并发跑同一 flow 的多个实例时限制同时运行的数量 azkaban.flow.max.concurrent.executions: 1 nodes: - name: extract type: command config: command: sh ./bin/extract.sh ${dt} - name: transform type: command dependsOn: - extract config: command: sh ./bin/transform.sh ${dt}除了自定义参数Azkaban 还提供一批内置变量写日期分区任务时非常省事${azkaban.flow.start.year}、${azkaban.flow.start.month}、${azkaban.flow.start.day}会取到本次执行的计划时间而不是当前系统时间补历史数据时手动改参数也不会错位。这一点在补数场景里是刚需用date命令取当天日期在补历史时必然出错。azkaban.flow.max.concurrent.executions这类 flow 级并发限制在老版本里可能不认上传后在 flow 页面看有没有对应的可选项没有就用调度周期拉开时间差来规避。4. 定时、重试与资源边界参数怎么取值4.1 cron 表达式六位写法、问号规则和时区错位Azkaban 的定时用的是 Quartz 风格的 cron六位或七位分别是秒、分、时、日、月、周、年。第一位是秒这一点和 Linux crontab 完全不同直接从 crontab 抄过来会得到“每天 0 点 0 分 0 秒”以外的奇怪结果。另一个硬规则是“日”和“周”这两个字段不能同时用*必须有一个写成?写错了界面上会直接报表达式非法。# 每天凌晨 2 点整 0 0 2 * * ? # 周一到周五 1 点半周末不跑 0 30 1 ? * MON-FRI # 每小时的第 5 分钟跑一次 0 5 * * * ? # 每天 8 点到 20 点之间每 2 小时一次 0 0 8-20/2 * * ?时区是这套系统里最典型的“看起来正常、实际差 8 小时”。default.timezone不写就跟随 JVM容器基础镜像基本都是 UTC界面显示的时间和实际触发时间会错开 8 小时而你从 cron 表达式上完全看不出问题。判断方法很简单建一个每分钟触发、只执行date的探测 flow跑十分钟对比日志时间和本地时间不一致就是时区没配对。改完azkaban.properties必须重启服务这个属性不支持热加载。4.2 重试、超时和失败通知的实际配法Azkaban 的 command 类型没有统一的原生超时属性不同版本支持程度不一样。我一般不在调度层赌这个属性直接在脚本外面套一层timeout让操作系统负责掐死卡住的进程这样换任何调度系统都成立。# 带超时的写法3600 秒没结束就杀掉退出码 124 会被判定为失败 typecommand commandtimeout -s KILL 3600 sh ./bin/heavy_job.sh ${dt} retries0 retry.backoff0这里retries特意设成 0。耗时长的任务重试代价很大一旦是数据问题导致的失败重试只会把同一个错误再跑一遍还占着执行队列。我的经验是短任务、下游是数据库或接口的给 2 次重试长任务、涉及大批量文件读写的不重试靠告警让人介入。timeout -s KILL用 KILL 而不是默认的 TERM是因为有些脚本对 TERM 做了 trap 却卡在清理逻辑里反而不退出。失败通知要先把azkaban.properties里的mail.sender、mail.host、mail.port、mail.user、mail.password配全缺一项在部分版本上会让通知模块初始化失败。配好之后先用一个必然失败的 job 做一次验证确认收件箱能收到别等真出故障时才发现邮件从来没发出去过——这种黑匣子式的通知失败比没有通知更耽误事。4.3 历史数据和日志清理H2 膨胀与磁盘写满solo 把所有执行历史写进内嵌 H2把每个 job 的 stdout/stderr 写进executions/。这两个地方的增速远超预期一个每天跑 20 个节点、每节点输出几百行的 flow一个月下来executions/就能吃掉几个 GH2 文件也会跟着涨。测试机上最经典的事故就是某天早上发现所有任务都失败原因是磁盘满了。# 归档并清理 30 天前的执行目录先打包再删别直接 rm cd azkaban-solo-server tar -czf /data/backup/exec_$(date %Y%m%d).tar.gz \ $(find executions -maxdepth 1 -type d -name [0-9]* -mtime 30) find executions -maxdepth 1 -type d -name [0-9]* -mtime 30 -exec rm -rf {} # H2 压缩必须在服务停止后做运行中会撞文件锁 bash bin/shutdown-solo.sh java -cp lib/* org.h2.tools.Shell \ -url jdbc:h2:./h2/azkaban -sql SHUTDOWN COMPACT bash bin/start-solo.shfind那条命令里的-name [0-9]*是为了只匹配以数字命名的执行目录避免误删projects之类的同级目录这个过滤条件不加删库跑路就不是玩笑了。SHUTDOWN COMPACT会重写整个库文件几十兆的库几秒钟完成上 G 的库要等几分钟期间别 Ctrl-C。归档目录放在另一块盘或者远端存储上和executions/放一起等于没备份。5. 避坑与排查solo server 上最容易翻车的五件事5.1 启动阶段进程起来就退出现象敲完启动脚本终端刷几行就回到提示符端口没监听logs/里日志很短。原因九成是在错误的目录执行脚本脚本内部按相对路径拼azkaban.home和 classpath从上级目录调用就找不到conf/。剩下那一成是端口被占或者jobtype插件目录里放了不兼容的 jar 导致容器初始化失败。解决先pwd确认在解压目录内再ss -lntp | grep 8081查端口最后看日志末尾的Caused by。插件引起的问题把plugins/下新加的 jar 挪走再启动能起来就说明是插件版本不匹配。5.2 执行阶段任务卡在 PREPARING 不动现象flow 点执行之后状态一直停在 PREPARING 或者 QUEUED既不成功也不失败日志目录是空的。原因solo 模式下 web 和 executor 在同一个 JVM但 executor 仍然监听executor.portweb 端通过azkaban.webserver.url回调自己。这两个值一个改了一个没改或者azkaban.webserver.url里写了容器内的127.0.0.1而实际访问走的是映射端口就会出现“界面上能看到 flow、执行状态永远同步不回来”。解决把executor.port和azkaban.webserver.url一起对齐URL 用能被本机解析到的主机名加真实端口改完重启。再验证一次flow 状态应该几秒内从 PREPARING 走到 RUNNING。5.3 结果阶段状态 SUCCESS 但数据不对现象任务全绿但产出文件是空的或者脚本里的相对路径找不到文件。原因command 的工作目录是本次执行的临时目录不是项目根。写./bin/x.sh在部分版本能跑通是因为文件被链接过去了而脚本内部再引用../conf/xxx.conf这种跨级相对路径就会断。另一个常见原因是打包时漏了bin/目录zip 里只有.job文件。解决在执行命令前面加一句pwd; ls -l打印现场看一眼工作目录到底在哪脚本内部的路径一律基于脚本自身位置推导而不是基于调用者的当前目录。打包后用unzip -l对一遍文件清单。5.4 调度阶段定时任务不触发或者晚 8 小时现象flow 手动执行正常排期之后到点没动静或者实际触发时间和预期差整 8 小时。原因default.timezone没配、配了没重启、或者 cron 表达式里的“日/周”两个字段同时写了*被静默丢弃。还有一种情况是排期建在了错误的 flow 上——上传新版本后 flow 名变了旧排期还挂着已经不存在的 flow。解决用每分钟执行date的探测 flow 校准时区确认服务重启生效cron 表达式统一按“日写具体值、周写?”的习惯写每次上传新版本后回排期列表核对 flow 名是否还存在失效的排期直接删掉重建别留着。5.5 重启阶段库文件被锁或历史丢失现象重启时报Database may be already in use或者重启后之前的执行记录、排期全没了。原因前者是上一个 JVM 没退干净kill -9之后锁文件还留在h2/目录里后者是 solo 的调度信息默认走内存存储重启后已建好的定时排期往往需要重新确认一遍。解决停机一律走shutdown-solo.sh起来之前用ps -ef | grep azkaban确认没有残留进程再决定要不要动锁文件。排期这块我的做法是把 cron 表达式和 flow 名一起记在项目的 README 里重启后照着重建比在界面上回忆可靠得多。6. 进阶把 job 目录当代码管迁集群前先跑一遍自检solo 最适合的用法不是长期当生产调度器而是当“调度逻辑的开发环境”。等 flow 稳定了再迁到多执行器集群这时候真正会咬人的不是语法而是那些只在单机上成立的假设脚本里写死的绝对路径、依赖本机 crontab 的兜底、把临时文件丢在/tmp下靠机器不重启来保证可用。所以我的习惯是把.job和.flow文件放进 git 仓库提交前跑一遍静态校验把“依赖写错名字”“command 为空”这类低级错误挡在上传之前。#!/usr/bin/env bash # check_jobs.sh上传前自检依赖是否能对上、必填项是否缺失 set -euo pipefail fail0 for f in *.job; do type$(grep -E ^type $f | cut -d -f2- || true) cmd$(grep -E ^command $f | cut -d -f2- || true) [ -z ${type:-} ] { echo [缺 type] $f; fail1; } [ -z ${cmd:-} ] { echo [缺 command] $f; fail1; } # dependencies 是逗号分隔的 job 名逐个确认对应文件存在 deps$(grep -E ^dependencies $f | cut -d -f2- || true) for dep in ${deps//,/ }; do [ -f ${dep}.job ] || { echo [依赖缺失] $f - $dep; fail1; } done done echo check done, fail$fail exit $fail这个脚本只做三件事但拦住的问题占了上传失败原因的一大半。deps//,/是 bash 的参数替换把逗号换成空格这样for就能逐个遍历|| true是为了在grep没匹配到时不让set -e直接把脚本干掉否则第一个没有dependencies的 job 就会中断整个检查。退出码直接用fail接进 CI 或者 git hook 都很自然。迁集群之前除了脚本校验还有几项值得逐条过一遍检查项solo 上的常见写法迁集群前应改成脚本路径依赖执行目录下的相对链接基于脚本自身位置推导或用项目内相对路径临时文件写/tmp或固定目录名带执行 ID 的独立目录任务结束自行清理时间参数脚本里date取当前时间${azkaban.flow.start.*}取计划时间失败兜底靠本机 crontab 再跑一次交给调度层的 retries脚本保持幂等见过最多的翻车是第二条单机上/tmp/work只有一个任务在用迁到集群后同一台执行器上并发跑两个实例两个进程往同一个目录里写产出互相覆盖排查时日志全是成功的只有数据对不上。这类问题在 solo 阶段几乎不会暴露因为默认并发低、机器就一台。从那以后我每次往调度里加新 flow都强制走一遍“脚本校验 一分钟探测 flow 验证时区 手动执行一次看工作目录”这三步哪怕只是改了一个参数。这套动作五分钟比事后对着一条绿色的执行记录猜数据为什么不对要划算得多。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。