Hadoop学习教程,从入门到精通,Azkaban工作流管理器 — 知识点详解与案例代码(12)

Azkaban工作流管理器 --- 知识点详解与案例代码


一、工作流管理器概述

1.1 什么是工作流管理器

工作流管理器(Workflow Manager)是一种用于定义、调度、管理和监控一系列任务(Job)按照预设顺序和依赖关系自动执行的系统工具。

核心功能:

  • 任务调度:按时间或事件触发任务执行
  • 依赖管理:定义任务之间的先后依赖关系
  • 失败重试:任务失败时自动重试或告警
  • 可视化监控:提供Web UI查看任务执行状态

常见的工作流管理器对比:

特性 Azkaban Oozie Airflow
易用性 简单易用 配置复杂 需编程基础
依赖管理 .job文件定义 XML定义 Python代码定义
Web UI 友好 一般 良好
语言 Java Java Python

二、Azkaban概述

2.1 Azkaban简介

Azkaban是由LinkedIn 公司开源的一个批量工作流任务调度器,用于在一个工作流内以特定顺序运行一组任务和流程。

2.2 Azkaban的架构

Azkaban由三个核心组件构成:

复制代码
┌─────────────────────────────────────────────┐
│                 Azkaban 架构                  │
├─────────────────────────────────────────────┤
│                                             │
│  ┌──────────────┐    ┌──────────────────┐   │
│  │  AzkabanWeb   │───>│  AzkabanExecutor │   │
│  │  Server       │    │  Server          │   │
│  │  (Web管理端)   │    │  (任务执行端)     │   │
│  └──────────────┘    └──────────────────┘   │
│         │                    │               │
│         └────────┬───────────┘               │
│                  │                           │
│          ┌───────▼───────┐                   │
│          │    MySQL      │                   │
│          │   (元数据库)   │                   │
│          └───────────────┘                   │
└─────────────────────────────────────────────┘
  • AzkabanWebServer:提供Web UI界面,负责项目管理、权限认证、任务调度触发
  • AzkabanExecutorServer:负责实际执行工作流中的各个任务
  • MySQL:存储项目代码、任务执行状态、调度计划等元数据

2.3 Azkaban的三种部署模式

模式 特点 适用场景
solo-server mode Web Server和Executor Server运行在同一个进程中,使用内嵌的H2数据库 开发测试、小规模使用
two-server mode Web Server和Executor Server分离,使用MySQL数据库 生产环境(主备架构)
multiple-executor mode 多个Executor Server,Web Server通过MySQL协调调度 大规模生产环境

三、安装与配置Azkaban

3.1 安装前准备

环境要求:

  • JDK 1.8+
  • MySQL 5.7+
  • 合适的Azkaban安装包
bash 复制代码
# ============ 检查Java环境 ============
# 检查Java是否安装及版本,Azkaban要求JDK 1.8及以上
java -version

# ============ 检查MySQL环境 ============
# 检查MySQL是否正常运行
systemctl status mysqld

# 如果MySQL未启动,则启动MySQL服务
systemctl start mysqld

# 设置MySQL开机自启动
systemctl enable mysqld

3.2 获取Azkaban安装包

bash 复制代码
# ============ 下载Azkaban安装包 ============
# 进入软件安装目录
cd /opt/software/

# 使用wget下载Azkaban源码包(以3.x版本为例)
# 也可以从GitHub Release页面下载编译好的二进制包
wget https://github.com/azkaban/azkaban/archive/refs/tags/3.90.0.tar.gz

# 解压源码包到指定目录
tar -zxvf 3.90.0.tar.gz -C /opt/module/

# 进入Azkaban源码目录
cd /opt/module/azkaban-3.90.0/

# 使用Gradle编译安装(跳过测试以加快编译速度)
# -x test 表示跳过测试用例
./gradlew build -x test

# 编译完成后,各组件的安装包位于以下路径:
# Web Server:   azkaban-web-server/build/distributions/
# Executor:     azkaban-exec-server/build/distributions/
# Solo Server:  azkaban-solo-server/build/distributions/

3.3 Solo Server模式部署(最简单)

bash 复制代码
# ============ 解压Solo Server ============
# Solo模式是最快的部署方式,适合学习和测试
# Web Server和Executor在同一进程中,使用内嵌H2数据库
cd /opt/software/

# 解压solo-server安装包
tar -zxvf azkaban-solo-server-3.90.0.tar.gz -cd /opt/module/

# 重命名目录方便管理
mv /opt/module/azkaban-solo-server-3.90.0 /opt/module/azkaban-solo

# 进入solo-server目录查看目录结构
cd /opt/module/azkaban-solo
ls -la
# bin/          启动脚本目录
# conf/         配置文件目录
# lib/          依赖jar包目录
# plugins/      插件目录
# web/          Web资源目录(HTML、CSS、JS等)
# extlib/       扩展jar包目录(用户自定义jar放这里)

3.4 配置Solo Server

bash 复制代码
# ============ 修改时区配置 ============
# 编辑conf/azkaban.properties文件
vi /opt/module/azkaban-solo/conf/azkaban.properties

# 修改以下关键配置项:
# 默认时区设置为亚洲/上海(中国时区)
default.timezone.id=Asia/Shanghai
properties 复制代码
# ============ azkaban.properties 完整配置说明 ============
# Azkaban的Web服务器配置文件

# ---------- 核心配置 ----------
# 项目名称,显示在Web UI标题栏
azkaban.name=Test
# 项目标签
azkaban.label=My Local Azkaban
# 默认时区(重要:必须设置为正确时区,否则定时调度时间不对)
default.timezone.id=Asia/Shanghai

# ---------- Web服务器配置 ----------
# Web服务器资源目录(包含HTML、CSS、JS等前端文件)
web.resource.dir=/opt/module/azkaban-solo/web/
# 默认用户为匿名用户(solo模式下不需要认证)
user.manager.class=azkaban.user.XmlUserManager
# 用户配置文件路径
user.manager.xml.file=/opt/module/azkaban-solo/conf/azkaban-users.xml

# ---------- 项目存储配置 ----------
# 项目存储类型为H2数据库(solo模式专用)
database.type=h2
h2.path=./h2
h2.create.tables=true

# ---------- Jetty服务器配置 ----------
# Web服务器监听的端口号
jetty.port=8081
# SSL加密配置(默认关闭,生产环境建议开启)
jetty.use.ssl=false
# SSL密钥库文件路径
jetty.keystore=
# SSL密钥库密码
jetty.password=
# SSL信任库文件路径
jetty.truststore=
# SSL信任库密码
jetty.trustpassword=

# ---------- Executor配置 ----------
# Executor端口号
executor.port=12321

# ---------- 邮件通知配置 ----------
# 邮件发送服务器
mail.sender=
# 邮箱SMTP服务器地址
mail.host=
# 邮箱用户名
mail.user=
# 邮箱密码
mail.password=
# 任务失败通知收件人列表
job.failure.email=
# 任务成功通知收件人列表
job.success.email=
bash 复制代码
# ============ 修改用户配置文件 ============
# 编辑用户配置文件,可以添加自定义用户
vi /opt/module/azkaban-solo/conf/azkaban-users.xml
xml 复制代码
<!-- azkaban-users.xml 用户配置文件详解 -->
<!-- xmlns:xsi: XML Schema实例命名空间 -->
<!-- xsi:noNamespaceSchemaLocation: XML Schema文件位置 -->
<azkaban-users>
  <!-- 
    用户定义:
    username - 用户登录名
    password - 用户登录密码
    roles    - 用户角色(用逗号分隔多个角色)
    groups   - 用户所属组(用逗号分隔多个组)
  -->
  <user username="admin" password="admin" roles="admin" groups="azkaban" />
  <user username="metrics" password="metrics" roles="metrics"/>
  
  <!-- 
    角色定义:
    name  - 角色名称
    permissions - 角色拥有的权限列表
  -->
  <role name="admin" permissions="ADMIN" />
  <role name="metrics" permissions="METRICS"/>
</azkaban-users>

3.5 启动Solo Server

bash 复制代码
# ============ 启动Azkaban Solo Server ============
# 进入Azkaban Solo目录
cd /opt/module/azkaban-solo/

# 执行启动脚本
# start-solo.sh 会启动内嵌的Web Server和Executor Server
bin/start-solo.sh

# 检查启动是否成功(查看日志文件)
# 日志文件在当前目录下的logs文件夹中
tail -f /opt/module/azkaban-solo/logs/azkaban-web-server.log

# 查看端口是否被监听(默认8081端口)
netstat -tlnp | grep 8081

# 访问Web UI
# 在浏览器中输入:http://<服务器IP>:8081
# 使用上面配置的用户名/密码登录:admin/admin
bash 复制代码
# ============ 停止Azkaban Solo Server ============
# 执行停止脚本
bin/shutdown-solo.sh

# 确认进程已停止
# 查找Azkaban相关的Java进程
jps | grep Azkaban

3.6 Two Server模式部署(生产环境)

bash 复制代码
# ============ 第一步:创建MySQL数据库和用户 ============
# 登录MySQL
mysql -u root -p

# 在MySQL中执行以下SQL语句:
sql 复制代码
-- ============ 创建Azkaban数据库 ============
-- 创建名为azkaban的数据库,使用UTF-8字符集(支持中文)
CREATE DATABASE IF NOT EXISTS azkaban
DEFAULT CHARACTER SET utf8
DEFAULT COLLATE utf8_general_ci;

-- ============ 创建Azkaban专用数据库用户 ============
-- 创建用户名为azkaban,密码为azkaban123的用户
-- 'azkaban'@'%' 表示允许从任何主机连接
CREATE USER 'azkaban'@'%' IDENTIFIED BY 'azkaban123';

-- 授予azkaban用户对azkaban数据库的所有权限
GRANT ALL PRIVILEGES ON azkaban.* TO 'azkaban'@'%';

-- 刷新权限,使授权立即生效
FLUSH PRIVILEGES;

-- 切换到azkaban数据库
USE azkaban;

-- ============ 导入Azkaban初始化SQL脚本 ============
-- 执行建表脚本(该脚本在Azkaban安装包的sql目录下)
-- source /opt/module/azkaban-db-3.90.0/create-all-sql-3.90.0.sql;

-- 查看创建的表
SHOW TABLES;
bash 复制代码
# ============ 第二步:配置Azkaban Web Server ============
# 解压Web Server安装包
cd /opt/software/
tar -zxvf azkaban-web-server-3.90.0.tar.gz -C /opt/module/
mv /opt/module/azkaban-web-server-3.90.0 /opt/module/azkaban-web

# 将MySQL驱动jar包拷贝到extlib目录
# 需要下载mysql-connector-java-5.1.x.jar
cp mysql-connector-java-5.1.49.jar /opt/module/azkaban-web/extlib/

# 编辑Web Server配置文件
vi /opt/module/azkaban-web/conf/azkaban.properties
properties 复制代码
# ============ Two Server模式 azkaban.properties 配置 ============

# ---------- 项目基本配置 ----------
azkaban.name=Production
azkaban.label=My Production Azkaban
default.timezone.id=Asia/Shanghai

# ---------- Web资源目录 ----------
web.resource.dir=/opt/module/azkaban-web/web/

# ---------- 用户认证配置 ----------
user.manager.class=azkaban.user.XmlUserManager
user.manager.xml.file=/opt/module/azkaban-web/conf/azkaban-users.xml

# ---------- MySQL数据库连接配置(关键!) ----------
# 数据库类型为MySQL
database.type=mysql
# MySQL驱动类名
mysql.port=3306
# MySQL服务器主机名
mysql.host=node01
# MySQL数据库名
mysql.database=azkaban
# MySQL用户名
mysql.user=azkaban
# MySQL密码
mysql.password=azkaban123
# 最大连接数
mysql.numconnections=100

# ---------- Jetty服务器配置 ----------
jetty.port=8081
jetty.use.ssl=false
jetty.keystore=/opt/module/azkaban-web/keystore
jetty.password=password
jetty.keypassword=password
jetty.truststore=/opt/module/azkaban-web/truststore
jetty.trustpassword=password

# ---------- Executor配置 ----------
# 注:Two Server模式下,executor的host和port需要配置
executor.host=node01
executor.port=12321

# ---------- 邮件配置 ----------
mail.sender=azkaban@example.com
mail.host=smtp.example.com
mail.user=azkaban@example.com
mail.password=***
job.failure.email=admin@example.com
job.success.email=admin@example.com

# ---------- 多Executor模式配置(如需) ----------
# azkaban.use.multiple.executors=true
# azkaban.executorselector.filters=StaticRemainingFlowSize,MinimumFreeMemory,CpuStatus
# azkaban.executorselector.comparator.NumberOfAssignedFlowComparator=1
bash 复制代码
# ============ 第三步:配置Azkaban Executor Server ============
# 解压Executor Server安装包
cd /opt/software/
tar -zxvf azkaban-exec-server-3.90.0.tar.gz -C /opt/module/
mv /opt/module/azkaban-exec-server-3.90.0 /opt/module/azkaban-exec

# 将MySQL驱动jar包拷贝到extlib目录
cp mysql-connector-java-5.1.49.jar /opt/module/azkaban-exec/extlib/

# 编辑Executor配置文件
vi /opt/module/azkaban-exec/conf/azkaban.properties
properties 复制代码
# ============ Executor Server配置文件 ============

# ---------- 基本配置 ----------
default.timezone.id=Asia/Shanghai

# ---------- 数据库配置(与Web Server一致) ----------
database.type=mysql
mysql.port=3306
mysql.host=node01
mysql.database=azkaban
mysql.user=azkaban
mysql.password=azkaban123
mysql.numconnections=100

# ---------- Executor配置 ----------
# Executor Server监听的端口号
executor.port=12321

# ---------- 邮件配置 ----------
mail.sender=azkaban@example.com
mail.host=smtp.example.com
mail.user=azkaban@example.com
mail.password=***
job.failure.email=admin@example.com
job.success.email=admin@example.com
bash 复制代码
# ============ 第四步:启动Two Server模式 ============

# ---- 1. 先启动Executor Server(重要:必须先启动Executor) ----
cd /opt/module/azkaban-exec/
bin/start-exec.sh

# 激活Executor(Azkaban 3.x需要手动激活)
# 向本机Executor发送激活请求
curl -G "localhost:12321/executor?action=activate" && echo

# ---- 2. 再启动Web Server ----
cd /opt/module/azkaban-web/
bin/start-web.sh

# 验证启动成功
jps
# 应该看到:
# AzkabanExecutorServer
# AzkabanWebServer

# 查看端口
netstat -tlnp | grep 8081    # Web Server端口
netstat -tlnp | grep 12321   # Executor Server端口

# 浏览器访问:http://node01:8081
bash 复制代码
# ============ 停止Two Server模式 ============

# 先停Web Server,再停Executor Server
cd /opt/module/azkaban-web/
bin/shutdown-web.sh

cd /opt/module/azkaban-exec/
bin/shutdown-exec.sh

四、Azkaban的常用概念

4.1 核心概念详解

复制代码
┌──────────────────────────────────────────────────┐
│                Azkaban 核心概念                    │
├──────────────────────────────────────────────────┤
│                                                  │
│  Project(项目)                                  │
│    └── 一组相关的工作流集合                         │
│                                                  │
│  Flow(工作流)                                   │
│    └── 由多个有依赖关系的Job组成的DAG(有向无环图)    │
│                                                  │
│  Job(任务)                                      │
│    └── 工作流中的最小执行单元                       │
│                                                  │
│  .job 文件                                        │
│    └── 定义单个任务的属性文件                        │
│                                                  │
│  .flow 文件                                       │
│    └── 定义整个工作流的YAML格式文件                  │
│                                                  │
│  Schedule(调度)                                 │
│    └── 定时触发工作流执行的配置                      │
│                                                  │
│  Execution(执行)                                │
│    └── 工作流的一次具体运行实例                      │
│                                                  │
└──────────────────────────────────────────────────┘

4.2 Job文件语法详解

.job 文件是Azkaban中定义任务的核心文件,采用Java Properties格式 (key=value),每个.job文件定义一个任务。

properties 复制代码
# ============ Job文件完整语法示例 ============

# ---- 基本属性 ----
# type: 任务类型(必填),指定使用哪种执行器
# 常见类型:
#   command   - 执行Linux Shell命令
#   java      - 执行Java类
#   hadoop    - 执行Hadoop任务
#   hive      - 执行Hive脚本
#   noop      - 空操作(用于依赖关系的组织)
type=command

# ---- 依赖属性 ----
# dependencies: 定义当前任务依赖的其他任务
# 多个依赖用逗号分隔
# Azkaban会等待所有依赖任务执行成功后,才会执行当前任务
dependencies=job_a,job_b

# ---- 命令属性 ----
# command: 要执行的具体命令
# 可以是任何Linux Shell命令
command=echo "Hello Azkaban"

# ---- 重试属性 ----
# retries: 任务失败后重试的次数
retries=3

# ---- 重试间隔 ----
# retry.backoff: 两次重试之间的等待时间(毫秒)
retry.backoff=5000

# ---- 超时设置 ----
# job.timeout: 任务超时时间(毫秒),超时则任务失败
job.timeout=600000

# ---- 工作目录 ----
# working.dir: 任务执行的工作目录
working.dir=/opt/module/azkaban/jobs

# ---- 环境变量 ----
# env.property: 设置环境变量
env.HADOOP_HOME=/opt/module/hadoop
env.HIVE_HOME=/opt/module/hive

# ---- 内存限制 ----
# memory.limit: 限制任务使用的最大内存
memory.limit=2048M

# ---- 失败告警邮件 ----
# failure.emails: 任务失败时发送告警邮件的地址列表
failure.emails=admin@example.com

# ---- 成功通知邮件 ----
# success.emails: 任务成功时发送通知邮件的地址列表
success.emails=admin@example.com

# ---- 用户以谁的身份执行 ----
# user.to.proxy: 以哪个用户身份执行任务
user.to.proxy=azkaban

4.3 常用Job类型

properties 复制代码
# ============ 类型1:command(Shell命令) ============
# 最常用的类型,执行任意Shell命令
type=command
command=echo "当前日期: $(date)"
command.1=hdfs dfs -ls /user/hive/warehouse   # 多行命令用command.1, command.2...

# ============ 类型2:java(Java类) ============
# 执行Java程序,需要指定classpath和main方法
type=java
java.class=com.example.MyJobMainClass
classpath=lib/my-job.jar,lib/dependency.jar
main.args=arg1 arg2 arg3

# ============ 类型3:hive(Hive脚本) ============
# 执行HiveQL脚本文件
type=hive
hive.script=test.hql

# ============ 类型4:hadoopJava(Hadoop程序) ============
# 执行Hadoop MapReduce程序
type=hadoopJava
jobtype=hadoop
hadoop.in.=/input/path
hadoop.out.=/output/path
classpath=${hadoop.home}/etc/hadoop,lib/my-hadoop-job.jar
java.class=com.example.WordCount
main.args=${hadoop.in.} ${hadoop.out.}

五、案例演示一:依赖任务调度管理

5.1 案例描述

创建一个包含多个依赖关系的工作流,任务执行顺序如下:

复制代码
         ┌──────────┐
         │  job_a   │  (第一步:创建目录)
         └────┬─────┘
              │
         ┌────▼─────┐
         │  job_b   │  (第二步:生成数据文件)
         └────┬─────┘
              │
         ┌────▼─────┐
         │  job_c   │  (第三步:统计文件行数)
         └────┬─────┘
              │
         ┌────▼─────┐
         │  job_d   │  (第四步:压缩归档)
         └──────────┘

5.2 编写Job文件

文件:job_a.job

properties 复制代码
# ============ job_a.job - 创建工作目录 ============
# 任务类型:command(执行Shell命令)
type=command

# 执行的命令:
# 1. 创建 /tmp/azkaban_test 目录(-p 递归创建,如果存在不报错)
# 2. 输出提示信息
command=mkdir -p /tmp/azkaban_test && echo "任务A完成:目录创建成功"

文件:job_b.job

properties 复制代码
# ============ job_b.job - 生成测试数据 ============
# 任务类型:command
type=command

# 依赖关系:依赖job_a,即job_a执行成功后才会执行job_b
dependencies=job_a

# 执行的命令:
# 1. 使用seq生成1到1000的数字序列
# 2. 使用awk为每个数字加上前缀"Line_",格式为 "Line_1", "Line_2", ...
# 3. 将结果写入 /tmp/azkaban_test/data.txt 文件
# 4. 输出完成提示
command=seq 1 1000 | awk '{print "Line_" $0}' > /tmp/azkaban_test/data.txt && echo "任务B完成:数据文件已生成"

文件:job_c.job

properties 复制代码
# ============ job_c.job - 统计数据行数 ============
# 任务类型:command
type=command

# 依赖关系:依赖job_b
dependencies=job_b

# 执行的命令:
# 1. 使用wc -l 统计文件行数
# 2. 将统计结果追加到 /tmp/azkaban_test/result.txt
# 3. 输出完成提示
command=wc -l /tmp/azkaban_test/data.txt >> /tmp/azkaban_test/result.txt && echo "任务C完成:行数统计完成"

文件:job_d.job

properties 复制代码
# ============ job_d.job - 压缩归档 ============
# 任务类型:command
type=command

# 依赖关系:依赖job_c
dependencies=job_c

# 执行的命令:
# 1. 使用tar命令将测试目录打包压缩为gz文件
#    -c: 创建归档
#    -z: 使用gzip压缩
#    -v: 显示详细过程
#    -f: 指定归档文件名
# 2. 输出完成提示,并显示压缩后的文件信息
command=cd /tmp && tar -czvf azkaban_test.tar.gz azkaban_test/ && echo "任务D完成:文件已压缩归档" && ls -lh /tmp/azkaban_test.tar.gz

5.3 打包上传项目

bash 复制代码
# ============ 将Job文件打包为ZIP ============
# 进入存放job文件的目录
cd /home/hadoop/azkaban_projects/dependency_test/

# 确认目录中包含所有job文件
ls -la
# job_a.job
# job_b.job
# job_c.job
# job_d.job

# 将所有文件打包为ZIP格式(注意:Azkaban只接受ZIP格式上传)
# -r 递归压缩目录下的所有文件
zip -r dependency_test.zip *.job

# 确认ZIP文件内容
unzip -l dependency_test.zip

5.4 在Web UI中操作

bash 复制代码
# ============ Web UI操作步骤 ============
# 1. 打开浏览器,访问 http://node01:8081
# 2. 使用 admin/admin 登录
# 3. 点击 "Create Project" 创建项目
#    - Project Name: dependency_test
#    - Description: 依赖任务调度测试
# 4. 点击 "Upload" 上传 dependency_test.zip
# 5. 进入项目,可以看到DAG图
# 6. 点击 "Execute Flow" 执行工作流
# 7. 在 "Flow View" 中可以查看每个任务的执行状态
# 8. 绿色表示成功,红色表示失败,蓝色表示正在运行

六、案例演示二:MapReduce程序调度管理

6.1 案例描述

使用Azkaban调度Hadoop WordCount MapReduce程序,并对输出结果进行处理。

复制代码
         ┌────────────────┐
         │  prepare_hdfs  │ (创建HDFS输入目录)
         └───────┬────────┘
                 │
         ┌───────▼────────┐
         │  upload_data   │ (上传数据文件到HDFS)
         └───────┬────────┘
                 │
         ┌───────▼────────┐
         │ wordcount_mr   │ (执行WordCount MapReduce)
         └───────┬────────┘
                 │
         ┌───────▼────────┐
         │ check_result   │ (查看输出结果)
         └───────┬────────┘
                 │
         ┌───────▼────────┐
         │   clean_up     │ (清理临时文件)
         └────────────────┘

6.2 编写Job文件

文件:prepare_hdfs.job

properties 复制代码
# ============ prepare_hdfs.job - 准备HDFS目录 ============
# 任务类型:command
type=command

# 执行的命令:
# 1. 删除HDFS上可能存在的旧输入目录(-f 参数强制删除,不提示确认)
# 2. 创建新的输入目录 /wordcount/input
# 3. 输出提示信息
command=hdfs dfs -rm -f -r /wordcount/input && \
        hdfs dfs -mkdir -p /wordcount/input && \
        echo "HDFS输入目录已创建: /wordcount/input"

文件:upload_data.job

properties 复制代码
# ============ upload_data.job - 上传数据到HDFS ============
# 任务类型:command
type=command

# 依赖关系:依赖prepare_hdfs任务
dependencies=prepare_hdfs

# 执行的命令:
# 1. 创建本地临时测试数据文件
#    使用echo和重定向创建一个包含若干行文本的文件
# 2. 将本地文件上传到HDFS的输入目录
# 3. 查看HDFS上的文件内容,确认上传成功
command=echo "Hello World Hello Hadoop" > /tmp/wc_input.txt && \
        echo "Hello Azkaban World" >> /tmp/wc_input.txt && \
        echo "Hadoop is a framework" >> /tmp/wc_input.txt && \
        echo "Azkaban is a scheduler" >> /tmp/wc_input.txt && \
        echo "Hello Hadoop MapReduce" >> /tmp/wc_input.txt && \
        hdfs dfs -put /tmp/wc_input.txt /wordcount/input/ && \
        echo "数据已上传到HDFS" && \
        hdfs dfs -cat /wordcount/input/wc_input.txt

文件:wordcount_mr.job

properties 复制代码
# ============ wordcount_mr.job - 执行WordCount MapReduce ============
# 任务类型:hadoopJava
# hadoopJava类型专门用于执行Hadoop MapReduce程序
type=hadoopJava

# 依赖关系:依赖upload_data任务
dependencies=upload_data

# Java主类的全限定类名(包含包名)
# 这里使用Hadoop自带的WordCount示例类
java.class=org.apache.hadoop.examples.WordCount

# 传递给main方法的参数
# 参数1:HDFS输入路径
# 参数2:HDFS输出路径
main.args=/wordcount/input /wordcount/output

# Classpath配置(指定程序运行所需的jar包路径)
# ${hadoop.home} 引用环境变量HADOOP_HOME
# 多个路径用逗号分隔
classpath=${hadoop.home}/etc/hadoop,${hadoop.home}/share/hadoop/common/*,${hadoop.home}/share/hadoop/common/lib/*,${hadoop.home}/share/hadoop/hdfs/*,${hadoop.home}/share/hadoop/hdfs/lib/*,${hadoop.home}/share/hadoop/mapreduce/*,${hadoop.home}/share/hadoop/mapreduce/lib/*,${hadoop.home}/share/hadoop/yarn/*,${hadoop.home}/share/hadoop/yarn/lib/*

# 如果使用自己的WordCount jar包,配置如下:
# java.class=com.example.WordCount
# classpath=lib/hadoop-wordcount.jar

# 设置任务执行的用户代理
user.to.proxy=hadoop

# 设置Hadoop相关环境变量
env.HADOOP_HOME=/opt/module/hadoop
env.HADOOP_CONF_DIR=/opt/module/hadoop/etc/hadoop

# 失败重试次数
retries=2

# 重试间隔(毫秒)
retry.backoff=10000

# 任务超时时间(30分钟)
job.timeout=1800000

文件:check_result.job

properties 复制代码
# ============ check_result.job - 查看MapReduce输出结果 ============
# 任务类型:command
type=command

# 依赖关系:依赖wordcount_mr任务
dependencies=wordcount_mr

# 执行的命令:
# 1. 列出HDFS输出目录中的文件
# 2. 显示WordCount的输出结果(每个单词的计数)
# 3. 输出完成提示
command=echo "=== MapReduce输出文件列表 ===" && \
        hdfs dfs -ls /wordcount/output/ && \
        echo "" && \
        echo "=== WordCount结果 ===" && \
        hdfs dfs -cat /wordcount/output/part-r-00000

文件:clean_up.job

properties 复制代码
# ============ clean_up.job - 清理临时文件 ============
# 任务类型:command
type=command

# 依赖关系:依赖check_result任务
dependencies=check_result

# 执行的命令:
# 1. 删除HDFS上的输入和输出目录(清理测试数据)
# 2. 删除本地临时文件
# 3. 输出清理完成提示
command=hdfs dfs -rm -f -r /wordcount/input && \
        hdfs dfs -rm -f -r /wordcount/output && \
        rm -f /tmp/wc_input.txt && \
        echo "清理完成:所有临时文件已删除"

6.3 自定义MapReduce程序调度

java 复制代码
/**
 * ============ 自定义WordCount.java ============
 * 这是自定义的WordCount MapReduce程序
 * 用于演示如何在Azkaban中调度自定义的MR程序
 */

// 包声明
package com.example;

// 导入Hadoop相关类
import java.io.IOException;                    // IO异常类
import java.util.StringTokenizer;              // 字符串分词器

import org.apache.hadoop.conf.Configuration;   // Hadoop配置类
import org.apache.hadoop.fs.Path;              // HDFS路径类
import org.apache.hadoop.io.IntWritable;        // Hadoop整数可序列化类型
import org.apache.hadoop.io.LongWritable;       // Hadoop长整数可序列化类型
import org.apache.hadoop.io.Text;              // Hadoop字符串可序列化类型
import org.apache.hadoop.mapreduce.Job;        // MapReduce作业类
import org.apache.hadoop.mapreduce.Mapper;      // Mapper基类
import org.apache.hadoop.mapreduce.Reducer;     // Reducer基类
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;   // 输入格式类
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; // 输出格式类

/**
 * WordCount 主类
 * 实现经典的词频统计功能
 */
public class WordCount {

    /**
     * Map类 - 负责将输入文本拆分为单词
     * 泛型参数说明:
     *   KEYIN   - LongWritable  : 输入key类型(行偏移量)
     *   VALUEIN - Text          : 输入value类型(一行文本)
     *   KEYOUT  - Text          : 输出key类型(单词)
     *   VALUEOUT- IntWritable    : 输出value类型(计数1)
     */
    public static class WordCountMapper 
            extends Mapper<LongWritable, Text, Text, IntWritable> {
        
        // 创建输出value对象,值为1(每出现一次计数1)
        // 使用类成员变量而非局部变量,避免频繁创建对象,提高性能
        private final static IntWritable one = new IntWritable(1);
        
        // 创建输出key对象(单词)
        private Text word = new Text();

        /**
         * map方法 - 对每行文本进行分词处理
         * @param key     行偏移量(本案例中未使用)
         * @param value   一行文本内容
         * @param context 上下文对象,用于输出中间结果
         */
        @Override
        protected void map(LongWritable key, Text value, Context context)
                throws IOException, InterruptedException {
            // 将Text类型转换为String
            String line = value.toString();
            
            // 使用StringTokenizer进行分词(按空格、制表符等分割)
            StringTokenizer tokenizer = new StringTokenizer(line);
            
            // 遍历每个单词
            while (tokenizer.hasMoreTokens()) {
                // 设置输出key为当前单词
                word.set(tokenizer.nextToken());
                // 输出 <单词, 1> 键值对
                context.write(word, one);
            }
        }
    }

    /**
     * Reduce类 - 负责汇总每个单词的出现次数
     * 泛型参数说明:
     *   KEYIN   - Text        : 输入key类型(单词)
     *   VALUEIN - IntWritable  : 输入value类型(计数1的列表)
     *   KEYOUT  - Text         : 输出key类型(单词)
     *   VALUEOUT- IntWritable   : 输出value类型(总计数)
     */
    public static class WordCountReducer 
            extends Reducer<Text, IntWritable, Text, IntWritable> {
        
        // 创建输出value对象
        private IntWritable result = new IntWritable();

        /**
         * reduce方法 - 对同一单词的所有计数求和
         * @param key     单词
         * @param values  该单词对应的所有计数(如 [1, 1, 1, 1])
         * @param context 上下文对象,用于输出最终结果
         */
        @Override
        protected void reduce(Text key, Iterable<IntWritable> values, Context context)
                throws IOException, InterruptedException {
            // 初始化计数器
            int sum = 0;
            
            // 遍历该单词的所有计数值并累加
            for (IntWritable val : values) {
                sum += val.get();  // get()方法获取IntWritable的int值
            }
            
            // 设置输出value为总计数
            result.set(sum);
            // 输出 <单词, 总次数> 键值对
            context.write(key, result);
        }
    }

    /**
     * main方法 - 程序入口,配置并提交MapReduce作业
     * @param args 命令行参数:args[0]=输入路径, args[1]=输出路径
     */
    public static void main(String[] args) throws Exception {
        // 创建Hadoop配置对象
        Configuration conf = new Configuration();
        
        // 创建Job对象,指定Job名称为"Word Count"
        Job job = Job.getInstance(conf, "Word Count");
        
        // 设置Job的主类(Jar包入口)
        job.setJarByClass(WordCount.class);
        
        // 设置Mapper类
        job.setMapperClass(WordCountMapper.class);
        
        // 设置Combiner类(可选,Map端预聚合,减少网络传输)
        // 在词频统计场景下,Combiner和Reducer逻辑相同
        job.setCombinerClass(WordCountReducer.class);
        
        // 设置Reducer类
        job.setReducerClass(WordCountReducer.class);
        
        // 设置输出key的类型
        job.setOutputKeyClass(Text.class);
        
        // 设置输出value的类型
        job.setOutputValueClass(IntWritable.class);
        
        // 设置输入路径(从命令行参数获取)
        FileInputFormat.addInputPath(job, new Path(args[0]));
        
        // 设置输出路径(从命令行参数获取)
        FileOutputFormat.setOutputPath(job, new Path(args[1]));
        
        // 提交Job并等待执行完成
        // waitForCompletion(true) 参数true表示显示执行进度
        // 返回true表示Job执行成功,false表示失败
        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }
}
bash 复制代码
# ============ 编译打包自定义MR程序 ============

# 创建项目目录结构
mkdir -p /home/hadoop/wordcount_project/src/com/example/

# 将WordCount.java放到源码目录
cp WordCount.java /home/hadoop/wordcount_project/src/com/example/

# 进入项目目录
cd /home/hadoop/wordcount_project/

# 编译Java源码(指定Hadoop依赖的classpath)
javac -classpath $(hadoop classpath) -d classes/ src/com/example/WordCount.java

# 打包为JAR文件
jar -cvf wordcount.jar -C classes/ .

# 将JAR包放到Azkaban项目的lib目录
mkdir -p /home/hadoop/azkaban_projects/mr_test/lib/
cp wordcount.jar /home/hadoop/azkaban_projects/mr_test/lib/

文件:custom_wordcount_mr.job

properties 复制代码
# ============ custom_wordcount_mr.job - 调度自定义MR程序 ============
# 任务类型:hadoopJava
type=hadoopJava

# 依赖关系:依赖数据准备任务
dependencies=prepare_hdfs,upload_data

# 自定义WordCount的主类全限定名
java.class=com.example.WordCount

# 传入输入输出路径参数
main.args=/wordcount/input /wordcount/output

# 指定自定义jar包路径和Hadoop依赖路径
classpath=lib/wordcount.jar,${hadoop.home}/etc/hadoop,${hadoop.home}/share/hadoop/common/*,${hadoop.home}/share/hadoop/mapreduce/*

# 设置Hadoop环境变量
env.HADOOP_HOME=/opt/module/hadoop
env.HADOOP_CONF_DIR=/opt/module/hadoop/etc/hadoop

# 以hadoop用户身份执行
user.to.proxy=hadoop

# 失败重试2次
retries=2

# 超时时间:30分钟
job.timeout=1800000

七、案例演示三:Hive脚本任务调度管理

7.1 案例描述

使用Azkaban调度Hive脚本,完成数据仓库的ETL流程。

复制代码
┌──────────────┐   ┌──────────────────┐   ┌──────────────────┐
│ create_table │──>│ create_test_data │──>│   etl_process    │
│ (创建数据表)   │   │ (生成测试数据)    │   │ (ETL数据处理)     │
└──────────────┘   └──────────────────┘   └────────┬─────────┘
                                                    │
                    ┌──────────────────┐   ┌────────▼─────────┐
                    │  export_result   │<──│  data_analysis   │
                    │ (导出分析结果)    │   │  (数据分析查询)   │
                    └──────────────────┘   └──────────────────┘

7.2 编写Hive脚本文件

文件:create_tables.hql

sql 复制代码
-- ============ create_tables.hql ============
-- 创建Hive数据表脚本
-- 该脚本在Azkaban的Hive任务中执行

-- 设置Hive执行参数
-- 开启本地模式(数据量小时可以加速执行)
SET hive.exec.mode.local.auto=true;

-- 设置输出结果不显示表头和分隔符(方便后续处理)
SET hive.cli.print.header=false;

-- 删除已存在的数据库(如果存在),使用CASCADE级联删除表
DROP DATABASE IF EXISTS azkaban_db CASCADE;

-- 创建数据库
CREATE DATABASE IF NOT EXISTS azkaban_db;

-- 切换到目标数据库
USE azkaban_db;

-- ============ 创建原始日志表 ============
-- 用于存储原始的用户访问日志
-- IF NOT EXISTS: 如果表已存在则不创建(避免报错)
CREATE TABLE IF NOT EXISTS user_access_log (
    user_id      STRING      COMMENT '用户ID',         -- 用户唯一标识
    user_name    STRING      COMMENT '用户名',          -- 用户名称
    access_time  STRING      COMMENT '访问时间',        -- 访问时间戳
    page_url     STRING      COMMENT '访问页面URL',     -- 用户访问的页面地址
    access_type  STRING      COMMENT '访问类型',        -- 如:pageview/click/download
    duration     INT         COMMENT '停留时长(秒)',     -- 用户在页面的停留时间
    ip_address   STRING      COMMENT 'IP地址'           -- 用户的IP地址
)
COMMENT '用户访问日志原始表'                             -- 表注释
ROW FORMAT DELIMITED                                    -- 行格式为分隔符格式
FIELDS TERMINATED BY '\t'                               -- 字段之间用制表符分隔
LINES TERMINATED BY '\n'                                -- 行之间用换行符分隔
STORED AS TEXTFILE;                                     -- 存储格式为纯文本文件

-- ============ 创建用户维度表 ============
-- 存储用户基本信息
CREATE TABLE IF NOT EXISTS user_dim (
    user_id      STRING      COMMENT '用户ID',
    user_name    STRING      COMMENT '用户名',
    user_level   STRING      COMMENT '用户等级',        -- 如:VIP/普通/新用户
    register_date STRING     COMMENT '注册日期',
    city         STRING      COMMENT '所在城市'
)
COMMENT '用户维度表'
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
LINES TERMINATED BY '\n'
STORED AS TEXTFILE;

-- ============ 创建日统计结果表 ============
-- 存储按日期统计的访问量
CREATE TABLE IF NOT EXISTS daily_access_stats (
    stat_date        STRING      COMMENT '统计日期',
    total_pv         BIGINT      COMMENT '总PV(页面浏览量)',
    total_uv         BIGINT      COMMENT '总UV(独立访客数)',
    avg_duration     DOUBLE      COMMENT '平均停留时长',
    max_duration     INT         COMMENT '最长停留时间',
    top_page         STRING      COMMENT '访问量最高的页面'
)
COMMENT '每日访问统计结果表'
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
LINES TERMINATED BY '\n'
STORED AS TEXTFILE;

-- 输出创建完成提示
SELECT '=== 所有表创建完成 ===';

文件:generate_test_data.hql

sql 复制代码
-- ============ generate_test_data.hql ============
-- 生成测试数据的Hive脚本
-- 使用Hive内置函数和子查询生成模拟数据

-- 切换到目标数据库
USE azkaban_db;

-- ============ 向user_access_log表插入模拟数据 ============
-- 使用多重INSERT语句一次性插入多条数据
INSERT OVERWRITE TABLE user_access_log
SELECT 
    'user_001',                                          -- user_id: 用户ID
    'Alice',                                             -- user_name: 用户名
    '2024-01-15 08:30:00',                               -- access_time: 访问时间
    '/home',                                             -- page_url: 首页
    'pageview',                                          -- access_type: 页面浏览
    120,                                                 -- duration: 停留120秒
    '192.168.1.101'                                      -- ip_address: IP地址
UNION ALL
SELECT 'user_002', 'Bob', '2024-01-15 09:15:00', '/products', 'click', 45, '192.168.1.102'
UNION ALL
SELECT 'user_003', 'Charlie', '2024-01-15 10:00:00', '/home', 'pageview', 200, '192.168.1.103'
UNION ALL
SELECT 'user_001', 'Alice', '2024-01-15 11:30:00', '/about', 'pageview', 80, '192.168.1.101'
UNION ALL
SELECT 'user_004', 'David', '2024-01-15 12:00:00', '/home', 'download', 300, '192.168.1.104'
UNION ALL
SELECT 'user_002', 'Bob', '2024-01-15 14:00:00', '/home', 'pageview', 150, '192.168.1.102'
UNION ALL
SELECT 'user_005', 'Eve', '2024-01-15 15:30:00', '/products', 'click', 60, '192.168.1.105'
UNION ALL
SELECT 'user_003', 'Charlie', '2024-01-15 16:00:00', '/contact', 'pageview', 90, '192.168.1.103'
UNION ALL
SELECT 'user_001', 'Alice', '2024-01-16 09:00:00', '/home', 'pageview', 180, '192.168.1.101'
UNION ALL
SELECT 'user_006', 'Frank', '2024-01-16 10:30:00', '/products', 'click', 75, '192.168.1.106';

-- ============ 向user_dim表插入用户维度数据 ============
INSERT OVERWRITE TABLE user_dim
SELECT 'user_001', 'Alice',   'VIP',   '2023-01-01', '北京'
UNION ALL
SELECT 'user_002', 'Bob',     '普通',  '2023-03-15', '上海'
UNION ALL
SELECT 'user_003', 'Charlie', 'VIP',   '2023-06-20', '广州'
UNION ALL
SELECT 'user_004', 'David',   '新用户', '2024-01-10', '深圳'
UNION ALL
SELECT 'user_005', 'Eve',     '普通',  '2023-09-01', '杭州'
UNION ALL
SELECT 'user_006', 'Frank',   '新用户', '2024-01-12', '成都';

-- 验证数据插入是否成功
SELECT '=== user_access_log数据条数 ===';
SELECT COUNT(*) AS log_count FROM user_access_log;

SELECT '=== user_dim数据条数 ===';
SELECT COUNT(*) AS user_count FROM user_dim;

文件:etl_process.hql

sql 复制代码
-- ============ etl_process.hql ============
-- ETL(Extract-Transform-Load)数据处理脚本
-- 对原始数据进行清洗、转换和加载

-- 切换到目标数据库
USE azkaban_db;

-- 设置Hive参数
-- 开启动态分区
SET hive.exec.dynamic.partition=true;
SET hive.exec.dynamic.partition.mode=nonstrict;

-- ============ 数据清洗 ============
-- 1. 去除无效数据(user_id为空的记录)
-- 2. 标准化访问类型(统一转小写)
-- 3. 过滤异常停留时长(>0 且 <86400 即不超过24小时)

-- 创建清洗后的临时表
DROP TABLE IF EXISTS tmp_clean_log;
CREATE TABLE tmp_clean_log AS
SELECT 
    user_id,                                             -- 用户ID
    user_name,                                           -- 用户名
    access_time,                                         -- 访问时间
    page_url,                                            -- 页面URL
    -- 使用LOWER函数将access_type统一转为小写
    LOWER(access_type) AS access_type,
    duration,                                            -- 停留时长
    ip_address,                                          -- IP地址
    -- 使用SUBSTR截取日期部分(前10位:YYYY-MM-DD)
    SUBSTR(access_time, 1, 10) AS access_date
FROM user_access_log
WHERE 
    -- 过滤条件1:user_id不为空
    user_id IS NOT NULL 
    AND user_id != ''
    -- 过滤条件2:停留时长在合理范围内
    AND duration > 0 
    AND duration < 86400;

-- 验证清洗后的数据量
SELECT '=== 清洗后数据条数 ===';
SELECT COUNT(*) AS clean_count FROM tmp_clean_log;

-- ============ 数据聚合:每日统计 ============
-- 将统计结果写入daily_access_stats表
INSERT OVERWRITE TABLE daily_access_stats
SELECT 
    access_date AS stat_date,                             -- 统计日期
    
    -- PV(Page View):总页面浏览量
    COUNT(*) AS total_pv,
    
    -- UV(Unique Visitor):独立访客数(去重统计user_id)
    COUNT(DISTINCT user_id) AS total_uv,
    
    -- 平均停留时长(保留2位小数)
    ROUND(AVG(duration), 2) AS avg_duration,
    
    -- 最长停留时间
    MAX(duration) AS max_duration,
    
    -- 访问量最高的页面(使用子查询获取)
    -- 先按页面分组计数,取访问量最大的页面
    (
        SELECT t2.page_url 
        FROM tmp_clean_log t2 
        WHERE t2.access_date = t1.access_date 
        GROUP BY t2.page_url 
        ORDER BY COUNT(*) DESC 
        LIMIT 1
    ) AS top_page
    
FROM tmp_clean_log t1
GROUP BY access_date                                        -- 按日期分组
ORDER BY access_date;                                       -- 按日期排序

-- 验证统计结果
SELECT '=== 每日访问统计结果 ===';
SELECT * FROM daily_access_stats;

-- ============ 清理临时表 ============
DROP TABLE IF EXISTS tmp_clean_log;

SELECT '=== ETL处理完成 ===';

文件:data_analysis.hql

sql 复制代码
-- ============ data_analysis.hql ============
-- 数据分析查询脚本
-- 基于ETL处理后的数据进行多维度分析

-- 切换到目标数据库
USE azkaban_db;

-- ============ 分析1:按用户等级统计访问情况 ============
-- 关联用户维度表,分析不同等级用户的访问行为
SELECT 
    '=== 按用户等级统计 ===' AS info;

SELECT 
    ud.user_level,                                        -- 用户等级
    COUNT(*) AS total_access,                             -- 总访问次数
    COUNT(DISTINCT ual.user_id) AS user_count,            -- 访问用户数
    ROUND(AVG(ual.duration), 2) AS avg_duration,          -- 平均停留时长
    MAX(ual.duration) AS max_duration                     -- 最长停留时间
FROM user_access_log ual
-- LEFT JOIN关联用户维度表
LEFT JOIN user_dim ud ON ual.user_id = ud.user_id
GROUP BY ud.user_level                                     -- 按用户等级分组
ORDER BY total_access DESC;                                -- 按访问量降序排列

-- ============ 分析2:按页面统计访问Top5 ============
SELECT 
    '=== 页面访问量Top5 ===' AS info;

SELECT 
    page_url,                                              -- 页面URL
    COUNT(*) AS page_views,                                -- 页面浏览量
    COUNT(DISTINCT user_id) AS unique_visitors,            -- 独立访客数
    ROUND(AVG(duration), 2) AS avg_stay_time               -- 平均停留时间
FROM user_access_log
GROUP BY page_url                                          -- 按页面分组
ORDER BY page_views DESC                                   -- 按浏览量降序
LIMIT 5;                                                   -- 只取前5条

-- ============ 分析3:按时段分析访问分布 ============
SELECT 
    '=== 按小时分析访问分布 ===' AS info;

SELECT 
    -- 使用SUBSTR提取小时部分(第12-13位,如 "08"、"14")
    SUBSTR(access_time, 12, 2) AS access_hour,
    COUNT(*) AS visit_count,                               -- 该时段访问量
    COUNT(DISTINCT user_id) AS unique_users                -- 该时段独立用户数
FROM user_access_log
GROUP BY SUBSTR(access_time, 12, 2)                        -- 按小时分组
ORDER BY access_hour;                                      -- 按小时排序

-- 输出分析完成标记
SELECT '=== 数据分析完成 ===';

文件:export_result.hql

sql 复制代码
-- ============ export_result.hql ============
-- 导出分析结果脚本
-- 将分析结果导出到HDFS指定目录

-- 切换到目标数据库
USE azkaban_db;

-- ============ 导出每日统计结果到HDFS ============
-- 使用INSERT OVERWRITE DIRECTORY将查询结果写入HDFS目录
-- 指定字段分隔符为制表符
INSERT OVERWRITE DIRECTORY '/azkaban_output/daily_stats'
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
LINES TERMINATED BY '\n'
SELECT * FROM daily_access_stats;

-- ============ 导出用户访问汇总到HDFS ============
INSERT OVERWRITE DIRECTORY '/azkaban_output/user_summary'
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
LINES TERMINATED BY '\n'
SELECT 
    ual.user_id,
    ud.user_name,
    ud.user_level,
    ud.city,
    COUNT(*) AS total_visits,
    SUM(ual.duration) AS total_duration,
    MIN(ual.access_time) AS first_visit,
    MAX(ual.access_time) AS last_visit
FROM user_access_log ual
LEFT JOIN user_dim ud ON ual.user_id = ud.user_id
GROUP BY ual.user_id, ud.user_name, ud.user_level, ud.city;

SELECT '=== 结果导出完成 ===';

7.3 编写Job文件

文件:create_table.job

properties 复制代码
# ============ create_table.job - 创建Hive表 ============
# 任务类型:hive(执行Hive脚本)
type=hive

# 指定要执行的Hive脚本文件路径
# 路径相对于项目ZIP包的根目录
hive.script=scripts/create_tables.hql

# 设置Hive相关环境变量
env.HIVE_HOME=/opt/module/hive
env.HADOOP_HOME=/opt/module/hadoop

# 用户代理
user.to.proxy=hadoop

# 超时时间:10分钟
job.timeout=600000

# 失败重试次数
retries=1

文件:generate_data.job

properties 复制代码
# ============ generate_data.job - 生成测试数据 ============
# 任务类型:hive
type=hive

# 依赖关系:依赖create_table任务(表创建后才能插入数据)
dependencies=create_table

# 指定Hive脚本
hive.script=scripts/generate_test_data.hql

# 设置环境变量
env.HIVE_HOME=/opt/module/hive
env.HADOOP_HOME=/opt/module/hadoop

# 用户代理
user.to.proxy=hadoop

# 超时时间
job.timeout=600000

文件:etl_job.job

properties 复制代码
# ============ etl_job.job - ETL数据处理 ============
# 任务类型:hive
type=hive

# 依赖关系:依赖generate_data任务
dependencies=generate_data

# 指定Hive脚本
hive.script=scripts/etl_process.hql

# 设置环境变量
env.HIVE_HOME=/opt/module/hive
env.HADOOP_HOME=/opt/module/hadoop

# 用户代理
user.to.proxy=hadoop

# 超时时间:20分钟(ETL处理可能耗时较长)
job.timeout=1200000

# 失败重试2次
retries=2

# 重试间隔10秒
retry.backoff=10000

文件:analysis_job.job

properties 复制代码
# ============ analysis_job.job - 数据分析 ============
# 任务类型:hive
type=hive

# 依赖关系:依赖etl_job任务
dependencies=etl_job

# 指定Hive脚本
hive.script=scripts/data_analysis.hql

# 设置环境变量
env.HIVE_HOME=/opt/module/hive
env.HADOOP_HOME=/opt/module/hadoop

# 用户代理
user.to.proxy=hadoop

# 超时时间
job.timeout=600000

文件:export_job.job

properties 复制代码
# ============ export_job.job - 导出分析结果 ============
# 任务类型:hive
type=hive

# 依赖关系:依赖analysis_job任务
dependencies=analysis_job

# 指定Hive脚本
hive.script=scripts/export_result.hql

# 设置环境变量
env.HIVE_HOME=/opt/module/hive
env.HADOOP_HOME=/opt/module/hadoop

# 用户代理
user.to.proxy=hadoop

# 超时时间
job.timeout=600000

7.4 项目打包与上传

bash 复制代码
# ============ 打包Hive调度项目 ============

# 创建项目目录结构
mkdir -p /home/hadoop/azkaban_projects/hive_etl/scripts/

# 将Hive脚本文件放入scripts子目录
cp create_tables.hql      /home/hadoop/azkaban_projects/hive_etl/scripts/
cp generate_test_data.hql /home/hadoop/azkaban_projects/hive_etl/scripts/
cp etl_process.hql        /home/hadoop/azkaban_projects/hive_etl/scripts/
cp data_analysis.hql      /home/hadoop/azkaban_projects/hive_etl/scripts/
cp export_result.hql      /home/hadoop/azkaban_projects/hive_etl/scripts/

# 将Job文件放入项目根目录
cp create_table.job   /home/hadoop/azkaban_projects/hive_etl/
cp generate_data.job  /home/hadoop/azkaban_projects/hive_etl/
cp etl_job.job        /home/hadoop/azkaban_projects/hive_etl/
cp analysis_job.job   /home/hadoop/azkaban_projects/hive_etl/
cp export_job.job     /home/hadoop/azkaban_projects/hive_etl/

# 进入项目目录
cd /home/hadoop/azkaban_projects/hive_etl/

# 查看项目文件结构
tree .
# .
# ├── create_table.job
# ├── generate_data.job
# ├── etl_job.job
# ├── analysis_job.job
# ├── export_job.job
# └── scripts/
#     ├── create_tables.hql
#     ├── generate_test_data.hql
#     ├── etl_process.hql
#     ├── data_analysis.hql
#     └── export_result.hql

# 打包为ZIP文件(-r 递归打包所有子目录和文件)
zip -r hive_etl_project.zip *.job scripts/

# 确认ZIP文件内容
unzip -l hive_etl_project.zip

# 在Web UI中创建项目并上传ZIP文件
# 1. 登录Azkaban Web UI
# 2. Create Project -> 项目名: hive_etl_project
# 3. Upload -> 选择 hive_etl_project.zip
# 4. 在Flow视图中查看DAG依赖图
# 5. 点击Execute Flow执行

八、Azkaban定时调度(Schedule)

8.1 通过Web UI设置定时调度

bash 复制代码
# ============ 定时调度配置步骤 ============
# 1. 在Azkaban Web UI中进入项目页面
# 2. 选择要调度的Flow(工作流)
# 3. 点击 "Schedule" 按钮
# 4. 配置Cron表达式或时间设置

# Cron表达式格式说明(Azkaban使用类似Unix Cron的格式):
# ┌───────────── 分钟 (0-59)
# │ ┌───────────── 小时 (0-23)
# │ │ ┌───────────── 日 (1-31)
# │ │ │ ┌───────────── 月 (1-12)
# │ │ │ │ ┌───────────── 星期 (0-7, 0和7都是周日)
# │ │ │ │ │
# * * * * *

8.2 Cron表达式常用示例

bash 复制代码
# ============ 常用Cron表达式示例 ============

# 每天凌晨0点执行
0 0 * * *

# 每天上午8点30分执行
30 8 * * *

# 每小时执行一次
0 * * * *

# 每隔5分钟执行一次
*/5 * * * *

# 每周一到周五的上午9点执行
0 9 * * 1-5

# 每月1号凌晨1点执行
0 1 1 * *

# 每天8点、12点、18点各执行一次
0 8,12,18 * * *

# 每天凌晨2点到6点之间每30分钟执行一次
0,30 2-6 * * *

8.3 通过API设置定时调度

bash 复制代码
# ============ 使用CURL命令通过API创建调度 ============

# Azkaban REST API创建Schedule的示例
# POST请求到Azkaban Web Server

# 方式1:使用Cron表达式
curl -k -X POST "https://node01:8081/schedule" \
  --data-urlencode "ajax=createSchedule" \
  --data-urlencode "session.id=<SESSION_ID>" \
  --data-urlencode "projectId=<PROJECT_ID>" \
  --data-urlencode "flow=dependency_test" \
  --data-urlencode "scheduleTime=0,30,8,*,*,?" \
  --data-urlencode "scheduleDate=01/01/2024" \
  --data-urlencode "period=1d" \
  --data-urlencode "failureEmails=admin@example.com" \
  --data-urlencode "successEmails=admin@example.com"

# 参数说明:
# ajax         - API操作类型
# session.id   - 登录后获取的会话ID
# projectId    - 项目ID(在Web UI中可以看到)
# flow         - 要调度的工作流名称
# scheduleTime - 调度时间(Cron格式)
# scheduleDate - 调度开始日期
# period       - 调度周期
# failureEmails - 失败通知邮箱
# successEmails - 成功通知邮箱
bash 复制代码
# ============ 获取Session ID(登录认证) ============

# 通过API登录获取session ID
curl -k -X POST "https://node01:8081/" \
  --data-urlencode "action=login" \
  --data-urlencode "username=admin" \
  --data-urlencode "password=admin"

# 返回结果示例:
# {"status":"success","session.id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}

九、高级配置与调优

9.1 Azkaban邮件通知配置

properties 复制代码
# ============ 完整的邮件通知配置 ============
# 在azkaban.properties中配置

# SMTP服务器地址
mail.host=smtp.qq.com

# SMTP端口(SSL加密端口)
mail.smtp.port=465

# 是否使用SSL加密
mail.use.ssl=true

# 发件人邮箱地址
mail.sender=azkaban@example.com

# SMTP认证用户名
mail.user=azkaban@example.com

# SMTP认证密码(对于QQ邮箱需要使用授权码)
mail.password=your_authorization_code

# 发件人显示名称
mail.sender.name=Azkaban Notification

9.2 任务告警邮件的Job配置

properties 复制代码
# ============ 在Job文件中配置邮件告警 ============

# 任务类型
type=command

# 执行命令
command=hive -f /path/to/important_query.hql

# 任务失败时发送告警邮件(多人用逗号分隔)
failure.emails=admin@example.com,dba@example.com

# 任务成功时发送通知邮件
success.emails=admin@example.com

# 任务开始时发送通知邮件
notify.emails=admin@example.com

9.3 Executor调优配置

properties 复制代码
# ============ Executor Server性能调优 ============
# 在executor的azkaban.properties中配置

# 线程池大小(同时执行的任务数)
executor.flow.threads=30

# 扫描新任务的间隔时间(毫秒)
executor.flow.scan.job.interval=1000

# Job最大重试次数(全局默认值)
azkaban.job.retry.count=2

# 全局Job超时时间(毫秒,默认1小时)
# 0 表示不限制
flow.max.job.timeout=3600000

# 全局内存限制
flow.max.job.memory=4096M

十、Azkaban常用操作命令汇总

bash 复制代码
# ============ Azkaban命令行操作大全 ============

# ---- Solo Server操作 ----
bin/start-solo.sh                  # 启动Solo Server
bin/shutdown-solo.sh               # 停止Solo Server

# ---- Two Server / Multi Executor操作 ----
# 启动(必须先Executor后Web)
bin/start-exec.sh                  # 启动Executor Server
bin/start-web.sh                   # 启动Web Server

# 停止(先Web后Executor)
bin/shutdown-web.sh                # 停止Web Server
bin/shutdown-exec.sh               # 停止Executor Server

# ---- Executor激活(Azkaban 3.x必需) ----
# 启动Executor后需要手动激活
curl -G "localhost:12321/executor?action=activate" && echo

# 如果是远程激活(指定hostname)
curl -G "http://node01:12321/executor?action=activate&host=node01" && echo

# ---- 查看日志 ----
tail -f logs/azkaban-web-server.log     # 查看Web Server日志
tail -f logs/azkaban-exec-server.log    # 查看Executor Server日志

# ---- 进程管理 ----
jps                                     # 查看Java进程
ps -ef | grep azkaban                   # 查看Azkaban相关进程
kill -9 <PID>                           # 强制杀死进程(谨慎使用)

# ---- 端口检查 ----
netstat -tlnp | grep 8081               # 检查Web Server端口
netstat -tlnp | grep 12321              # 检查Executor Server端口

# ---- 项目打包 ----
zip -r project_name.zip *.job scripts/  # 打包项目为ZIP

# ---- API操作 ----
# 获取Session ID
curl -k -X POST "http://node01:8081/?action=login&username=admin&password=admin"

# 获取项目列表
curl -k "http://node01:8081/manager?ajax=fetchProjectFlows&session.id=<SID>&project=<PROJECT_NAME>"

# 执行工作流
curl -k -X POST "http://node01:8081/executor?ajax=executeFlow&session.id=<SID>&project=<PROJECT_NAME>&flow=<FLOW_NAME>"

# 取消正在执行的工作流
curl -k -X POST "http://node01:8081/executor?ajax=cancelFlow&session.id=<SID>&execid=<EXEC_ID>"

# 查看执行状态
curl -k "http://node01:8081/executor?ajax=fetchexecflow&session.id=<SID>&execid=<EXEC_ID>"

十一、常见问题排查

11.1 FAQ与解决方案

bash 复制代码
# ============ 问题1:Web Server启动失败 ============
# 症状:启动后端口未监听,日志报错

# 解决方案:
# 1. 检查日志文件
cat logs/azkaban-web-server.log | grep -i "error\|exception"

# 2. 检查端口是否被占用
netstat -tlnp | grep 8081
# 如果端口被占用,杀死占用进程或修改端口号

# 3. 检查Java版本
java -version
# 必须是JDK 1.8+

# ============ 问题2:Executor无法启动 ============
# 解决方案:
# 1. 检查MySQL连接配置是否正确
# 2. 检查MySQL驱动jar包是否在extlib目录中
ls extlib/mysql-connector-java-*.jar

# 3. 检查MySQL数据库是否可以正常连接
mysql -u azkaban -p -h node01 azkaban

# ============ 问题3:任务提交后一直处于PREPARING状态 ============
# 解决方案:
# 1. 检查Executor是否已激活
curl -G "localhost:12321/executor?action=activate"

# 2. 检查数据库中Executor状态
mysql -u azkaban -p azkaban
# SELECT * FROM executors;

# ============ 问题4:Hive脚本执行失败 ============
# 解决方案:
# 1. 确保HIVE_HOME环境变量在Job中正确设置
# env.HIVE_HOME=/opt/module/hive

# 2. 确保Hive命令可以在服务器上正常执行
hive -e "SHOW DATABASES;"

# 3. 检查Hive脚本文件路径是否正确(相对ZIP包根目录)

# ============ 问题5:时区不正确导致定时调度时间错误 ============
# 解决方案:
# 在azkaban.properties中确认时区设置
default.timezone.id=Asia/Shanghai

# 确认服务器系统时区
date
timedatectl

十二、本章小结

知识点 核心内容
Azkaban概述 LinkedIn开源的批量工作流调度器,三大组件:Web Server、Executor Server、MySQL
部署模式 Solo Server(开发测试)、Two Server(生产主备)、Multi Executor(大规模生产)
安装配置 安装JDK、MySQL,配置azkaban.properties,导入数据库初始化脚本
启动顺序 Two Server模式:先启动Executor,激活后,再启动Web Server
Job文件语法 type、dependencies、command等核心属性,支持command/hive/hadoopJava等类型
依赖调度 通过dependencies属性定义DAG(有向无环图)依赖关系
MapReduce调度 使用hadoopJava类型,配置java.class、main.args、classpath
Hive脚本调度 使用hive类型,配置hive.script指向.hql文件
定时调度 通过Web UI或REST API,使用Cron表达式配置定时执行
邮件告警 配置failure.emails和success.emails实现任务状态通知