复习:
1、启动 frpc
frpc http --sd xiongda -l 8802 -s frp.qfbigdata.com:7001 -u xiongda
2、启动Nginx
[root@caiji ~]# openresty -p /opt/apps/collect-app/ -s stop
[root@caiji ~]# openresty -p /opt/apps/collect-app/
3、日志切割任务启动 (检查脚本日志中的路径是否写错了)
systemctl restart crond
4、启动flume (flume中的脚本位置饮用有问题,启动的时候位置有问题)
sh flume-agent.sh start
5、加载数据到hive中(启动 metastore hiveserver2)(连不上 启动多个了,或者mysql服务器没启动)
hive --service metastore &
hive --service hiveserver2 &
beeline -i ~/.hiverc
beeline> !connect jdbc:hive2://caiji:10000
Enter username for jdbc:hive2://caiji:10000: root
Enter password for jdbc:hive2://caiji:10000: ******
jdbc:hive2://caiji:10000> show databases;
6、执行加载分区数据的脚本
一、创建新的表news_parquet
目前news表中的数据:
| news | 1664851731089 | {"distinct_id":"6420","event":"SignUp","properties":{"model":"小米Note","network_type":"4G","is_charging":"","app_version":"2.3","element_name":"","element_page":"","carrier":"","os":"","imei":"875421071346","battery_level":"89","screen_width":"1920","screen_height":"768","device_id":"FIBERHOME2c862518f5ef","client_time":"2022-10-04 10:48:26","ip":"222.37.69.73","manufacturer":"xiaomi"}}
编写SQL语句用于查询,特别的费劲。虽然可以使用json方法解析它,但是还是有点麻烦,为了将来查询分析方便,需要将
{"distinct_id":"6420","event":"SignUp","properties":{"model":"小米Note","network_type":"4G","is_charging":"","app_version":"2.3","element_name":"","element_page":"","carrier":"","os":"","imei":"875421071346","battery_level":"89","screen_width":"1920","screen_height":"768","device_id":"FIBERHOME2c862518f5ef","client_time":"2022-10-04 10:48:26","ip":"222.37.69.73","manufacturer":"xiaomi"}}
展开,获取到有用的字段,变为一个真实的多个字段。
将json中的数据获取出来:
select get_json_object('{"name":"jack","age":19}','$.age');
此种方案是可行的,通过 属性. 的方式将数据获取到
select content.properties.model from news;

还可以在flume抽取数据的时候,就把字段一个个全部取出来,然后放在hdfs上,然后映射。
需要做的内容:
1、创建一个新的表,这个表中有我们想要的字段,是一个分区表
2、将ods_news.news 表中的数据,查询出来,塞入到新表中,这个做法叫做动态分区。
动态分区是需要设置的。
set hive.exec.dynamic.partition=true;
set hive.exec.dynamic.partition.mode=nonstrict;
动态分区中默认要求你必须至少有一个静态分区字段,所以要开启非严格模式。
可以将以上事情,编写为脚本:news_parquet.sh
#!/bin/bash
# desc:将news表的数据到处到news_parquet表中
##1. 申明变量
B_HOST=caiji
B_PORT=10000
B_USER=root
HIVE_HOME=/opt/installs/hive
##2. 导入的日期
exec_date=$1
if [ "${exec_date}" ]; then
exec_date=`date -d "${exec_date} 1 days ago" +%Y%m%d`
else
exec_date=`date -d "1 days ago" +%Y%m%d`
fi
echo "news_parquet.sh exec_date is ${exec_date}"
##3. 建表的SQL
CREATE_TABLE_SQL="create external table if not exists ods_news.news_parquet(
event string,
ctime string,
distinct_id string,
model string,
network_type string,
is_charging string,
app_version string,
element_name string,
element_page string,
carrier string,
os string,
imei string,
battery_level string,
screen_width string,
screen_height string,
device_id string,
client_time string,
ip string,
manufacturer string
)
partitioned by(logday string)
stored as parquet
location '/sources/news-parquet'"
echo "${CREATE_TABLE_SQL}"
${HIVE_HOME}/bin/beeline -i ~/.hiverc -n ${B_USER} -p 123456 -u jdbc:hive2://${B_HOST}:${B_PORT} -e "${CREATE_TABLE_SQL}"
##4. 修改表:添加分区
NEWS_PARQUET_SQL="set hive.exec.dynamic.partition=true;
set hive.exec.dynamic.partition.mode=nonstrict;
insert overwrite table ods_news.news_parquet partition(logday)
select
content.event,
ctime,
content.distinct_id,
content.properties.model,
content.properties.network_type,
content.properties.is_charging,
content.properties.app_version,
content.properties.element_name,
content.properties.element_page,
content.properties.carrier,
content.properties.os,
content.properties.imei,
content.properties.battery_level,
content.properties.screen_width,
content.properties.screen_height,
content.properties.device_id,
content.properties.client_time,
content.properties.ip,
content.properties.manufacturer,
logday
from ods_news.news
where logday=${exec_date} and ctime is not null
"
echo "${NEWS_PARQUET_SQL}"
${HIVE_HOME}/bin/beeline -i ~/.hiverc -n ${B_USER} -p 123456 -u jdbc:hive2://${B_HOST}:${B_PORT} -e "${NEWS_PARQUET_SQL}"
##5. 结束
echo "executable sql successful"
sh news_parquet.sh 20230701
导入成功以后,可以通过beeline查看数据库的数据是否导入成功
select * from news_parquet limit 10;
yarn的web界面:http://caiji:8088/cluster

二、Lua语法入门
1)安装lua插件,并下载解析应用程序

安装完之后,重启idea。



LuaBinaries - Browse /5.3.5/Tools Executables at SourceForge.net






2)编写代码
简介:
Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
Lua 是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组于 1993 年开发的,该小组成员有:Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo。
标识符(起名字):
Lua 标示符用于定义一个变量,函数获取其他用户定义的项。标示符以一个字母 A 到 Z 或 a 到 z 或下划线 _ 开头后加上 0 个或多个字母,下划线,数字(0 到 9)。
最好不要使用下划线加大写字母的标示符,因为Lua的保留字也是这样的。
Lua 不允许使用特殊字符如 @, $, 和 % 来定义标示符。 Lua 是一个区分大小写的编程语言。因此在 Lua 中 Runoob 与 runoob 是两个不同的标示符。
关键字:


Demo1.lua
---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by admin.
--- DateTime: 2023/9/15 10:18
---
-- 我是单行注释
print("hello lua")
--[[
我是多行注释
--]]
print(a) --nil
a = 20 --全局变量
print(a)
local b = 30 --局部变量
print(b)
require("Demo02") -- 加载其他模块
print(c) --模块中的全局变量可以打印,局部变量不能使用
print(d)
--[[
Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。
]]
-- type函数的意思是打印一个值的变量类型
print(type(123))
print(type("hello"))
-- function
--function
print(type(print))
print(type(type))
print(type(10.4*3)) --没有浮点型
print(type(true)) --boolean
print(type(nil)) --空值类型
--[[
while( true )
do
print("循环将永远执行下去")
end ]]
a=10
while( a < 20 )
do
print("a 的值为:", a)
a = a+1
end
-- var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次 "执行体"。exp3 是可选的,如果不指定,默认为1
for var=1,10,2 do
print(var)
end
--[[ 函数返回两个值的最大值 --]]
function max(num1, num2)
print(a)
print(b)
local dd=300
cc = 300
if (num1 > num2) then
result = num1;
else
result = num2;
end
return result;
end
print(dd) --验证一个函数中有一个局部变量,是否可以在函数外打印
print(cc) --全局变量在方法没有执行之前也是拿不到值的
-- 调用函数
print("两值比较最大值为 ",max(10,4))
print("两值比较最大值为 ",max(5,6))
print(dd)
print(cc)
-- 运算符的演示
print(a - b)
print(a == b)
print(a ~= b) --- 不等于
--字符串演示
print("aaa".."bbbb")
-- print("aaa"+"bbbb")
--查看字符串的长度
str="hello"
print(#str)
print(rawlen(str))
print("123"+456)
--数组的定义
-- 创建一个数组
local myArray = {10, 20, 30, 40, 50}
-- 访问数组元素 下标从1开始的
print(myArray[1]) -- 输出 10
print(myArray[3]) -- 输出 30
print(#myArray)
--通过for循环打印数组
for var=1,#myArray do
print(myArray[var])
end
--再搞一个循环 k=下标 v=值
for k,v in ipairs(myArray) do
print(k,v)
end
function fun2(a,b,c)
print(a,b,c) --非常新颖的输出
return a + b + c
end
num = fun2(1,3,5)
print(num)
local user = {name="张三",age=20}
user.class = "三年级二班"
print(type(user)) ---table
print(user.name)
print(user["age"])
print(user.class)
Demo2.lua
---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by User.
--- DateTime: 2022/10/8 9:59
---
local c = 100 ---局部变量只能在当前的lua文件中使用,不能跨文件使用
d = 200 --- 全局的变量可以跨文件使用
三、使用Azkaban进行自动化导入

azkaban自动调度数据采集
frpc --> Nginx --> Lua --> log 日志 --> 定时任务,进行切割 --> data目录下的一个个小文件 --> Flume进行采集
--> 经过flume拦截器解析变为明文 --> HDFS --> 映射成hive中的数据(news) --> hive中的news_parquet 表
分析:
HDFS --> 映射成hive中的数据(news) --> hive中的news_parquet 表 都是非自动的,需要手动的执行。
如果在加上 内容日志的采集以及业务日志的采集,这些都是非自动化的。
如何才能做到多个脚本自动化执行,并且还可以指定先执行谁在执行谁。Azkaban就可以做到。
以前的Azkaban启动的时候需要启动web-server exec-server 两种
现在的Azkaban使用的是solo
在bin目录下:
start-solo.sh
shutdown-solo.sh
上传至 /opt/modules下
azkaban-solo-server-0.1.0-SNAPSHOT.tar.gz
解压:
tar -zxvf azkaban-solo-server-0.1.0-SNAPSHOT.tar.gz -C /opt/installs
重命名:
mv azkaban-solo-server-0.1.0-SNAPSHOT azkaban-solo
配置环境变量:
vi /etc/profile
export AZKABAN_HOME=/opt/installs/azkaban-solo
export PATH=$PATH:$AZKABAN_HOME/bin:
保存,source /etc/profile
启动的时候报错:
cd bin目录下,执行 ./start-solo.sh
java.io.IOException: Cannot find 'database.properties' file in /usr/local/azkaban/soloserver/bin/sql/database.properties
at azkaban.database.AzkabanDatabaseSetup.loadDBProps(AzkabanDatabaseSetup.java:178)
at azkaban.database.AzkabanDatabaseSetup.loadTableInfo(AzkabanDatabaseSetup.java:102)
at azkaban.database.AzkabanDatabaseUpdater.runDatabaseUpdater(AzkabanDatabaseUpdater.java:82)
at azkaban.soloserver.AzkabanSingleServer.start(AzkabanSingleServer.java:93)
at azkaban.soloserver.AzkabanSingleServer.main(AzkabanSingleServer.java:58)
换个启动方式就可以了:
bin/start-solo.sh

访问地址: http://192.168.32.131:8081/index
登录的时候,可以使用自定义账号和密码,也可以使用自带的 azkaban azkaban 账户
咱们这个项目中有一些脚本,是需要每天执行的,我不想手动执行,使用工具完成自动执行。
编写脚本
demo.flow
这个文档编写比较麻烦,采用的是yaml格式,对缩进对齐等特别严格,编写的时候一定要采用 utf-8 without-BOM格式,否则报错:
type: noop 表示这个节点什么都不执行,一般用于开始和结束


## filename : ods.flow
## author : YuPangZa
## date : 2022-06-16
## version : 1.0
config:
param.script_path_prefix: /opt/scripts
param.exec_date: ${exec_date}
nodes:
# 开始节点
- name: START
type: noop
# sqoop : biz2hdfs.sh
- name: SQOOP_BIZ_HDFS
type: command
dependsOn:
- START
config:
command: sh ${param.script_path_prefix}/biz2hdfs.sh ${param.exec_date}
# sqoop : biz_add_partition.sh
- name: SQOOP_BIZ_ADD_PARTITION
type: command
dependsOn:
- SQOOP_BIZ_HDFS
config:
command: sh ${param.script_path_prefix}/biz_add_partition.sh ${param.exec_date}
# flume : news_add_partition.sh
- name: NEWS_ADD_PARTITION
type: command
dependsOn:
- START
config:
command: sh ${param.script_path_prefix}/news_add_partition.sh ${param.exec_date}
# flume : news_parquet.sh
- name: NEWS_PARQUET
type: command
dependsOn:
- NEWS_ADD_PARTITION
config:
command: sh ${param.script_path_prefix}/news_parquet.sh ${param.exec_date}
# 结束节点
- name: END
type: noop
dependsOn:
- NEWS_PARQUET
- SQOOP_BIZ_ADD_PARTITION
demo.project
azkaban-flow-version: 2.0

检查你的脚本中,是否有tab键





四、使用钉钉完成报警
Aakaban之前可以使用邮件,以及电话报警。
可以创建一个钉钉群,所有的大数据开发人员都在群里面,找一个群里的机器人,只要任务失败了,机器人就发送消息到群里,最好这个消息还能点击,跳转到Azkaban的界面。
下载钉钉,登录,创建群聊

电脑端无法创建群,需要手机端创建。
新规则,只有内部群,才可以添加自定义机器人:



页面中有几个值特别的有用,不要泄露:

帮助文档地址:
测试一下:
穿插一下:有一个软件专门用来测试请求的发送和接受--PostMan

curl '机器人的hook' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text","text": {"content":"hi,我就是我, 是不一样的烟火"}}'

第二种模式:加签方式,启动了加签模式,关键字模式就失效了
把timestamp+"\n"+密钥当做签名字符串,使用HmacSHA256算法计算签名,然后进行Base64 encode,最后再把签名参数再进行urlEncode,得到最终的签名(需要使用UTF-8字符集)。
| 参数 | 说明 |
| timestamp | 当前时间戳,单位是毫秒,与请求调用时间误差不能超过1小时。 |
| secret | 密钥,机器人安全设置页面,加签一栏下面显示的SEC开头的字符串。 |
代码演示:
导入包:
<dependencies>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.12</version>
</dependency>
</dependencies>
package com.jiaqian;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import java.net.URLEncoder;
public class Demo{
public static void main(String[] args) throws Exception{
Long timestamp = System.currentTimeMillis();
String secret = "密钥";
String stringToSign = timestamp + "\n" + secret;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"));
byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
String sign = URLEncoder.encode(new String(Base64.encodeBase64(signData)),"UTF-8");
System.out.println(sign);
}
}
要想获取到秘钥,需要开启加签:

通过代码,生成签名和时间戳:
--签名
uoqBPmtwWe8Sv8H79G7K4d%2BQvaLR6Ntc1lzRcqbuD%2Fc%3D
--时间戳
1694760143669
发送消息的格式发生了变化:
https://oapi.dingtalk.com/robot/send?access_token=XXXXXX×tamp=XXX&sign=XXX
通过加签的方式发送消息:
curl '****' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text","text": {"content":"hi,20230801班的大数据才子们!"}}'
发送消息的内容格式可以非常的丰富:
发送一个Link类型的数据:
来自于官方给的Demo:
{
"msgtype": "link",
"link": {
"text": "这个即将发布的新版本,创始人xx称它为红树林。而在此之前,每当面临重大升级,产品经理们都会取一个应景的代号,这一次,为什么是红树林",
"title": "时代的火车向前开",
"picUrl": "",
"messageUrl": "https://www.dingtalk.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI"
}
}
修改一下消息的格式:
curl '****' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "link","link": {
"text": "hi,欲练此功,必先自宫",
"title": "2202班面试宝典",
"picUrl": "https://gss0.baidu.com/-fo3dSag_xI4khGko9WTAnF6hhy/zhidao/pic/item/9922720e0cf3d7ca55b70c41ff1fbe096b63a947.jpg",
"messageUrl": "https://www.taobao.com"
}}'

接着编写一个应用程序,里面需要用到azkaban和钉钉的整合。
编写对应的代码:

第一步:导入包:
假如遇到了一个jar包,这个jar是一个非常小众的jar包,远程仓库没有,但是这个jar包已经在你手里面了。
怎么办?
可以将这个jar包放在本地或者本地仓库,指定路径即可:
1)演示将jar包放在本地(桌面上都行)
需要注意的点:
首先scope 必须是system
接着路径systemPath:必须是你本地磁盘正确的路径
再者:g a v 坐标随便写,没任何影响
demo
<dependency>
<groupId>nlp</groupId>
<artifactId>localjar</artifactId>
<version>1.0.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/repo/nlp/localjar/1.0.0/LocalJar.jar</systemPath>
</dependency>
2)手动将一个jar包,导入到本地仓库
演示window版的操作:
下载jar包:
http://doc.qfbigdata.com/qf/project/soft/dingding/dingtalk-sdk-java.zip
第二步:解压

安装本地jar包:
mvn install:install-file -Dfile=taobao-sdk-java-auto_1479188381469-20200519.jar -DgroupId=com.dingtalk -DartifactId=dingtalk-api -Dversion=1.0.0 -Dpackaging=jar

dingdingSDK中的jar包,本地仓库没有,远程仓库也没有
# 下载SDK,并安装到本地Maven仓库
wget -P /tmp/ http://doc.qfbigdata.com/qf/project/soft/dingding/dingtalk-sdk-java.zip
wget 是下载的意思,使用 -P 路径,就是将软件下载到对应的文件夹下,如果不指定 -P 默认在哪个文件夹下执行这个命令,就下载到那个文件夹下。
yum install -y wget 安装这个命令
cd /tmp/ && unzip dingtalk-sdk-java.zip
# 在你的项目中执行如下命令,安装dingding sdk到本地Maven仓库
mvn install:install-file -Dfile=C:\Users\User\Desktop\dingtalk-sdk-java\taobao-sdk-java-auto_1479188381469-20200519.jar -DgroupId=com.dingtalk -DartifactId=dingtalk-api -Dversion=1.0.0 -Dpackaging=jar
这个命令的意思是使用maven 安装 jar包到本地仓库。
完成的maven配置文件,记得点击 刷新按钮安装:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bigdata</groupId>
<artifactId>DingDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<azkaban.version>2.5.0</azkaban.version>
<junit.version>4.13</junit.version>
<dingtalk-api.version>1.0.0</dingtalk-api.version>
<slf4j.version>1.6.4</slf4j.version>
<log4j.version>1.2.16</log4j.version>
</properties>
<dependencies>
<!--<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.12</version>
</dependency>-->
<dependency>
<groupId>com.linkedin.azkaban</groupId>
<artifactId>azkaban</artifactId>
<version>${azkaban.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!--不是所有的jar包,仓库中都有的,遇到没有的,可以使用本地的jar路径-->
<dependency>
<groupId>com.dingtalk</groupId>
<artifactId>dingtalk-api</artifactId>
<version>${dingtalk-api.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>ali-maven</id>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>ali-maven</id>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>create-distribution</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>src/main/assembly/assembly.xml</descriptor>
</descriptors>
<finalName>${project.artifactId}</finalName>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.5.3</version>
<configuration>
<tagNameFormat>@{project.version}</tagNameFormat>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.5</version>
<configuration>
<forkMode>once</forkMode>
<argLine>-Dfile.encoding=UTF-8</argLine>
<includes>
<include>**/*Test.java</include>
</includes>
<excludes>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
编写代码:
package com.qf.bigdata;
/**
* @Author YuPangZa
* @Description TODO
* @Date 2022/10/9 14:46
* @Version 1.0
*/
import azkaban.alert.Alerter;
import azkaban.executor.ExecutableFlow;
import azkaban.sla.SlaOption;
import azkaban.utils.Props;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.request.OapiRobotSendRequest;
import com.taobao.api.ApiException;
import org.apache.log4j.Logger;
import java.text.SimpleDateFormat;
import java.util.Map;
public class DingAlerter implements Alerter {
private final static Logger LOGGER = Logger.getLogger(DingAlerter.class);
private String dingServerUri;
private Props props;
public DingAlerter(Props props) {
this.props = props;
dingServerUri="https://oapi.dingtalk.com/robot/send?access_token=";
}
public void alertOnSuccess(ExecutableFlow flow) throws Exception {
if (isActivated(flow, "success")) {
int execId = flow.getExecutionId();
StringBuilder sb = new StringBuilder("Flow '").append(flow.getFlowId()).append("' has succeeded:\n");
String title = sb.toString();
sb.append("\t- Start: ").append(formatTime(flow.getStartTime())).append("\n");
sb.append("\t- End: ").append(formatTime(flow.getEndTime())).append("\n");
sb.append("\t- Duration: ").append(formatDuration(flow.getStartTime(), flow.getEndTime()));
send(flow,title, execId, sb.toString());
}
}
public void alertOnError(ExecutableFlow flow, String... extraReasons) throws Exception {
if (isActivated(flow, "error")) {
int execId = flow.getExecutionId();
StringBuilder sb = new StringBuilder("Flow '").append(flow.getFlowId()).append("' has failed:\n");
String title = sb.toString();
sb.append("\t- Start: ").append(formatTime(flow.getStartTime())).append("\n");
sb.append("\t- End: ").append(formatTime(flow.getEndTime())).append("\n");
sb.append("\t- Duration: ").append(formatDuration(flow.getStartTime(), flow.getEndTime()));
send(flow,title, execId, sb.toString());
}
}
public void alertOnFirstError(ExecutableFlow flow) throws Exception {
if (isActivated(flow, "first.error")) {
int execId = flow.getExecutionId();
StringBuilder sb = new StringBuilder("Flow '").append(flow.getFlowId()).append("' has encountered a first failure on:\n");
String title = sb.toString();
sb.append("\t- Start: ").append(formatTime(flow.getStartTime())).append("\n");
sb.append("\t- End: ").append(formatTime(flow.getEndTime())).append("\n");
sb.append("\t- Duration: ").append(formatDuration(flow.getStartTime(), flow.getEndTime()));
send(flow,title, execId, sb.toString());
}
}
public void alertOnSla(SlaOption slaOption, String s) throws Exception {
sendSLA("sla Alert", "Sla Violation Alert:\n" + s);
}
/**
* 封装成钉钉 link 类型的消息
*
* @param title dingding 报警时的 title信息
* @param execId 执行的ID
* @param message 报警的文本消息
* @throws Exception
*/
private void send(ExecutableFlow flow, String title, int execId, String message) throws Exception {
String dingdingToken = getPropertiesByFlowParameters(flow,"ding.token");
DefaultDingTalkClient client = new DefaultDingTalkClient(dingServerUri+dingdingToken);
// 在配置文件中读取到Azkaban的host,当收到报警时会拼接成
// host/executor?execid=${execid}#jobslist 格式,点击会直接跳转到本次执行的joblist页面
String linkHost = getPropertiesByFlowParameters(flow,"ding.link.azkaban.host");
// 验证的关键字 Azkaban
String dingAuthWord = getPropertiesByFlowParameters(flow,"ding.auth.word");
// 拼接字符串 http://192.168.32.131:8081/executor?execid=40#jobslist
String linkAddres = linkHost + "/executor?execid=" + execId + "#jobslist";
OapiRobotSendRequest req = new OapiRobotSendRequest();
req.setMsgtype("link");
OapiRobotSendRequest.Link link = new OapiRobotSendRequest.Link();
link.setMessageUrl(linkAddres);
link.setPicUrl("https://pics1.baidu.com/feed/37d12f2eb9389b50f80280d4e3e7f7d6e5116e90.jpeg");
link.setTitle(dingAuthWord + title);
link.setText(message);
req.setLink(link);
try {
client.execute(req);
} catch (ApiException e) {
String msg = "send message to dingding fail : " + e.getMessage();
LOGGER.error(msg);
throw new ApiException(msg, e);
}
}
private void sendSLA(String title, String message) throws Exception {
String dingdingToken = props.getString("ding.token");
DefaultDingTalkClient client = new DefaultDingTalkClient(dingServerUri+dingdingToken);
String linkHost = props.getString("ding.link.azkaban.host");
String dingAuthWord = props.getString("ding.auth.word");
OapiRobotSendRequest req = new OapiRobotSendRequest();
req.setMsgtype("link");
OapiRobotSendRequest.Link link = new OapiRobotSendRequest.Link();
link.setMessageUrl(linkHost);
link.setPicUrl("");
link.setTitle(dingAuthWord + title);
link.setText(message);
req.setLink(link);
try {
client.execute(req);
} catch (ApiException e) {
String msg = "send sla message to dingding fail : " + e.getMessage();
LOGGER.error(msg);
throw new ApiException(msg, e);
}
}
private String formatTime(long timestamp) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(timestamp);
}
private boolean isActivated(ExecutableFlow flow, String type) {
Map<String, String> parameters = flow.getExecutionOptions().getFlowParameters();
String property = "ding.alert.on." + type;
String defaultActivation = props.getString(property, "false");
String activated = parameters.getOrDefault(property, defaultActivation);
return Boolean.parseBoolean(activated);
}
private String getPropertiesByFlowParameters(ExecutableFlow flow,String propKey) {
Map<String, String> parameters = flow.getExecutionOptions().getFlowParameters();
String defaultChannel = props.getString(propKey);
return parameters.getOrDefault(propKey, defaultChannel);
}
private String formatDuration(long startTime, long endTime) {
if (startTime == -1) {
return "-";
}
long durationMS;
if (endTime == -1) {
durationMS = System.currentTimeMillis() - startTime;
} else {
durationMS = endTime - startTime;
}
long seconds = durationMS / 1000;
if (seconds < 60) {
return seconds + " sec";
}
long minutes = seconds / 60;
seconds %= 60;
if (minutes < 60) {
return minutes + "m " + seconds + "s";
}
long hours = minutes / 60;
minutes %= 60;
if (hours < 24) {
return hours + "h " + minutes + "m " + seconds + "s";
}
long days = hours / 24;
hours %= 24;
return days + "d " + hours + "h " + minutes + "m";
}
}
plugin.properties
# 报警插件的名称,不必修改
alerter.name=dingding
# 加载的类,不需要修改
alerter.class=com.bigdata.DingAlerter
# 钉钉机器人的 token
ding.token = ***
# 钉钉机器人的认证词语,你在钉钉上配置什么,这里就填写什么
ding.auth.word = hi
# 发送到钉钉消息的,点击消息跳转到joblist页面,这里需要配置你azkaban的web地址
ding.link.azkaban.host = http://192.168.52.129:8081
# flow 成功时报警
ding.alert.on.success=true
# flow 第一次错误时报警
ding.alert.on.first.error=false
# flow 出错时报警
ding.alert.on.error=true
在src\main 下创建文件夹assembly以及文件assembly.xml
<?xml version='1.0' encoding='UTF-8'?>
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0
http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>all-hook</id>
<formats>
<format>tar.gz</format>
</formats>
<includeBaseDirectory>true</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>target/classes/META-INF/conf</directory>
<outputDirectory>conf/META-INF/conf</outputDirectory>
<fileMode>0644</fileMode>
</fileSet>
<fileSet>
<directory>target/classes</directory>
<outputDirectory>conf</outputDirectory>
<fileMode>0644</fileMode>
<includes>
<include>*.properties</include>
<include>*.xml</include>
</includes>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
</dependencySet>
</dependencySets>
</assembly>
容易出现的地方,一定上传tar.gz文件,不要上传.jar

上传至plugins/alerter之后,一定要解压:
tar -zxvf DingDing-all-hook.tar.gz
导包成功后,创建文件夹以配置:
mkdir -p /opt/installs/azkaban-solo/plugins/alerter
将打好的tar.gz 这个包上传至该文件夹下,并解压
rm -rf DingDemo-all-hook.tar.gz
重启azkaban
[root@caiji azkaban-solo]# bin/shutdown-solo.sh
[root@caiji azkaban-solo]# bin/start-solo.sh
关闭加签模式:

编写一个简单的任务:a.flow
nodes:
- name: jobA
type: command
config:
command: echo "this is a simple test"
a.project
azkaban-flow-version: 2.0

ding.alert.on.success
alert.type


由于我们的这个代码模拟的是关键字场景下的发送,请关闭【加签】模式。
817

被折叠的 条评论
为什么被折叠?



