首页 / 资讯中心 / 文章详情

Azkaban任务调度实战:架构原理、配置调优与避坑指南

Azkaban任务调度实战:架构原理、配置调优与避坑指南 ★ FEATURED ARTICLE
1. 为什么我们最终选择了Azkaban做任务调度刚接手数据平台那会儿团队里为了选一个任务调度工具前后折腾了将近一个月。Airflow、XXL-JOB、Azkaban都搭了测试环境跑过一轮最后还是把Azkaban留了下来。原因不复杂——我们的场景是典型的大数据批处理链路每天凌晨要跑几百个Hive SQL、Spark任务和Shell脚本任务之间有明确的依赖关系但对实时性和动态DAG的要求没那么高。Azkaban在这种“依赖固定、规模中等、运维人手有限”的场景下性价比是最高的。Azkaban本质上是一个批处理工作流调度器由LinkedIn开源核心能力是把多个任务按照依赖关系组织成一个Flow然后按计划触发执行。它解决的核心问题是当你有几十上百个脚本需要按顺序、按条件、按时间跑起来的时候靠crontab已经管不住了——你不知道哪个任务失败了、哪个任务还在等上游、失败了怎么重跑。Azkaban把这些东西可视化、可配置化让调度这件事从“人肉运维”变成“平台管理”。这篇文章适合谁看如果你正在做数据仓库、ETL、报表生成这类需要批量调度的工作或者你已经在用Azkaban但总觉得用得不够顺手那接下来的内容应该对你有用。我会从架构原理讲到实操配置再到踩过的坑尽量把每个环节说透。2. Azkaban的核心架构与运行原理拆解2.1 三大组件到底各干什么活Azkaban的架构不复杂就三个核心组件但很多人用了一年也没搞清楚它们之间的边界。AzkabanWebServer是整个系统的大脑。它负责项目管理、权限控制、任务触发、执行状态展示。你在Web界面上看到的所有操作——创建项目、上传Flow、设置定时、查看日志——都是它在处理。它本身不执行任务只负责“指挥”。AzkabanExecutorServer是真正干活的。它接收WebServer分配过来的Flow解析依赖关系逐个执行Job。每个Executor可以跑多个Flow但同一时间能跑多少取决于你配置的并发参数。Executor挂了正在跑的任务就断了这是生产环境必须做高可用的原因。MySQL数据库是状态存储中心。项目元数据、Flow定义、执行历史、调度计划全存在这里。WebServer和Executor都通过MySQL来同步状态。数据库一旦出问题整个调度系统就瘫了所以生产环境一定要给MySQL做备份和主从。三者之间的关系可以用一个生活场景类比WebServer是餐厅前台负责接单和告诉客人菜好了没Executor是后厨真正炒菜的地方MySQL是订单本前台和后厨都靠它来对账。前台不炒菜后厨不接客订单本丢了就全乱套。2.2 Flow与Job的依赖模型Azkaban的调度模型是基于属性文件的DAG。每个Job是一个.job文件里面用dependenciesjobA,jobB来声明依赖。多个Job组成一个FlowFlow通过azkaban-flow的YAML格式或者传统的.job.flow文件来定义。这里有个关键点Azkaban的依赖是强依赖。上游Job失败下游Job默认不会执行整个Flow会被标记为失败。这跟某些调度工具的“弱依赖”不同——弱依赖下上游失败下游照样跑。强依赖的好处是数据链路不会出现脏数据坏处是一个小任务挂了整条链路都得重跑。我们后来通过配置failure.emails和job.notification来做告警但依赖模型本身没有改。Flow的执行顺序由拓扑排序决定。Azkaban在提交Flow时会先做一次DAG校验如果发现循环依赖会直接拒绝提交。这个校验很有必要我见过有人手写.job文件时把A依赖B、B依赖A写进去了结果提交时报错排查了半天。2.3 调度触发方式与执行策略Azkaban支持三种触发方式手动触发、定时调度、API触发。手动触发就是点按钮适合调试定时调度用cron表达式适合日常批处理API触发适合跟外部系统集成比如上游数据到了之后通过接口调起Flow。执行策略上Azkaban提供了几种重要的配置项。flow.num.job.threads控制Flow内Job的并发度默认是10。如果你的Flow里有大量无依赖的Job可以调大这个值来加速。azkaban.max.running.flows控制单个Executor上同时运行的Flow数量这个值要根据机器配置来定设太大容易把内存跑满。还有一个容易被忽略的配置是job.max.retries和job.retry.backoff。前者控制失败重试次数后者控制重试间隔。对于网络抖动导致的偶发失败配个2次重试能省很多事。但要注意重试是针对单个Job的不是整个Flow。3. 从零搭建Azkaban的关键配置与实操要点3.1 环境准备与数据库初始化Azkaban对环境的依赖不多JDK 1.8以上、MySQL 5.7以上就能跑。但有几个细节必须注意。MySQL的字符集要设成utf8mb4否则中文项目名和Job名会乱码。建库的时候直接指定CREATE DATABASE azkaban DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;然后要创建Azkaban专用的数据库用户不要直接用root。Azkaban的建表脚本在源码包的azkaban-db目录下执行create-all-sql-*.sql就行。这个脚本会创建大约20张表核心的是projects、flows、execution_flows、execution_jobs、triggers这几张。注意建表脚本执行前一定要确认MySQL的max_allowed_packet参数不小于16M否则执行过程中可能因为包太大而中断。JDK版本方面Azkaban 3.x对JDK 11的支持不完善建议用JDK 8。如果服务器上已经装了高版本JDK可以用update-alternatives来切换不要直接卸载系统自带的JDK容易影响其他服务。3.2 WebServer与Executor的配置分离Azkaban的配置文件分两块azkaban.properties是WebServer用的azkaban-executor.properties是Executor用的。很多人图省事把两个配成一样的结果踩坑。WebServer的配置重点在数据库连接和会话管理database.typemysql mysql.port3306 mysql.hostyour-db-host mysql.databaseazkaban mysql.userazkaban_user mysql.passwordyour_password mysql.numconnections100 session.timeout.ms7200000session.timeout.ms默认是半小时对于需要长时间查看日志的场景太短了建议调到2小时。Executor的配置重点在执行目录和并发控制executor.port12321 azkaban.flow.threads30 executor.global.properties/path/to/global.propertiesexecutor.global.properties这个文件很关键它定义了所有Job共享的环境变量比如HADOOP_HOME、HIVE_HOME、PATH等。把公共环境变量放这里就不用每个Job都写一遍。实操心得Executor的执行目录executor.dir要放在空间充足的磁盘上因为每次执行都会在本地生成临时文件和日志。我们之前放在系统盘跑了两个月磁盘就满了后来迁到数据盘才解决。3.3 项目上传与Flow定义的两种写法Azkaban支持两种Flow定义方式传统的.job.flow文件和新的YAML格式。老版本只支持前者3.0之后推荐用YAML。传统写法下每个Job一个.job文件内容类似typecommand commandsh /path/to/script.sh dependenciesjobA,jobB retries2 retry.backoff60000然后需要一个.flow文件或者项目名同名的.project文件来定义Flow。这种写法文件多管理起来麻烦但兼容性好。YAML写法把所有Job定义在一个文件里nodes: - name: extract_data type: command config: command: sh /path/to/extract.sh - name: transform_data type: command config: command: sh /path/to/transform.sh dependsOn: - extract_dataYAML的好处是一目了然改依赖不用翻多个文件。但要注意缩进必须用空格不能用Tab否则解析会报错。我见过有人从网页复制YAML配置缩进混了Tab和空格排查了半小时才发现。3.4 定时调度的cron配置与时区问题Azkaban的定时调度用Quartz的cron表达式格式是秒 分 时 日 月 周 年比Linux的cron多了秒和年两个字段。一个每天凌晨2点执行的配置0 0 2 * * ? *这里?表示不指定值用在“日”和“周”字段上避免冲突。*表示所有值。最后的*是年份一般不用管。时区问题是个大坑。Azkaban默认用JVM的时区如果服务器时区是UTC你配的凌晨2点实际是北京时间上午10点。解决办法是在启动脚本里加-Duser.timezoneAsia/Shanghai或者在azkaban.properties里配azkaban.server.timezoneAsia/Shanghai。注意修改时区配置后需要重启WebServer才生效而且已经创建的调度计划不会自动调整需要重新编辑保存一次。4. 生产环境中的实战操作与性能调优4.1 大规模Flow的拆分策略当Flow里的Job超过50个时Azkaban的Web界面会变得很卡执行时的状态刷新也会变慢。我们的做法是按业务域拆分Flow而不是把所有Job塞进一个大Flow。比如一个电商数据仓库的日常调度可以拆成订单域Flow、商品域Flow、用户域Flow、报表汇总Flow。域内Job保持强依赖域之间通过“哨兵Job”来串联——上游Flow的最后一个Job成功后触发下游Flow的哨兵Job。这种拆分方式的好处是单个Flow失败不影响其他域重跑范围小Web界面响应快Executor的负载更均衡。坏处是跨Flow的依赖关系不如单Flow内直观需要额外维护一张依赖关系表。4.2 Executor的并发参数计算azkaban.flow.threads这个参数决定了单个Executor能同时跑多少个Job。设太小任务排队设太大机器扛不住。怎么算假设每个Job平均消耗200MB内存Executor所在机器有32GB内存留给Azkaban的按24GB算那么理论上可以跑120个Job。但实际要考虑CPU和磁盘IO一般按内存算出来的值打个6折比较稳妥。所以azkaban.flow.threads设成70左右比较合适。另外azkaban.max.running.flows控制同时运行的Flow数量。这个值跟Flow的平均Job数有关。如果平均每个Flow有20个Jobflow.threads是70那么max.running.flows设成4-5比较合理再多就会频繁切换上下文。这些参数没有绝对的最优值需要根据实际监控数据来调。我们一般会观察Executor的CPU使用率和内存使用率如果长期低于60%可以适当调大如果经常超过85%就要调小。4.3 日志管理与磁盘清理Azkaban的日志默认存在Executor的executor.dir/logs目录下每次执行都会生成一个日志文件。如果不清理几个月就能把磁盘撑满。Azkaban本身提供了一个日志清理的配置azkaban.executor.log.retention.days30但这个配置只清理数据库里的执行记录不删本地日志文件。本地日志需要自己写脚本清理。我们用的是find命令配合crontabfind /path/to/executor/logs -name *.log -mtime 30 -delete实操心得清理日志前一定要确认这些日志没有被其他系统引用。我们有一次清理完发现监控系统取不到历史日志了后来改成先归档到对象存储再删除本地文件。4.4 失败重跑与断点续跑Azkaban的重跑有两种方式整体重跑和从失败节点重跑。整体重跑会重新执行所有Job包括已经成功的浪费时间。从失败节点重跑只执行失败Job及其下游效率高很多。在Web界面上Flow执行失败后会出现“Retry Failed”按钮点击后从失败节点开始重跑。但要注意如果失败Job的上游数据已经变了重跑可能会导致数据不一致。我们的做法是对于幂等的Job比如覆盖写Hive分区直接重跑对于非幂等的Job比如追加写先确认上游数据没变再重跑。还有一个技巧是用job.notification配置失败告警。Azkaban支持邮件通知但邮件容易被忽略。我们后来通过API把失败事件推送到内部告警群响应速度快了很多。5. 常见问题排查与避坑指南5.1 任务卡在“Running”状态不结束这是最常见的问题。原因通常有三种Job进程僵死、Executor与WebServer失联、数据库连接池耗尽。排查步骤先看Executor的日志确认Job进程是否还在然后用jstack看Executor的JVM线程状态最后检查数据库连接数是否达到上限。如果是Job进程僵死可以在Job配置里加超时时间job.timeout3600000单位是毫秒超过这个时间Job会被强制杀掉。但要注意强制杀进程可能导致数据写了一半所以超时时间要设得合理。5.2 Flow提交后一直排队不执行这种情况一般是Executor没有注册到WebServer或者Executor的并发已满。先检查Executor是否正常启动日志里有没有“Registered to WebServer”的字样。如果没有检查azkaban.properties里的azkaban.webserver.url配置是否正确。如果Executor已注册但任务排队看azkaban.max.running.flows是否已满。可以临时调大这个值来缓解但根本解决办法还是加Executor节点。5.3 中文乱码问题中文乱码通常出现在三个地方项目名、Job名、日志内容。项目名和Job名的乱码是数据库字符集问题建库时用utf8mb4就能解决。日志内容的乱码是JVM字符集问题在启动脚本里加-Dfile.encodingUTF-8。还有一个隐蔽的乱码场景Job脚本里输出的中文在Web界面显示正常但下载日志文件后打开是乱码。这是因为日志文件本身是UTF-8编码但本地编辑器默认用GBK打开。用支持编码切换的编辑器打开就行。5.4 依赖关系不生效明明配了dependencies但下游Job还是在上游之前跑了。这种情况一般是Job名拼写错误或者大小写不一致。Azkaban的Job名是大小写敏感的。jobA和joba是两个不同的Job。另外.job文件里的dependencies值不要加空格dependenciesjobA, jobB这种写法会导致解析失败正确写法是dependenciesjobA,jobB。避坑技巧提交Flow前在本地用Azkaban提供的校验工具跑一遍能提前发现大部分配置错误。校验工具在源码包的azkaban-common模块里。5.5 常见问题速查表问题现象可能原因排查方法解决方案任务卡RunningJob僵死/连接池满看Executor日志、jstack加超时配置、调大连接池Flow一直排队Executor未注册/并发满检查注册日志、看并发数修复注册配置、加节点中文乱码字符集不一致检查数据库和JVM编码统一用UTF-8依赖不生效Job名拼写错误核对Job名大小写修正配置、用校验工具日志文件过大未配置清理检查日志目录大小加清理脚本、配保留天数定时不触发时区不对/cron错误检查时区和cron表达式统一时区、校验cron6. 一些让Azkaban更好用的扩展思路Azkaban本身的功能不算丰富但它的API和插件机制留了不少扩展空间。我们后来做了几件事用起来顺手很多。自定义告警插件。Azkaban支持自定义JobFailureListener实现这个接口可以在Job失败时做任何事——发短信、调API、写数据库都行。我们实现了一个推送到内部告警群的Listener比邮件通知及时得多。执行历史归档。Azkaban的execution_flows表会一直增长几个月就上百万行查询变慢。我们写了个定时任务把三个月前的执行记录归档到另一张表主表只保留近期数据。参数化Flow。Azkaban支持在Flow执行时传入参数用${param}的方式在Job配置里引用。这个功能很适合做“同一套Flow跑不同业务线”的场景。比如一个数据同步Flow通过传入不同的source.table参数来同步不同的表。与内部平台的集成。Azkaban的API可以创建项目、上传Flow、触发执行、查询状态。我们把它集成到了内部的数据开发平台上开发人员在平台上配置好任务后一键发布到Azkaban不用手动上传文件。这些扩展都不复杂但能显著提升日常使用的效率。Azkaban的定位就是一个“够用”的调度器它不追求大而全把核心的依赖调度做扎实了剩下的交给使用者去扩展。对于中小规模的数据团队来说这种设计哲学反而比那些功能繁多但运维复杂的工具更实用。我在实际使用中最大的体会是调度工具没有绝对的好坏关键是匹配团队的实际场景。Azkaban的强依赖模型和简单的架构让它在“任务依赖固定、运维人手有限”的场景下特别稳。但如果你的场景需要动态DAG、需要秒级调度、需要复杂的条件分支那Azkaban可能就不太合适了。选型之前先想清楚自己的核心需求是什么比盲目追新更重要。
阅读完成 · 觉得有帮助?
咨询建站