随着公司平台用户数量与表数量的不断增多,各种表之间的数据流向也变得更加复杂,特别是某个任务中会对源表读取并进行一系列复杂的变换后又生成新的数据表,因此需要一套表血缘关系解析机制能清晰地解析出每个任务所形成的表血缘关系链。

实现思路:

spark对sql的操作会形成一个dataframe,dataframe中的logicplan包含了sql的语法树,通过对logicplan的语法树解析可以获取当前stage所操作的输入表和输出表,将整套表关系链连接起来,再去除中间表即可获取当前作业的输入表和输出表信息。

核心代码:

def resolveLogicPlan(plan: LogicalPlan, currentDB:String): (util.Set[DcTable], util.Set[DcTable]) ={
val inputTables = new util.HashSet[DcTable]()
val outputTables = new util.HashSet[DcTable]()
resolveLogic(plan, currentDB, inputTables, outputTables)
Tuple2(inputTables, outputTables)
} def resolveLogic(plan: LogicalPlan, currentDB:String, inputTables:util.Set[DcTable], outputTables:util.Set[DcTable]): Unit ={
plan match { case plan: Project =>
val project = plan.asInstanceOf[Project]
resolveLogic(project.child, currentDB, inputTables, outputTables) case plan: Union =>
val project = plan.asInstanceOf[Union]
for(child <- project.children){
resolveLogic(child, currentDB, inputTables, outputTables)
} case plan: Join =>
val project = plan.asInstanceOf[Join]
resolveLogic(project.left, currentDB, inputTables, outputTables)
resolveLogic(project.right, currentDB, inputTables, outputTables) case plan: Aggregate =>
val project = plan.asInstanceOf[Aggregate]
resolveLogic(project.child, currentDB, inputTables, outputTables) case plan: Filter =>
val project = plan.asInstanceOf[Filter]
resolveLogic(project.child, currentDB, inputTables, outputTables) case plan: Generate =>
val project = plan.asInstanceOf[Generate]
resolveLogic(project.child, currentDB, inputTables, outputTables) case plan: RepartitionByExpression =>
val project = plan.asInstanceOf[RepartitionByExpression]
resolveLogic(project.child, currentDB, inputTables, outputTables) case plan: SerializeFromObject =>
val project = plan.asInstanceOf[SerializeFromObject]
resolveLogic(project.child, currentDB, inputTables, outputTables) case plan: MapPartitions =>
val project = plan.asInstanceOf[MapPartitions]
resolveLogic(project.child, currentDB, inputTables, outputTables) case plan: DeserializeToObject =>
val project = plan.asInstanceOf[DeserializeToObject]
resolveLogic(project.child, currentDB, inputTables, outputTables) case plan: Repartition =>
val project = plan.asInstanceOf[Repartition]
resolveLogic(project.child, currentDB, inputTables, outputTables) case plan: Deduplicate =>
val project = plan.asInstanceOf[Deduplicate]
resolveLogic(project.child, currentDB, inputTables, outputTables) case plan: Window =>
val project = plan.asInstanceOf[Window]
resolveLogic(project.child, currentDB, inputTables, outputTables) case plan: MapElements =>
val project = plan.asInstanceOf[MapElements]
resolveLogic(project.child, currentDB, inputTables, outputTables) case plan: TypedFilter =>
val project = plan.asInstanceOf[TypedFilter]
resolveLogic(project.child, currentDB, inputTables, outputTables) case plan: Distinct =>
val project = plan.asInstanceOf[Distinct]
resolveLogic(project.child, currentDB, inputTables, outputTables) case plan: SubqueryAlias =>
val project = plan.asInstanceOf[SubqueryAlias]
val childInputTables = new util.HashSet[DcTable]()
val childOutputTables = new util.HashSet[DcTable]() resolveLogic(project.child, currentDB, childInputTables, childOutputTables)
if(childInputTables.size()>){
for(table <- childInputTables) inputTables.add(table)
}else{
inputTables.add(DcTable(currentDB, project.alias))
} case plan: CatalogRelation =>
val project = plan.asInstanceOf[CatalogRelation]
val identifier = project.tableMeta.identifier
val dcTable = DcTable(identifier.database.getOrElse(currentDB), identifier.table)
inputTables.add(dcTable) case plan: UnresolvedRelation =>
val project = plan.asInstanceOf[UnresolvedRelation]
val dcTable = DcTable(project.tableIdentifier.database.getOrElse(currentDB), project.tableIdentifier.table)
inputTables.add(dcTable) case plan: InsertIntoTable =>
val project = plan.asInstanceOf[InsertIntoTable]
resolveLogic(project.table, currentDB, outputTables, inputTables)
resolveLogic(project.query, currentDB, inputTables, outputTables) case plan: CreateTable =>
val project = plan.asInstanceOf[CreateTable]
if(project.query.isDefined){
resolveLogic(project.query.get, currentDB, inputTables, outputTables)
}
val tableIdentifier = project.tableDesc.identifier
val dcTable = DcTable(tableIdentifier.database.getOrElse(currentDB), tableIdentifier.table)
outputTables.add(dcTable) case plan: GlobalLimit =>
val project = plan.asInstanceOf[GlobalLimit]
resolveLogic(project.child, currentDB, inputTables, outputTables) case plan: LocalLimit =>
val project = plan.asInstanceOf[LocalLimit]
resolveLogic(project.child, currentDB, inputTables, outputTables) case `plan` => logger.info("******child plan******:\n"+plan)
}
}

上述代码是对logicplan做递归解析的,当logicplan为LocalLimit、GlobalLimit、Window等类型时,继续解析其子类型;当logicplan为CataLogRelation、UnresolvedRelation时,解析出的表名作为输入表;当logicplan为CreateTable、InsertIntoTable时,解析出的表名为输出表。

这里需要考虑一种特殊情况,某些源表是通过spark.read加载得到的,这样logicplan解析出来的类型为LogicRDD,不能直接获取到表名,以下面的python代码为例:

schema = StructType([StructField('id', IntegerType(), True), StructField('name', StringType(), True), StructField('age', IntegerType(), True)])
rdd = sparkSession.sparkContext.textFile('/user/hive/warehouse/bigdata.db/tdl_spark_test/testdata.txt').map(lambda r:r.split(',')).map(lambda p: Row(int(p[]), p[], int(p[])))
df = sparkSession.createDataFrame(rdd, schema)
df.createOrReplaceTempView('tdl_spark_test')
sparkSession.sql('create table tdl_file_test as select * from tdl_spark_test')

上述代码首先通过textFile读取文件得到rdd,再对rdd进行变换,最后将rdd注册成dataframe,这里对df的logicplan进行解析会得到LogicRDD,对于这种情况的解决思路是在调用textFile时记录产生的rdd,解析df的logicplan时获取其rdd,判断之前产生的rdd是否为当前rdd的祖先,如果是,则将之前rdd对应的表名计入。

判断rdd依赖关系的逻辑为:

def checkRddRelationShip(rdd1:RDD[_], rdd2:RDD[_]): Boolean ={
if (rdd1.id == rdd2.id) return true
dfsSearch(rdd1, rdd2.dependencies)
} def dfsSearch(rdd1:RDD[_], dependencies:Seq[Dependency[_]]): Boolean ={
for(dependency <- dependencies){
if(dependency.rdd.id==rdd1.id) return true
if(dfsSearch(rdd1, dependency.rdd.dependencies)) return true
}
false
}

对LogicRDD的解析为:

case plan: LogicalRDD =>
val project = plan.asInstanceOf[LogicalRDD]
try{
for(rdd <- rddTableMap.keySet()){
if(checkRddRelationShip(rdd, project.rdd)){
val tableName = rddTableMap.get(rdd)
val db = StringUtils.substringBefore(tableName, ".")
val table = StringUtils.substringAfter(tableName, ".")
inputTables.add(DcTable(db, table))
}
}
}catch {
case e:Throwable => logger.error("resolve LogicalRDD error:", e)
}

在spark中会生成dataframe的代码段中通过aspect进行拦截,并且解析dataframe得到表的关系链,此时的关系链是一张有向无环图,图中可能包含中间表,去除掉中间表节点,则得到最终的数据流向图。

例如上图的左边是一张原始的表数据流向,其中tempC和tempE为临时表,去除这个图中的临时表节点,得到右图的数据流向图。对于前面给出的python代码,执行过后获取的数据流向为:

 [bigdata.tdl_spark_test]--->bigdata.tdl_file_test

当然这种解析方式也存在一些缺点,比如首先通过spark.read读取数据注册一张临时表,再将临时表中的某些字段值拉到本地缓存,然后创建一个空的datadrame,将缓存的字段值直接插入到该df中,由于当前创建的df与之前创建的df已经没有依赖关系,因此这种情况将无法解析出准确的数据流向。

基于spark logicplan的表血缘关系解析实现的更多相关文章

  1. 基于Spark GraphX计算二度关系

    关系计算问题描述 二度关系是指用户与用户通过关注者为桥梁发现到的关注者之间的关系.目前微博通过二度关系实现了潜在用户的推荐.用户的一度关系包含了关注.好友两种类型,二度关系则得到关注的关注.关注的好友 ...

  2. 苏宁基于Spark Streaming的实时日志分析系统实践 Spark Streaming 在数据平台日志解析功能的应用

    https://mp.weixin.qq.com/s/KPTM02-ICt72_7ZdRZIHBA 苏宁基于Spark Streaming的实时日志分析系统实践 原创: AI+落地实践 AI前线 20 ...

  3. 基于MaxCompute InformationSchema进行血缘关系分析

    一.需求场景分析 在实际的数据平台运营管理过程中,数据表的规模往往随着更多业务数据的接入以及数据应用的建设而逐渐增长到非常大的规模,数据管理人员往往希望能够利用元数据的分析来更好地掌握不同数据表的血缘 ...

  4. 基于Extjs的web表单设计器 第七节——取数公式设计之取数公式的使用

    基于Extjs的web表单设计器 基于Extjs的web表单设计器 第一节 基于Extjs的web表单设计器 第二节——表单控件设计 基于Extjs的web表单设计器 第三节——控件拖放 基于Extj ...

  5. 基于 Spark 的文本情感分析

    转载自:https://www.ibm.com/developerworks/cn/cognitive/library/cc-1606-spark-seniment-analysis/index.ht ...

  6. 京东基于Spark的风控系统架构实践和技术细节

    京东基于Spark的风控系统架构实践和技术细节 时间 2016-06-02 09:36:32  炼数成金 原文  http://www.dataguru.cn/article-9419-1.html ...

  7. 血缘关系分析工具SQLFLOW--实践指南

    SQLFlow 是用于追溯数据血缘关系的工具,它自诞生以来以帮助成千上万的工程师即用户解决了困扰许久的数据血缘梳理工作. 数据库中视图(View)的数据来自表(Table)或其他视图,视图中字段(Co ...

  8. Django 一对多,多对多关系解析

    [转]Django 一对多,多对多关系解析   Django 的 ORM 有多种关系:一对一,多对一,多对多. 各自定义的方式为 :        一对一: OneToOneField         ...

  9. mybatis整合spring 之 基于接口映射的多对一关系

    转载自:http://my.oschina.net/huangcongmin12/blog/83731 mybatis整合spring 之  基于接口映射的多对一关系. 项目用到俩个表,即studen ...

随机推荐

  1. 在动态sql的使用where时,if标签判断中,如果实体类中的某一个属性是String类型,那么就可以这样来判断连接语句:

    在动态sql的使用where时,if标签判断中,如果实体类中的某一个属性是String类型,那么就可以这样来判断连接语句: 如果是String类型的字符串进行判空的时候: <if test=&q ...

  2. Python3中BeautifulSoup的使用方法

    BeautifulSoup的使用 我们学习了正则表达式的相关用法,但是一旦正则写的有问题,可能得到的就不是我们想要的结果了,而且对于一个网页来说,都有一定的特殊的结构和层级关系,而且很多标签都有id或 ...

  3. Python 利用random库来实现圆周率的运算

    蒙特卡罗方法求解圆周率 随机向一个正方形以及其内切圆(半径为1)的图形中随机抛洒大量的点,计算每个点到圆心的距离从而判断该点在圆内或圆外,用圆内的点除以总点数就是π/4的值.点数越多,值就越精确. 具 ...

  4. 《The One!团队》第八次作业:ALPHA冲刺(一)

    项目 内容 作业所属课程 所属课程 作业要求 作业要求 团队名称 < The One !> 作业学习目标 (1)掌握软件测试基础技术.(2)学习迭代式增量软件开发过程(Scrum) 第一天 ...

  5. HDFS中的数据块(Block)

    我们在分布式存储原理总结中了解了分布式存储的三大特点: 数据分块,分布式的存储在多台机器上 数据块冗余存储在多台机器以提高数据块的高可用性 遵从主/从(master/slave)结构的分布式存储集群 ...

  6. nginx添加系统服务(start|stop|restart|reload)

    nginx添加系统服务 1.编写脚本,名为nginx #vim /etc/init.d/nginx #!/bin/bash#chkconfig: - 99 20 #description: Nginx ...

  7. 织梦cms 后台查看会员性别为空

    织梦cms会员注册选择性别为保密时,在后台会员中心查看性别为空的修改方法 第一步:找到dede/templets/member_view.htm,在引号中添加保密就ok 再次修改后的结果

  8. Apache Kylin在4399大数据平台的应用

    来自:AI前线(微信号:ai-front),作者:林兴财,编辑:Natalie作者介绍:林兴财,毕业于厦门大学计算机科学与技术专业.有多年的嵌入式开发.系统运维经验,现就职于四三九九网络股份有限公司, ...

  9. learning java AWT EventQs

    import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.Ac ...

  10. http2 3

    HTTP 2 推荐阅读:https://segmentfault.com/a/1190000011172823?utm_source=tag-newest 进来支持 HTTP 2 的网站越来愈多了,这 ...