# 项目难点

# gojs 制作数据血缘可视化

在做项目的时候,遇到了一个需求,需要做类似下面原型图的效果

拿到图之后,经各方查找,最终决定用gojs (opens new window)来做

注意:学习 gojs 是有成本的,需要一定时间,我根据官方文档写了一篇简单的gojs 入门教程

image-20211010221722952

# 数据血缘基本概念

数据血缘分析属于元数据管理的重要一部分,能够使开发者直观的了解数据的来龙去迈

广义上,血缘分析包含以下 3 个级别:

  • 任务级别

    • 大数据平台当中的数据,往往由一个个任务生成,虽然在不同的应用系统中虽然有不同的名字,如 Yarn 中的 application、Oozie 中的 Job、Spark/MR/Hive 中的 Job,但本质上都是同一类东西:都有 input/output Data,有的 Job 会有多个 input/output Data
    • 通过查看任务级别的血缘关系,可以了解到更高层级的信息,如服务器、运行时长、等待时长、当前任务流状态等
  • 数据级别

    • 数据,也叫表、目录等,广义上包括 HDFS、HBase、关系型数据库、Kafka、Ftp、本地文件等。
    • 通过查看数据级别的血缘关系,可以看到:
      • 表的依赖链条
      • 表的重要程度(后续的使用者多少)
      • 表的基础信息
    • 进而可以基于此,做一些数据质量、影响分析的工作
  • 字段级别

    • 在实际使用中,有时会有一些稍显苛刻的需求,如更改字段的影响有多大、字段是如何产生的等,此时就需要字段级别的血缘关系,按照 Hive 当中的定义,分为两种:Projection 和 Predicate

      • Projection

        投影,只影响单一输出字段

      • Predicate

        断言,影响所有输出字段

    • 实际应用中,由于 Predicate 影响所有字段,绘制到图标当中会产生很多条线,因此,我们默认展示 Projection

# Apache Atlas 如何生成血缘数据

注入元数据信息到 Atlas 中(本质上是写入元数据到 Atlas 中)

元数据Metadata),又称中介数据中继数据,为描述数据 (opens new window)的数据(data about data),主要是描述数据属性 (opens new window)(property)的信息 (opens new window),用来支持如指示存储位置、历史 (opens new window)数据、资源 (opens new window)查找、文件记录等功能。

Apache Atlas 是 Hadoop 社区为解决 Hadoop 生态系统的元数据治理问题而产生的开源项目,它为 Hadoop 集群提供了包括数据分类、集中策略引擎、数据血缘、安全和生命周期管理在内的元数据治理核心能力。

  1. 方式一:通过 Atlas 为数据系统开发好的 hook 来注入

    • sqoop 同步自动生成血缘数据

      sqoop 同步 MySQL 数据库数据到 hive,同步成功后,通过 sqoop 的 Atlas Hook 自动生成血缘数据

      sqoop 将 MySQL 数据库所有表数据同步到 hive 仓库命令:

      sqoop import-all-tables --connect jdbc:mysql://192.168.1.1:3306/testdb --username root --password ** --hive-import --hive-database testdb --m 1

  2. 方式二:通过 Atlas 自带的 web-ui 来填写元数据信息注入

  3. 方式三:通过调用 Atlas 对外暴露的 RestAPI 接口来灵活注入

    • 通过 Atlas 的 RestAPI 接口新增 Process,可以生成血缘数据。

      将 Atlas 元数据管理的 MySQL 数据库表和 hive 数据表关联生成血缘数据,先查到两张表的 guid 值,然后构造请求数据调用接口:http://{atlas_host}:21000/api/atlas/v2/entity/bulk

      请求消息:

      {"entities":[{"typeName":"Process","attributes":{"owner":"root","createTime":"2020-05-07T10:32:21.0Z","updateTime":"","qualifiedName":"people@process@mysql://192.168.1.1:3306","name":"peopleProcess","description":"people Process","comment":"test people Process","contact_info":"jdbc","type":"table","inputs":[{"guid": "5a676b74-e058-4e81-bcf8-42d73f4c1729","typeName": "rdbms_table"}],"outputs":[{"guid": "2e7c70e1-5a8a-4430-859f-c46d267e33fd","typeName": "hive_table"}]}}]}

hive 建表语句自动生成血缘数据

  • hive 执行 hive SQL 语句create table t2 as select id, name from T1创建表,会自动生成表的血缘数据以及字段级的血缘数据。
  • Hive 2.2.0 以下的低版本存在 bug,字段级的血缘数据不能自动生成,需升级 hive 版本到 2.2.0 及以上才能正常生成字段级的血缘数据。

如何修改 Atlas 的元数据

  1. 方式一:通过 Atlas 自带的 web-ui 界面来修改元数据信息
  2. 方式二:调用 Atlas 接口更加灵活 的修改元数据信息

# 如何收集血缘关系数据

考虑到数据来源的多样性,我们需要为每个数据分配唯一的标识 ID,参考《WhereHows (opens new window)》中的设计,不同数据源的数据,经过 ETL 进入元数据系统时,由元数据系统唯一分配 ID

ETL,是英文 Extract-Transform-Load 的缩写,用来描述将数据 (opens new window)从来源端经过抽取(extract)、转换 (opens new window)(transform)、加载(load)至目的端的过程。ETL一词较常用在数据仓库 (opens new window),但其对象并不限于数据仓库。

解决了数据 ID 标识的问题,另一个难点在于数据流转关系的收集,针对不同的数据处理方式,收集方式也不一样

例如

  • Hive
    • Hive 提供了 Hook 机制,在 Hive 编译、执行的各个阶段,可以调用参数配置的各种 Hook
    • 利用hive.exec.post.hooks这个钩子,在每条语句执行结束后自动调用该钩子
    • org.apache.hadoop.hive.ql.hooks.LineageLogger的 run 方法中,调用org.apache.hadoop.hive.ql.tools.LineageInfo中实例的getLineageInfo,getInputTableList,getOutputTableList就可以获取相关血缘信息
  • Spark
    • 类似于 Hive,可以通过自定义org.apache.spark..SparkListener
    • spark/conf中指定spark.sql.queryExecutionListenersspark.extraListeners

最后将前面收集到的原始信息做一次ETL处理,与元数据系统中的 ID 库做一个映射,进而封装成JSON数据,提供给前端展示

在 Apache Atlas 框架中,可以通过 Rest API 查询血缘数据

get 请求:http://{atlas_host}:21000/api/atlas/v2/lineage/01d12e5f-1ef5-46a8-ac13-29be71e8f78e

# 获取异步挂载的 DOM 元素

做项目遇到一个布局,如下图,它放置在页面的左侧边栏作为筛选框,这里我们只关注具体实现

需求是这样的:

  1. 每一行有很多radio也就是单选框,在一行放置不下的时候,显示更多这个按钮,否则不显示
  2. 点击更多按钮展示所有的radio,并显示收缩按钮,按钮的位置始终在右侧
  3. 每个radio从左到右顺序排列,长度不唯一,可长可短

image-20211010223724510

很容易想到,做一个通用组件,传入labeloptions,因为options是异步请求回来的数据,所以一开始options是为空的,当options为空数组的时候不显示该组件,通过v-if去控制显示整个组件,这样子该组件就变成一个异步组件了,如果你尝试在组件内部mounted生命周期函数中,通过ref或者其他获取dom元素的方式去获取是无法获取到的,因为组件渲染挂载是异步的,而mounted等生命周期钩子执行是同步的,所以问题就变成如何获取异步挂载组件的 dom 元素

先说为什么要解决上面这个问题,因为我们需要获取存放radio元素外面的 DOM 容器,然后每次加一个radio进去,判断是否超出存放所有radio元素+更多按钮的宽度,如果超出,则不再添加,这个操作我们是递归去实现的,执行递归的时机就是异步组件挂载后的时机,也只执行一次

解决方法:

  1. 通过watch监听options的变化,当options从空数组变为非空数组的时候,这个时候v-if条件判断为true开始渲染子组件,这时候只需要通过nextick即可获取dom元素

    举例:在子组件中监听options的变化

    watch: {
        options(val) {
            if (val.length != 0) {
                this.$nextTick(function () {console.log(this.$refs)});
            }
        }
    },
    
    1
    2
    3
    4
    5
    6
    7
  2. 通过自定义指令的生命周期函数mounted,它在绑定元素的父组件被挂载后调用

    举例:在子组件中自定义局部指令

    directives: {
        realMounted: {
            <!-- vue2.x写法 -->
            inserted(el, binding, vnode, prevVnode) {
                binding.value()
            },
    
            <!-- vue3.x写法 -->
            mounted(el, binding, vnode, prevVnode) {
                binding.value()
            },
        }
    },
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    v-if的元素(或者内部任意元素)添加指令,绑定一个函数即可

    <div v-if="options.length != 0" v-realMounted="realMounted">
        <div v-for="i in options" :key="i">{{i}}</div>
        <div ref="child1"></div>
        <div ref="child2"></div>
    </div>
    
    1
    2
    3
    4
    5

    这样绑定指令的元素真正挂载的时候就会调用绑定的realMounted函数

  3. vue2.x 通过 hook:mounted,vue3.x 通过 vnode-mounted 事件监听 VNode 挂载时机

  • vue2.x 写法

    <div v-if="options.length != 0" @hook:mounted="realMounted">
        <div v-for="i in options" :key="i">{{i}}</div>
        <div ref="child1"></div>
        <div ref="child2"></div>
    </div>
    
    1
    2
    3
    4
    5
  • vue3.x 写法

    <div v-if="options.length0" @vnode-mounted="realMounted">
        <div v-for="i in options" :key="i">{{i}}</div>
        <div ref="child1"></div>
        <div ref="child2"></div>
    </div>
    
    1
    2
    3
    4
    5

    这样绑定事件的元素真正挂载的时候就会调用绑定的realMounted函数

# 参考

gojs 中文文档 (opens new window)

Hadoop 的元数据治理--Apache Atlas (opens new window)

Apache Atlas 元数据血缘关系(Lineage)功能研究 (opens new window)

大数据血缘分析系统设计 (opens new window)

最近更新: 4 小时前