# 项目难点
# gojs 制作数据血缘可视化
在做项目的时候,遇到了一个需求,需要做类似下面原型图的效果
拿到图之后,经各方查找,最终决定用gojs (opens new window)来做
注意:学习 gojs 是有成本的,需要一定时间,我根据官方文档写了一篇简单的gojs 入门教程
# 数据血缘基本概念
数据血缘分析属于元数据管理的重要一部分,能够使开发者直观的了解数据的来龙去迈
广义上,血缘分析包含以下 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 集群提供了包括数据分类、集中策略引擎、数据血缘、安全和生命周期管理在内的元数据治理核心能力。
方式一:通过 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
方式二:通过 Atlas 自带的 web-ui 来填写元数据信息注入
方式三:通过调用 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 的元数据
- 方式一:通过 Atlas 自带的 web-ui 界面来修改元数据信息
- 方式二:调用 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.queryExecutionListeners
和spark.extraListeners
- 类似于 Hive,可以通过自定义
最后将前面收集到的原始信息做一次ETL
处理,与元数据系统中的 ID 库做一个映射,进而封装成JSON
数据,提供给前端展示
在 Apache Atlas 框架中,可以通过 Rest API 查询血缘数据
get 请求:
http://{atlas_host}:21000/api/atlas/v2/lineage/01d12e5f-1ef5-46a8-ac13-29be71e8f78e
# 获取异步挂载的 DOM 元素
做项目遇到一个布局,如下图,它放置在页面的左侧边栏作为筛选框,这里我们只关注具体实现
需求是这样的:
- 每一行有很多
radio
也就是单选框,在一行放置不下的时候,显示更多
这个按钮,否则不显示 - 点击
更多
按钮展示所有的radio
,并显示收缩
按钮,按钮的位置始终在右侧 - 每个
radio
从左到右顺序排列,长度不唯一,可长可短
很容易想到,做一个通用组件,传入label
和options
,因为options
是异步请求回来的数据,所以一开始options
是为空的,当options
为空数组的时候不显示该组件,通过v-if
去控制显示整个组件,这样子该组件就变成一个异步组件了,如果你尝试在组件内部mounted
生命周期函数中,通过ref
或者其他获取dom
元素的方式去获取是无法获取到的,因为组件渲染挂载是异步的,而mounted
等生命周期钩子执行是同步的,所以问题就变成如何获取异步挂载组件的 dom 元素
先说为什么要解决上面这个问题,因为我们需要获取存放radio
元素外面的 DOM 容器,然后每次加一个radio
进去,判断是否超出存放所有radio
元素+更多
按钮的宽度,如果超出,则不再添加,这个操作我们是递归去实现的,执行递归的时机就是异步组件挂载后的时机,也只执行一次
解决方法:
通过
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通过自定义指令的生命周期函数
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
函数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
5vue3.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
函数
# 参考
Hadoop 的元数据治理--Apache Atlas (opens new window)
← 知识点