hivehook 表血缘与字段血缘的解析

代码

java 复制代码
import org.apache.hadoop.hive.ql.hooks.ExecuteWithHookContext;
import org.apache.hadoop.hive.ql.hooks.HookContext;
import org.apache.hadoop.hive.ql.hooks.LineageInfo;
import org.apache.hadoop.hive.metastore.api.Table;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

public class HiveServerQueryLogHook implements ExecuteWithHookContext {
	static final Logger LOG = LoggerFactory.getLogger(HiveServerQueryLogHook.class);
	
    @Override
    public void run(HookContext hookContext) throws Exception {
        printLineageInfo(hookContext);
    }
    private void printLineageInfo(HookContext hookContext) {
        // 输出表
        Set<String> inputTables = new HashSet<>();;

        // 输入表
        Set<String> outputTables = new HashSet<>();;

        // 字段血缘 Map
        // key为输出字段,value为来源字段数组
        Map<String, ArrayList<String>> fieldLineage = new HashMap<>();
		
		// 从 `hookContext` 中获取 `Linfo` 并返回其 entry set,这意味着我们会获取到一个包含键值对的集合;遍历 `hookContext` 中 `Linfo` 的 entry set
        for(Map.Entry<LineageInfo.DependencyKey, LineageInfo.Dependency> dep: hookContext.getLinfo().entrySet()){
            // 表血缘
            // 将 `dep.getKey()` 转换为一个 Optional 对象,以防止空指针异常。
            Optional.ofNullable(dep.getKey())
            		// 如果 `dep.getKey()` 不为空,则将其转换为 `DataContainer` 对象。
                    .map(LineageInfo.DependencyKey::getDataContainer)
                    // 如果 `DataContainer` 不为空,则获取其表信息。
                    .map(LineageInfo.DataContainer::getTable)
                    // 将表信息传递给 `dealOutputTable` 方法进行处理。
                    .map(this::dealOutputTable)
                    // 如果处理后的结果不为空,则将其添加到 `outputTables` 集合中。
                    .ifPresent(outputTables::add);
            Optional.ofNullable(dep.getValue())
                    .map(LineageInfo.Dependency::getBaseCols)
                    .ifPresent(items -> items.stream().map(LineageInfo.BaseColumnInfo::getTabAlias)
                            .map(LineageInfo.TableAliasInfo::getTable)
                            .map(this::dealOutputTable)
                            .forEach(inputTables::add));

            // 字段血缘
            // 将 `dep.getKey()` 转换为一个 Optional 对象,以防止空指针异常。
            String column = Optional.ofNullable(dep.getKey())
            		// 如果 `dep.getKey()` 不为空,则将其传递给 `dealDepOutputField` 方法进行处理。
                    .map(this::dealDepOutputField)
                    // 如果处理后的结果不为空,则将其作为键放入 `fieldLineage` 中,并关联一个空的 ArrayList,然后将该键返回给 `column` 变量。

                    .map(aimField -> {
                        fieldLineage.put(aimField, new ArrayList<>());
                        return aimField;
                    // 如果处理后的结果为空,则将 `column` 设置为 null。
                    }).orElse(null);
                    // 将 `dep.getValue()` 转换为一个 Optional 对象,以防止空指针异常。
            Optional.ofNullable(dep.getValue())
            		// 如果 `dep.getValue()` 不为空,则获取其基础列信息。

                    .map(LineageInfo.Dependency::getBaseCols)
                    // 如果基础列信息不为空,则将其转换为流并依次处理,将处理后的结果添加到 `fieldLineage` 中对应 `column` 的列表中。
                    .ifPresent(items -> items.stream()
                            .map(this::dealBaseOutputField)
                            .forEach(item -> {
                                fieldLineage.get(column).add(item);
                            }));
        }
        LOG.info("inputTables : {} ",inputTables);
        LOG.info("outputTables : {} ",outputTables);
        LOG.info("fieldLineage : {} ",fieldLineage.toString());
    }
    // 处理表的格式为 库.表
    private String dealOutputTable(Table table) {
        String dbName = table.getDbName();
        String tableName = table.getTableName();
        return dbName != null ? String.format("%s.%s", dbName, tableName) : tableName;
    }

    // 处理输出字段的格式
    private String dealDepOutputField(LineageInfo.DependencyKey dependencyKey) {
        try{
            String tableName = dealOutputTable(dependencyKey.getDataContainer().getTable());
            String field = dependencyKey.getFieldSchema().getName();
            return String.format("%s.%s", tableName, field);
        }catch (Exception e) {
            LOG.error("deal dep output field error" + e.getMessage());
            return null;
        }
    }

    // 处理来源字段的格式
    private String dealBaseOutputField(LineageInfo.BaseColumnInfo baseColumnInfo) {
        try{
            String tableName = dealOutputTable(baseColumnInfo.getTabAlias().getTable());
            String field = baseColumnInfo.getColumn().getName();
            return String.format("%s.%s", tableName, field);
        }catch (Exception e) {
            LOG.error("deal base output field error" + e.getMessage());
            return null;
        }
    }

配置

编译后生成jar文件添加到hive运行环境,设置hook

powershell 复制代码
1)jar放置/disk1/hive-jars/hook

2)设置env,conf/hive-env.sh
pushd /disk1/hive-jars/hook
export HOOK_DEPS=$(ls *.jar| xargs -Ixx echo "`pwd`/xx" | sort | tr '\n' ':')
popd
export HIVE_AUX_JARS_PATH=${xxx%:}:${HOOK_DEPS%:}

3)修改hive-site.xml
  <property>
    <name>hive.exec.post.hooks</name>
    <value>com.xxx.xxx.HiveServerQueryLogHook</value>
    <description>
      Comma-separated list of post-execution hooks to be invoked for each statement.
      A post-execution hook is specified as the name of a Java class which implements the
      org.apache.hadoop.hive.ql.hooks.ExecuteWithHookContext interface.
    </description>
  </property>

测试

sql

sql 复制代码
use db_dev;
CREATE TABLE IF NOT EXISTS `all_report_creator`(
`project_id` INT COMMENT '项目组id',
`report_id` INT COMMENT '报告id',
`creator_id` INT COMMENT '报告创建者id',
`nick` STRING COMMENT 'nick名字'
) 
STORED AS PARQUET;
insert overwrite table all_report_creator
select
t1.project_id,t1.id,t2.id,t2.nick from db.new_report t1 left join db.bigviz_user t2 on t1.creator_id = t2.id where t1.project_id in(7,24)

血缘

powershell 复制代码
24/07/12 13:34:31 INFO xxx.HiveServerQueryLogHook: inputTables : [db.new_report, db.bigviz_user]
24/07/12 13:34:31 INFO xxx.HiveServerQueryLogHook: outputTables : [db_dev.all_report_creator]
24/07/12 13:34:31 INFO xxx.HiveServerQueryLogHook: fieldLineage : {db_dev.all_report_creator.project_id=[db.new_report.project_id], db_dev.all_report_creator.report_id=[db.new_report.id], db_dev.all_report_creator.creator_id=[db.bigviz_user.id], db_dev.all_report_creator.nick=[db.bigviz_user.nick]}

参考

HIVE源码学习-hivehook尝试表血缘与字段血缘的解析

http://ganjiacheng.cn/article/2020/article_16_HIVE源码学习-hivehook尝试血缘解析/

相关推荐
clorinda9 分钟前
Hive 窗口函数详解:让数据在分组中完成排名、累计和跨行计算
数据仓库·hive·hadoop
陈年老古董2 小时前
Hive 文件格式与查询学习笔记
hive·笔记·学习
卷毛迷你猪4 天前
快速实验篇(B12)异常流量与刷单识别(方法论演示)
大数据·hive·hadoop·数据挖掘·聚类
卷毛迷你猪5 天前
快速实验篇(B07 )Session 会话化与序列
大数据·hive·hadoop
卷毛迷你猪5 天前
快速实验篇(B08)搜索词分析实战
大数据·hive·hadoop
卷毛迷你猪5 天前
快速实验篇(B10)品类共现与关联规则实战
大数据·hive·hadoop
卷毛迷你猪5 天前
快速实验篇(B09)城市维度分析,均匀随机字段的识别与证明
大数据·hive·hadoop
卷毛迷你猪6 天前
快速实验篇(B06)页面单跳转化率
大数据·hive·hadoop
卷毛迷你猪7 天前
快速实验篇(B02)DWD 生产级升级
大数据·hive·hadoop
卷毛迷你猪7 天前
快速实验篇(B03) 用户行为漏斗与转化率
大数据·hive·hadoop