背景:初衷是源于对hive hook(钩子)函数的一知半解,在跟大佬交流过程中恨自己理解的不够深入(丢脸了,丢脸这件事一次就够了哈),故写下了这篇文章自省也希望大家可以从中学习
1、了解hive hook
需要先了解hive的整体是怎么运行的,我们先来看看我们平时写下的一句句hql是怎么运行的吧
图来自:https://cloud.tencent.com/developer/article/1676816
1.0、具体处理流程如下:
- HQL解析生成AST语法树 Antlr定义SQL的语法规则,完成SQL词法和语法解析,将SQL转化为抽象语法树AST Tree
- 语法分析得到QueryBlock 遍历AST Tree,抽象出查询的基本组成单元QueryBlock
- 生成逻辑执行计划 遍历QueryBlock,翻译为执行操作树Operator Tree
- Logical Optimizer Operator进行逻辑优化 逻辑层优化器进行OperatorTree变换,合并不必要的ReduceSinkOperator,减少shuffle数据量
- 生成物理执行计划Task Plan 遍历Operator Tree,翻译为MapReduce任务
- 物理优化Task Tree,构建执行计划QueryPlan 物理层优化器进行MapReduce任务的变换,生成最终的执行计划
- 表以及其他操作鉴权
- 执行引擎执行
以下以mapreduce引擎来展示运行逻辑,图来自:https://zhuanlan.zhihu.com/p/56346671
1.1、先来认识四大组件
- Driver – 这个组件是用来接收来自 JDBC/ODBC 的查询语句。
- Compiler – 解释器用于解析 query,并且生成一个查询表达式,最终生成执行计划 execution plan。
- Metastore – 这个组件存储了所有的结构信息,包括列、列类型信息,序列化和反序列化还有 HDFS 文件的存储位置。
- Execution Engine – 执行引擎是用于执行 execution plan 的地方,这个执行计划是一个基于 stage 的 DAG。执行引擎维护着执行计划的所有依赖,以及将其放在合适的组件中执行。
1.2、hive query 生命周期:
- Driver接受命令。
- org.apache.hadoop.hive.ql.HiveDriverRunHook.preDriverRun() 读取hive.exec.pre.hooks决定要运行的pre-hooks 。
- org.apache.hadoop.hive.ql.Driver.compile()通过创建代表该查询的抽象语法树(AST)来开始处理查询。
- org.apache.hadoop.hive.ql.parse.AbstractSemanticAnalyzerHook实现了HiveSemanticAnalyzerHook,调用preAnalyze() 方法。
- 对抽象语法树(AST)执行语义分析。
- org.apache.hadoop.hive.ql.parse.AbstractSemanticAnalyzerHook.postAnalyze()会被调用,它执行所有配置的语义分析hooks。
- 创建并验证物理查询计划。
- Driver.execute() 已经准备好开始运行job
- 调用org.apache.hadoop.hive.ql.hooks.ExecuteWithHookContext.run() 方法去执行所有的 pre-execution hooks。
- org.apache.hadoop.hive.ql.hooks.ExecDriver.execute()执行该query的所有jobs
- 对于每个job都会执行org.apache.hadoop.hive.ql.stats.ClientStatsPublisher.run(),来为每个job发布统计信息。该间隔是由hive.exec.counters.pull.interval配置控制,默认是1000ms。hive.client.stats.publishers配置决定着运行的publishers。也可以通过设置hive.client.stats.counters来决定发布哪些counters。
- 完成所有task。
- (可选)如果任务失败,请调用hive.exec.failure.hooks配置的hooks。
- 通过堆所有 hive.exec.post.hooks指定的hooks执行ExecuteWithHookContext.run() 来运行post execution hooks。
- org.apache.hadoop.hive.ql.HiveDriverRunHook.postDriverRun()。请注意,这是在查询完成运行之后以及将结果返回给客户端之前运行的。
- 返回结果。
2、什么是Hive Hook(钩子)
Hook是一种在处理过程中拦截事件,消息或函数调用的机制。 Hive hooks是绑定到了Hive内部的工作机制,无需重新编译Hive。从这个意义上讲,提供了使用hive扩展和集成外部功能的能力。换句话说,Hive hadoop可用于在查询处理的各个步骤中运行/注入一些代码。根据钩子的类型,它可以在查询处理期间的不同点调用
来自官网地址:http://hive.apache.org/javadocs/r2.1.1/api/org/apache/hadoop/hive/ql/hooks/Hook.html
- Pre-execution Hook 在执行引擎执行查询之前被调用。这个需要在 Hive 对查询计划进行过优化之后才可以使用。
- Post-execution hooks 在执行计划执行结束结果返回给用户之前被调用。
- Failure-execution hooks 在执行计划失败之后被调用。
- Pre-driver-run 和 post-driver-run 是在查询运行的时候运行的。
- Pre-semantic-analyzer and Post-semantic-analyzer Hook 在 Hive 对查询语句进行语义分析的时候调用。
3、Hive Hook(钩子) 详细介绍及使用场景
HiveDriverRunHook的preDriverRun
该钩子函数由参数hive.exec.driver.run.hooks控制,决定要运行的pre hooks,多个钩子实现类以逗号间隔,钩子需实现 org.apache.hadoop.hive.ql.HiveDriverRunHook接口。
HiveSemanticAnalyzerHook的preAnalyze
在Driver开始run之前,HQL经过解析会进入编译阶段的语法分析,而在语法分析前会经过钩子HiveSemanticAnalyzerHook的preAnalyze方法处理。该钩子函数由hive.semantic.analyzer.hook配置,钩子需实现org.apache.hadoop.hive.ql.parse.HiveSemanticAnalyzerHook接口。
HiveSemanticAnalyzerHook的postAnalyze
与preAnalyze同属于一个钩子类,配置参数相同,会执行所有配置的语义分析hooks,但它位于Hive的语法分析之后,可以获取HQL的输入和输出表及分区信息,以及语法分析得到的task信息,由此可以判断是否是需要分布式执行的任务,以及执行引擎是什么。
生成执行计划之前的redactor钩子
该钩子由hive.exec.query.redactor.hooks配置,多个实现类以逗号间隔,钩子需继承org.apache.hadoop.hive.ql.hooks.Redactor抽象类,并替换redactQuery方法。
这个钩子函数是在语法分析之后,生成QueryPlan之前,所以执行它的时候语法分析已完成,具体要跑的任务已定,这个钩子的目的在于完成QueryString的替换,比如QueryString中包含敏感的表或字段信息,在这里都可以完成替换,从而在Yarn的RM界面或其他方式查询该任务的时候,会显示经过替换后的HQL。
task执行前的preExecutionHook
在执行计划QueryPlan生成完,并通过鉴权后,就会执行具体的task,而task执行之前会经过一个钩子函数,钩子函数由hive.exec.pre.hooks配置,多个钩子实现类以逗号间隔。实现方式:
1)实现org.apache.hadoop.hive.ql.hooks.ExecuteWithHookContext 通过实现该接口的run方法,执行所有的pre-execution hooks
// Pre/Post Execute Hook can run with the HookContext
public interface ExecuteWithHookContext extends Hook {
/** hookContext: The hook context passed to each hooks.
* HookContext带有执行计划、Hive的配置信息、Lineage、UGI、提交的用户以及输入输出表等信息
*/
void run(HookContext hookContext) throws Exception;
}
2)实现org.apache.hadoop.hive.ql.hooks.PreExecute
该接口的run方法已经标注为过时,并且相对于ExecuteWithHookContext,PreExecute提供的信息可能不能完全满足我们的业务需求。
public interface PreExecute extends Hook {
/**
* The run command that is called just before the execution of the query.
* SessionState、UGI、HQL输入表及分区信息,HQL输出表、分区以及本地和hdfs文件目录信息
*/
@Deprecated
public void run(SessionState sess, Set<ReadEntity> inputs,Set<WriteEntity> outputs, UserGroupInformation ugi) throws Exception;
}
task执行失败时的ON_FAILURE_HOOKS
task执行失败时,Hive会调用这个hook执行一些处理措施。该钩子由参数hive.exec.failure.hooks配置,多个钩子实现类以逗号间隔。需实实现org.apache.hadoop.hive.ql.hooks.ExecuteWithHookContext接口。
task执行完成时的postExecutionHook
在task任务执行完成后执行。如果task失败,会先执行ON_FAILURE_HOOKS,之后执行postExecutionHook,该钩子由参数hive.exec.post.hooks指定的hooks(多个钩子实现类以逗号间隔)执行post execution hooks。实现方式:
1)实现org.apache.hadoop.hive.ql.hooks.ExecuteWithHookContext
2)实现org.apache.hadoop.hive.ql.hooks.PostExecute
ExecuteWithHookContext和PostExecute跟分别与上述task执行前的preExecutionHook、PreExecute对应,这里不再赘述。
HiveDriverRunHook的postDriverRun
在查询完成运行之后以及将结果返回给客户端之前执行,与preDriverRun对应。
此外,Hive中已经有一些内置实现的hook,下面举一些例子以及它们的主要作用:
ATSHook:实现了ExecuteWithHookContext,将查询和计划信息推送到Yarn App Timeline Server。
DriverTestHook:实现了HiveDriverRunHook的preDriverRun方法(对postDriverRun是空实现),用于打印输出的命令。
EnforceReadOnlyTables:pre execute hook,实现了ExecuteWithHookContext,用于阻止修改只读表。
LineageLogger:实现了ExecuteWithHookContext,它将查询的血统信息记录到日志文件中。LineageInfo包含有关query血统的所有信息。
PreExecutePrinter和PostExecutePrinter:pre和post hook的示例,它将参数打印输出。
PostExecTezSummaryPrinter:post execution hook,实现了ExecuteWithHookContext,可以打印Hive Tez计数器的相关信息。
PostExecOrcFileDump:post execution hook,实现了ExecuteWithHookContext,用于打印ORC文件信息。
UpdateInputAccessTimeHook:pre execution hook,可在运行查询之前更新所有输入表的访问时间。
特别强调一下LineageLogger和LineageInfo,对于做Hive血缘关系分析很有参考价值,当然Hive血缘分析不是本篇文章的重点,这里先不做展开。
4、实践写一个Hive Hook
4.1 在pom文件引入
<dependency> <groupId>org.apache.hive</groupId> <artifactId>hive-exec</artifactId> <version>${hive.version}</version> </dependency>
完整pom.xml:
<?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>org.example</groupId>
<artifactId>hive_hook</artifactId>
<version>1.0</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<hive.version>2.1.1</hive.version>
<java.version>1.8</java.version>
<hadoop.version>2.7.3</hadoop.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<log4j.version>2.12.1</log4j.version>
</properties>
<repositories>
<repository>
<id>apache.snapshots</id>
<name>Apache Development Snapshot Repository</name>
<url>https://repository.apache.org/content/repositories/snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<!--添加Hadoop的依赖-->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${hadoop.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--添加hive依赖-->
<dependency>
<groupId>org.apache.hive</groupId>
<artifactId>hive-exec</artifactId>
<version>${hive.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Java Compiler -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<!-- We use the maven-shade plugin to create a fat jar that contains all necessary dependencies. -->
<!-- Change the value of <mainClass>...</mainClass> if your program entry point changes. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.1</version>
<executions>
<!-- Run shade goal on package phase -->
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<excludes>
<exclude>org.apache.flink:force-shading</exclude>
<exclude>com.google.code.findbugs:jsr305</exclude>
<exclude>org.slf4j:*</exclude>
<exclude>log4j:*</exclude>
</excludes>
</artifactSet>
<filters>
<filter>
<!-- Do not copy the signatures in the META-INF folder.
Otherwise, this might cause SecurityExceptions when using the JAR. -->
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.xiaoe.WarehouseStreaming</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!-- This improves the out-of-the-box experience in Eclipse by resolving some warnings. -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<versionRange>[3.1.1,)</versionRange>
<goals>
<goal>shade</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore/>
</action>
</pluginExecution>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<versionRange>[3.1,)</versionRange>
<goals>
<goal>testCompile</goal>
<goal>compile</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore/>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
4.2、编码
实现 org.apache.hadoop.hive.ql.hooks.ExecuteWithHookContext 重写run方法
package com.zsigner;
import org.apache.hadoop.hive.ql.hooks.ExecuteWithHookContext;
import org.apache.hadoop.hive.ql.hooks.HookContext;
/**
* 编写一个hook 例子
* @author: Zsigner
* @create: 2021/03/29
*/
public class hookTest implements ExecuteWithHookContext {
@Override
public void run(HookContext hookContext) throws Exception {
System.out.println("the first pre hook");
}
}
4.3、打包,上传jar 到hive
mvn clean package
##将jar包放到该目录下
cd /usr/local/service/hive/lib
4.4、进入hive client当前有效
add jar /usr/local/service/hive/lib/hive_hook-1.0.jar;
set hive.exec.pre.hooks=com.zsigner.hookTest;
4.5、永久有效 编辑hive-site.xml
##进入hive 配置文件目录
/usr/local/service/hive/conf
##编辑hive-site.xml
vim hive-site.xml
添加:
<property>
<name>hive.exec.pre.hooks</name>
<value>com.zsigner.hookTest<value/>
</property>
更多例子如下:
hive.exec.pre.hooks
从名称可以看出,在执行引擎执行查询之前被调用。这个需要在 Hive 对查询计划进行过优化之后才可以使用。使用该Hooks需要实现接口:org.apache.hadoop.hive.ql.hooks.ExecuteWithHookContext,具体在hive-site.xml中的配置如下:
<property>
<name>hive.exec.pre.hooks</name>
<value>com.zsigner.hookTest<value/>
</property>
hive.exec.post.hooks
在执行计划执行结束结果返回给用户之前被调用。使用时需要实现接口:org.apache.hadoop.hive.ql.hooks.ExecuteWithHookContext,具体在hive-site.xml中的配置如下:
<property>
<name>hive.exec.post.hooks</name>
<value>com.zsigner.hookTest<value/>
</property>
hive.exec.failure.hooks
在执行计划失败之后被调用。使用时需要实现接口:org.apache.hadoop.hive.ql.hooks.ExecuteWithHookContext,具体在hive-site.xml中的配置如下:
<property>
<name>hive.exec.failure.hooks</name>
<value>com.zsigner.hookTest<value/>
</property>
hive.metastore.init.hooks
HMSHandler初始化是被调用。使用时需要实现接口:org.apache.hadoop.hive.metastore.MetaStoreInitListener,具体在hive-site.xml中的配置如下:
<property>
<name>hive.metastore.init.hooks</name>
<value>com.zsigner.hookTest<value/>
</property>
hive.exec.driver.run.hooks
在Driver.run开始或结束时运行,使用时需要实现接口:org.apache.hadoop.hive.ql.HiveDriverRunHook,具体在hive-site.xml中的配置如下:
<property>
<name>hive.exec.driver.run.hooks</name>
<value>com.zsigner.hookTest<value/>
</property>
hive.semantic.analyzer.hook
Hive 对查询语句进行语义分析的时候调用。使用时需要集成抽象类:org.apache.hadoop.hive.ql.parse.AbstractSemanticAnalyzerHook,具体在hive-site.xml中的配置如下:
<property>
<name>hive.semantic.analyzer.hook</name>
<value>com.zsigner.hookTest<value/>
</property>
4.6 把jar包分发到hive 集群下的各个节点,重启hive
4.7、hive hook优缺点
优点
1、可以很方便地在各种查询阶段嵌入或者运行自定义的代码
2、可以被用作更新元数据
缺点
1、当使用Hooks时,获取到的元数据通常需要进一步解析,否则很难理解
2、会影响查询的过程
4.8 拓展
更多实际运用hive hook列子
1.利用SemanticAnalyzerHook来过滤不加分区条件的Hive查询
2.有了Hook,可以实现例如非法SQL拦截,SQL收集和审计等功能,业界的案例可以参考Airbnb的reair :https://github.com/airbnb/reair
5、Metastore Listeners分类
以下内容来自:https://blog.csdn.net/weixin_39628864/article/details/111138752
hive.metastore.pre.event.listeners
需要扩展此抽象类,以提供在metastore上发生特定事件之前需要执行的操作实现。在metastore上发生事件之前,将调用这些方法。
使用时需要继承抽象类:org.apache.hadoop.hive.metastore.MetaStorePreEventListener,在Hive-site.xml中的配置为:
<property>
<name>hive.metastore.pre.event.listeners</name>
<value>实现类的全限定名</value>
</property>
hive.metastore.event.listeners
需要扩展此抽象类,以提供在metastore上发生特定事件时需要执行的操作实现。每当Metastore上发生事件时,就会调用这些方法。
使用时需要继承抽象类:org.apache.hadoop.hive.metastore.MetaStoreEventListener,在Hive-site.xml中的配置为:
<property>
<name>hive.metastore.event.listeners</name>
<value>实现类的全限定名</value>
</property>
hive.metastore.end.function.listeners
每当函数结束时,将调用这些方法。
使用时需要继承抽象类:org.apache.hadoop.hive.metastore.MetaStoreEndFunctionListener ,在Hive-site.xml中的配置为:
<property>
<name>hive.metastore.end.function.listeners</name>
<value>实现类的全限定名</value>
</property>
Metastore Listeners优缺点
优点
元数据已经被解析好了,很容易理解
不影响查询的过程,是只读的
缺点
不灵活,仅仅能够访问属于当前事件的对象
对于metastore listener,本文会给出 MetaStoreEventListener的使用案例,具体会实现两个方法:onCreateTable和onAlterTable
参考链接:
xml存储数据 优缺点_元数据管理|Hive Hooks和Metastore监听器介绍