hive遍历_MLSQL系列: 教你如何实现 Hive 列权限控制

8214a8c0a8453ca6802d589128270952.png

大数据安全问题至关重要,基于Hive创建的数据仓库也很普遍,提起Hive权限控制,首先能想到的可能就是Apache Ranger,通过拦截Hive Thrift Sever请求的方式,进行SQL解析与权限认证。但对于Spark SQL来讲,更倾向于直接使用SQL访问Hive,而不是通过JDBC方式。因此对于Spark SQL访问Hive,就需要用户自己去解决安全问题。今天,笔者以解析Spark SQL查询计划的方式,来提取库表列的信息,从而去做权限认证。

在开始之前,先讲一下Spark SQL的逻辑计划:

fea914135589122cfbca3be5f9997746.png

逻辑算子树的生成过程经历三个阶段:

  1. 由SparkSqlParser中的AstBuilder访问各种context节点,生成未解析的逻辑算子树,此时未绑定数据信息(数据源信息、列信息等);

  2. Analyzer在Unresolved LogicalPlan上应用一系列Rule,绑定数据信息(数据源信息、列信息等),生成解析后的逻辑算子树;

  3. 应用各种优化Rule在保证语义正确的前提下对一些低效的逻辑计划进行转换等,生成优化的逻辑算子树。

下面通过执行一段SQL来感受下:
//表信息CREATE TABLE if not exists respect.test(id string,name string,  age int,  dt string)//Spark SQL代码  val spark = SparkSession      .builder()      .enableHiveSupport()      .master("local[*]")      .getOrCreate()    val sql =       """        |select name,        |       max_age,        |       "mlsql" mlsql        |  from(        |    select name,        |           max(age) max_age        |      from respect.test        |     where dt <= 20200501        |     group by name) t      """.stripMargin    val df = spark.sql(sql)    df.explain(true)
输出:
== Parsed Logical Plan =='Project ['name, 'max_age, mlsql AS mlsql#1]+- 'SubqueryAlias `t`   +- 'Aggregate ['name], ['name, 'max('age) AS max_age#0]      +- 'Filter ('dt <= 20200501)         +- 'UnresolvedRelation `respect`.`test`== Analyzed Logical Plan ==name: string, max_age: int, mlsql: stringProject [name#12, max_age#0, mlsql AS mlsql#1]+- SubqueryAlias `t`   +- Aggregate [name#12], [name#12, max(age#13) AS max_age#0]      +- Filter (cast(dt#14 as int) <= 20200501)         +- SubqueryAlias `respect`.`test`            +- HiveTableRelation `respect`.`test`, org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe, [id#11, name#12, age#13, dt#14]== Optimized Logical Plan ==Aggregate [name#12], [name#12, max(age#13) AS max_age#0, mlsql AS mlsql#1]+- Project [name#12, age#13]   +- Filter (isnotnull(dt#14) && (cast(dt#14 as int) <= 20200501))      +- HiveTableRelation `respect`.`test`, org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe, [id#11, name#12, age#13, dt#14]
可以看出,通过分析解析后的逻辑计划就可以提取出库表列的信息。请注意一点,在Project [name#18, max_age#8]中,#18中的18是name的exprId,后面会涉及到。 然后在了解一下Spark的TreeNode体系:

680747fa074c5e2d2a5e50f559cd6fa1.png

f268245d8b6c9df7d0307afedd5742eb.png

从上图中可以看出,逻辑计划属于TreeNode体系,因此它可以使用TreeNode的所有方法。

下面来看MLSQL中是如何实现的(MLSQLDFParser类):

def extractTableWithColumns(df: DataFrame) = {    val tableAndCols = mutable.HashMap.empty[String, mutable.HashSet[String]]    val relationMap = new mutable.HashMap[Long, String]()    val analyzed = df.queryExecution.analyze
对于Hive表,Relation包含两种:1. HiveTableRelation,2. 有catalogTable的LogicalRelation。 首先通过collectLeaves()找到所有的叶子节点(Relation一定是叶子节点),匹配出以上两种Relation。然后记录每一个属性(对应Hive中的列)的exprId与数据库表的对应关系。 比如上述SQL:17->respect.test  (17为id的exprId)

(HiveTableRelation `respect`.`test`, org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe, [id#17, name#18, age#19, dt#20])

   //只对Hive表处理,临时表不需要授权    analyzed.collectLeaves().foreach { lp =>      lp match {case r: HiveTableRelation =>          r.dataCols.foreach(c =>            relationMap.put(c.exprId.id              , r.tableMeta.identifier.toString().replaceAll("`", ""))          )case r: LogicalRelation =>          r.attributeMap.foreach(c =>if (r.catalogTable.nonEmpty) {              relationMap.put(c._2.exprId.id                , r.catalogTable.get.identifier.toString().replaceAll("`", ""))            }          )case _ =>      }    }
从分析的查询计划中可看出,SQL中使用的列(Select、Where、On等)都要经过Project或Aggregate,因此只需要收集Project和Aggregate的属性(对应Hive中的列)。 通过analyzed.map递归遍历所有节点,收集Project和Aggregate的属性。
//如果查询SQL中包含Hive表if (relationMap.nonEmpty) {     val neSet = mutable.HashSet.empty[NamedExpression]     analyzed.map { lp =>        lp match {case wowLp: Project =>            wowLp.projectList.map { item =>              item.collectLeaves().foreach(                _ match {case ne: NamedExpression => neSet.add(ne)case _ =>                }              )            }case wowLp: Aggregate =>            wowLp.aggregateExpressions.map { item =>              item.collectLeaves().foreach(                _ match {case ne: NamedExpression => neSet.add(ne)case _ =>                }              )            }case _ =>        }      }
exprId->数据库表的对应关系与exprId->列名的对应关系做交集,就可以计算出数据库表与查询的所有列的对应关系。
//相当于记录列的exprId与列名的对应关系      val neMap = neSet.zipWithIndex.map(ne => (ne._1.exprId.id ,ne._1)).toMap      relationMap.foreach { x =>if (neMap.contains(x._1)) {          val dbTable = x._2          val value = tableAndCols.getOrElse(dbTable, mutable.HashSet.empty[String])          value.add(neMap.get(x._1).get.name)          tableAndCols.update(dbTable, value)        }      }    }    tableAndCols  }
一个SQL的DataFrame传进去,可以分析出库表列的对应关系,然后就可以拿着这些信息去做权限认证了。
println(extractTableWithColumns(df))//执行输出的结果为:Map(respect.test -> Set(name, age))
其实通过分析Spark SQL查询计划还可以做很多其他的事情,比如指标的血缘分析、聚合分析、join分析等,或许还能算出个SQL复杂度、评估危险SQL,随便想一想哈6f59f94ca634f7e8aad519e334a8798c.png(当然这里面有一些要注意的地方,在做某些分析时,比如union、window等就需要特殊处理)

f219754f7b0cf26eac42e322c6b466a1.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值