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实现任务状态通知 |