13-数据采集项目Day04

复习:

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

image.png

 

二、Lua语法入门

1)安装lua插件,并下载解析应用程序

安装完之后,重启idea。

The Programming Language Lua

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

2)编写代码

Lua 教程 | 菜鸟教程

简介:

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进行自动化导入

image.png

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的界面。

下载钉钉,登录,创建群聊

电脑端无法创建群,需要手机端创建。

新规则,只有内部群,才可以添加自定义机器人:

 

image.png

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

帮助文档地址:

自定义机器人接入 - 钉钉开放平台

测试一下:

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

image.png

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&timestamp=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

image.png

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YuPangZa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值