You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

21 KiB

20 | Hive + Spark强强联合分布式数仓的不二之选

你好,我是吴磊。

在数据源与数据格式以及数据转换那两讲第15、16讲我们介绍了在Spark SQL之上做数据分析应用开发的一般步骤。

这里我们简单回顾一下首先我们通过SparkSession read API从分布式文件系统创建DataFrame。然后通过创建临时表并使用SQL语句或是直接使用DataFrame API来进行各式各样的数据转换、过滤、聚合等操作。最后我们再用SparkSession的write API把计算结果写回分布式文件系统。

实际上直接与文件系统交互仅仅是Spark SQL数据应用的常见场景之一。Spark SQL另一类非常典型的场景是与Hive做集成、构建分布式数据仓库。我们知道数据仓库指的是一类带有主题、聚合层次较高的数据集合它的承载形式往往是一系列Schema经过精心设计的数据表。在数据分析这类场景中数据仓库的应用非常普遍。

在Hive与Spark这对“万金油”组合中Hive擅长元数据管理而Spark的专长是高效的分布式计算二者的结合可谓是“强强联合”。今天这一讲我们就来聊一聊Spark与Hive集成的两类方式一类是从Spark的视角出发我们称之为Spark with Hive而另一类则是从Hive的视角出发业界的通俗说法是Hive on Spark。

Hive架构与基本原理

磨刀不误砍柴工在讲解这两类集成方式之前我们不妨先花点时间来了解一下Hive的架构和工作原理避免不熟悉Hive的同学听得云里雾里。

Hive是Apache Hadoop社区用于构建数据仓库的核心组件它负责提供种类丰富的用户接口接收用户提交的SQL查询语句。这些查询语句经过Hive的解析与优化之后往往会被转化为分布式任务并交付Hadoop MapReduce付诸执行。

Hive是名副其实的“集大成者”它的核心部件其实主要是User Interface1和Driver3。而不论是元数据库4、存储系统5还是计算引擎6Hive都以“外包”、“可插拔”的方式交给第三方独立组件所谓“把专业的事交给专业的人去做”如下图所示。

图片

Hive的User Interface为开发者提供SQL接入服务具体的接入途径有Hive Server 22、CLI和Web InterfaceWeb界面入口。其中CLI与Web Interface直接在本地接收SQL查询语句而Hive Server 2则通过提供JDBC/ODBC客户端连接允许开发者从远程提交SQL查询请求。显然Hive Server 2的接入方式更为灵活应用也更为广泛。

我们以响应一个SQL查询为例看一看Hive是怎样工作的。接收到SQL查询之后Hive的Driver首先使用其Parser组件将查询语句转化为ASTAbstract Syntax Tree查询语法树

紧接着Planner组件根据AST生成执行计划而Optimizer则进一步优化执行计划。要完成这一系列的动作Hive必须要能拿到相关数据表的元信息才行比如表名、列名、字段类型、数据文件存储路径、文件格式等等。而这些重要的元信息通通存储在一个叫作“Hive Metastore”4的数据库中。

本质上Hive Metastore其实就是一个普通的关系型数据库RDBMS它可以是免费的MySQL、Derby也可以是商业性质的Oracle、IBM DB2。实际上除了用于辅助SQL语法解析、执行计划的生成与优化Metastore的重要作用之一是帮助底层计算引擎高效地定位并访问分布式文件系统中的数据源

这里的分布式文件系统可以是Hadoop生态的HDFS也可以是云原生的Amazon S3。而在执行方面Hive目前支持3类计算引擎分别是Hadoop MapReduce、Tez和Spark。

当Hive采用Spark作为底层的计算引擎时我们就把这种集成方式称作“Hive on Spark”。相反当Spark仅仅是把Hive当成是一种元信息的管理工具时我们把Spark与Hive的这种集成方式叫作“Spark with Hive”。

你可能会觉得很困惑“这两种说法听上去差不多嘛两种集成方式到底有什么本质的不同呢”接下来我们就按照“先易后难”的顺序先来说说“Spark with Hive”这种集成方式然后再去介绍“Hive on Spark”。

Spark with Hive

在开始正式学习Spark with Hive之前我们先来说说这类集成方式的核心思想。前面我们刚刚说过Hive Metastore利用RDBMS来存储数据表的元信息如表名、表类型、表数据的Schema、表分区数据的存储路径、以及存储格式等等。形象点说Metastore就像是“户口簿”它记录着分布式文件系统中每一份数据集的“底细”。

Spark SQL通过访问Hive Metastore这本“户口簿”即可扩充数据访问来源。而这就是Spark with Hive集成方式的核心思想。直白点说在这种集成模式下Spark是主体Hive Metastore不过是Spark用来扩充数据来源的辅助工具。厘清Spark与Hive的关系有助于我们后面区分Hive on Spark与Spark with Hive之间的差异。

作为开发者,我们可以通过3种途径来实现Spark with Hive的集成方式它们分别是

  1. 创建SparkSession访问本地或远程的Hive Metastore
  2. 通过Spark内置的spark-sql CLI访问本地Hive Metastore
  3. 通过Beeline客户端访问Spark Thrift Server。

SparkSession + Hive Metastore

为了更好地理解Hive与Spark的关系我们先从第一种途径也就是通过SparkSession访问Hive Metastore说起。首先我们使用如下命令来启动Hive Metastore。

hive --service metastore

Hive Metastore启动之后我们需要让Spark知道Metastore的访问地址也就是告诉他数据源的“户口簿”藏在什么地方。

要传递这个消息我们有两种办法。一种是在创建SparkSession的时候通过config函数来明确指定hive.metastore.uris参数。另一种方法是让Spark读取Hive的配置文件hive-site.xml该文件记录着与Hive相关的各种配置项其中就包括hive.metastore.uris这一项。把hive-site.xml拷贝到Spark安装目录下的conf子目录Spark即可自行读取其中的配置内容。

接下来我们通过一个小例子来演示第一种用法。假设Hive中有一张名为“salaries”的薪资表每条数据都包含id和salary两个字段表数据存储在HDFS那么在spark-shell中敲入下面的代码我们即可轻松访问Hive中的数据表。

import org.apache.spark.sql.SparkSession
import  org.apache.spark.sql.DataFrame
 
val hiveHost: String = _
// 创建SparkSession实例
val spark = SparkSession.builder()
                   .config("hive.metastore.uris", s"thrift://hiveHost:9083")
                   .enableHiveSupport()
                   .getOrCreate()
 
// 读取Hive表创建DataFrame
val df: DataFrame = spark.sql(select * from salaries)
 
df.show
 
/** 结果打印
+---+------+
| id|salary|
+---+------+
|  1| 26000|
|  2| 30000|
|  4| 25000|
|  3| 20000|
+---+------+
*/

第16讲我们讲过利用createTempView函数从数据文件创建临时表的方法临时表创建好之后我们就可以使用SparkSession的sql API来提交SQL查询语句。连接到Hive Metastore之后咱们就可以绕过第一步直接使用sql API去访问Hive中现有的表是不是很方便

更重要的是createTempView函数创建的临时表它的生命周期仅限于Spark作业内部这意味着一旦作业执行完毕临时表也就不复存在没有办法被其他应用复用。Hive表则不同它们的元信息已经持久化到Hive Metastore中不同的作业、应用、甚至是计算引擎如Spark、Presto、Impala等等都可以通过Hive Metastore来访问Hive表。

总结下来在SparkSession + Hive Metastore这种集成方式中Spark对于Hive的访问仅仅涉及到Metastore这一环节对于Hive架构中的其他组件Spark并未触及。换句话说在这种集成方式中Spark仅仅是“白嫖”了Hive的Metastore拿到数据集的元信息之后Spark SQL自行加载数据、自行处理如下图所示。

图片

在第一种集成方式下通过sql API你可以直接提交复杂的SQL语句也可以在创建DataFrame之后再使用第16讲提到的各种算子去实现业务逻辑。

spark-sql CLI + Hive Metastore

不过你可能会说“既然是搭建数仓那么能不能像使用普通数据库那样直接输入SQL查询绕过SparkSession的sql API呢

答案自然是肯定的接下来我们就来说说Spark with Hive的第二种集成方式spark-sql CLI + Hive Metastore。与spark-shell、spark-submit类似spark-sql也是Spark内置的系统命令。将配置好hive.metastore.uris参数的hive-site.xml文件放到Spark安装目录的conf下我们即可在spark-sql中直接使用SQL语句来查询或是处理Hive表。

显然在这种集成模式下Spark和Hive的关系与刚刚讲的SparkSession + Hive Metastore一样本质上都是Spark通过Hive Metastore来扩充数据源。

不过相比前者spark-sql CLI的集成方式多了一层限制那就是在部署上spark-sql CLI与Hive Metastore必须安装在同一个计算节点。换句话说spark-sql CLI只能在本地访问Hive Metastore而没有办法通过远程的方式来做到这一点。

在绝大多数的工业级生产系统中不同的大数据组件往往是单独部署的Hive与Spark也不例外。由于Hive Metastore可用于服务不同的计算引擎如前面提到的Presto、Impala因此为了减轻节点的工作负载Hive Metastore往往会部署到一台相对独立的计算节点。

在这样的背景下不得不说spark-sql CLI本地访问的限制极大地削弱了它的适用场景这也是spark-sql CLI + Hive Metastore这种集成方式几乎无人问津的根本原因。不过这并不妨碍我们学习并了解它这有助于我们对Spark与Hive之间的关系加深理解。

Beeline + Spark Thrift Server

说到这里你可能会追问“既然spark-sql CLI有这样那样的限制那么还有没有其他集成方式既能够部署到生产系统又能让开发者写SQL查询呢”答案自然是“有”Spark with Hive集成的第三种途径就是使用Beeline客户端去连接Spark Thrift Server从而完成Hive表的访问与处理。

Beeline原本是Hive客户端通过JDBC接入Hive Server 2。Hive Server 2可以同时服务多个客户端从而提供多租户的Hive查询服务。由于Hive Server 2的实现采用了Thrift RPC协议框架因此很多时候我们又把Hive Server 2称为“Hive Thrift Server 2”。

通过Hive Server 2接入的查询请求经由Hive Driver的解析、规划与优化交给Hive搭载的计算引擎付诸执行。相应地查询结果再由Hiver Server 2返还给Beeline客户端如下图右侧的虚线框所示。

图片

Spark Thrift Server脱胎于Hive Server 2在接收查询、多租户服务、权限管理等方面这两个服务端的实现逻辑几乎一模一样。它们最大的不同在于SQL查询接入之后的解析、规划、优化与执行。

我们刚刚说过Hive Server 2的“后台”是Hive的那套基础架构。而SQL查询在接入到Spark Thrift Server之后它首先会交由Spark SQL优化引擎进行一系列的优化。

在第14讲我们提过借助于Catalyst与Tungsten这对“左膀右臂”Spark SQL对SQL查询语句先后进行语法解析、语法树构建、逻辑优化、物理优化、数据结构优化、以及执行代码优化等等。然后Spark SQL将优化过后的执行计划交付给Spark Core执行引擎付诸运行。

图片

不难发现SQL查询在接入Spark Thrift Server之后的执行路径与DataFrame在Spark中的执行路径是完全一致的。

理清了Spark Thrift Server与Hive Server 2之间的区别与联系之后接下来我们来说说Spark Thrift Server的启动与Beeline的具体用法。要启动Spark Thrift Server我们只需调用Spark提供的start-thriftserver.sh脚本即可。

// SPARK_HOME环境变量指向Spark安装目录
cd $SPARK_HOME/sbin
 
// 启动Spark Thrift Server
./start-thriftserver.sh

脚本执行成功之后Spark Thrift Server默认在10000端口监听JDBC/ODBC的连接请求。有意思的是关于监听端口的设置Spark复用了Hive的hive.server2.thrift.port参数。与其他的Hive参数一样hive.server2.thrift.port同样要在hive-site.xml配置文件中设置。

一旦Spark Thrift Server启动成功我们就可以在任意节点上通过Beeline客户端来访问该服务。在客户端与服务端之间成功建立连接Connections之后咱们就能在Beeline客户端使用SQL语句处理Hive表了。需要注意的是在这种集成模式下SQL语句背后的优化与计算引擎是Spark。

/**
用Beeline客户端连接Spark Thrift Server
其中hostname是Spark Thrift Server服务所在节点
*/
beeline -u “jdbc:hive2://hostname:10000”

好啦到此为止Spark with Hive这类集成方式我们就讲完了。

为了巩固刚刚学过的内容,咱们趁热打铁,一起来做个简单的小结。不论是SparkSession + Hive Metastore、spark-sql CLI + Hive Metastore还是Beeline + Spark Thrift ServerSpark扮演的角色都是执行引擎而Hive的作用主要在于通过Metastore提供底层数据集的元数据。不难发现在这类集成方式中Spark唱“主角”而Hive唱“配角”

Hive on Spark

说到这里你可能会好奇“对于Hive社区与Spark社区来说大家都是平等的那么有没有Hive唱主角而Spark唱配角的时候呢”还真有这就是Spark与Hive集成的另一种形式Hive on Spark。

基本原理

在这一讲的开头我们简单介绍了Hive的基础架构。Hive的松耦合设计使得它的Metastore、底层文件系统、以及执行引擎都是可插拔、可替换的。

在执行引擎方面Hive默认搭载的是Hadoop MapReduce但它同时也支持Tez和Spark。所谓的“Hive on Spark”实际上指的就是Hive采用Spark作为其后端的分布式执行引擎如下图所示。

图片

从用户的视角来看使用Hive on MapReduce或是Hive on Tez与使用Hive on Spark没有任何区别执行引擎的切换对用户来说是完全透明的。不论Hive选择哪一种执行引擎引擎仅仅负责任务的分布式计算SQL语句的解析、规划与优化通通由Hive的Driver来完成。

为了搭载不同的执行引擎Hive还需要做一些简单的适配从而把优化过的执行计划“翻译”成底层计算引擎的语义。

举例来说在Hive on Spark的集成方式中Hive在将SQL语句转换为执行计划之后还需要把执行计划“翻译”成RDD语义下的DAG然后再把DAG交付给Spark Core付诸执行。从第14讲到现在我们一直在强调Spark SQL除了扮演数据分析子框架的角色之外还是Spark新一代的优化引擎。

在Hive on Spark这种集成模式下Hive与Spark衔接的部分是Spark Core而不是Spark SQL这一点需要我们特别注意。这也是为什么相比Hive on SparkSpark with Hive的集成在执行性能上会更胜一筹。毕竟Spark SQL + Spark Core这种原装组合相比Hive Driver + Spark Core这种适配组合在契合度上要更高一些。

集成实现

分析完原理之后接下来我们再来说说Hive on Spark的集成到底该怎么实现。

首先既然我们想让Hive搭载Spark那么我们事先得准备好一套完备的Spark部署。对于Spark的部署模式Hive不做任何限定Spark on Standalone、Spark on Yarn或是Spark on Kubernetes都是可以的。

Spark集群准备好之后我们就可以通过修改hive-site.xml中相关的配置项来轻松地完成Hive on Spark的集成如下表所示。

图片

其中hive.execution.engine用于指定Hive后端执行引擎可选值有“mapreduce”、“tez”和“spark”显然将该参数设置为“spark”即表示采用Hive on Spark的集成方式。

确定了执行引擎之后接下来我们自然要告诉Hive“Spark集群部署在哪里”spark.master正是为了实现这个目的。另外为了方便Hive调用Spark的相关脚本与Jar包我们还需要通过spark.home参数来指定Spark的安装目录。

配置好这3个参数之后我们就可以用Hive SQL向Hive提交查询请求而Hive则是先通过访问Metastore在Driver端完成执行计划的制定与优化然后再将其“翻译”为RDD语义下的DAG最后把DAG交给后端的Spark去执行分布式计算。

当你在终端看到“Hive on Spark”的字样时就证明Hive后台的执行引擎确实是Spark如下图所示。

图片

当然除了上述3个配置项以外Hive还提供了更多的参数用于微调它与Spark之间的交互。对于这些参数你可以通过访问Hive on Spark配置项列表来查看。不仅如此在第12讲我们详细介绍了Spark自身的基础配置项这些配置项都可以配置到hive-site.xml中方便你更细粒度地控制Hive与Spark之间的集成。

重点回顾

好啦,到此为止,今天的内容就全部讲完啦!内容有点多,我们一起来做个总结。

今天这一讲你需要了解Spark与Hive常见的两类集成方式Spark with Hive和Hive on Spark。前者由Spark社区主导以Spark为主、Hive为辅后者则由Hive社区主导以Hive为主、Spark为辅。两类集成方式各有千秋适用场景各有不同。

在Spark with Hive这类集成方式中Spark主要是利用Hive Metastore来扩充数据源从而降低分布式文件的管理与维护成本如路径管理、分区管理、Schema维护等等。

对于Spark with Hive我们至少有3种途径来实现Spark与Hive的集成分别是SparkSession + Hive Metastorespark-sql CLI + Hive Metastore和Beeline + Spark Thrift Server。对于这3种集成方式我把整理了表格供你随时查看。

图片

与Spark with Hive相对另一类集成方式是Hive on Spark。这种集成方式本质上是Hive社区为Hive用户提供了一种新的选项这个选项就是在执行引擎方面除了原有的MapReduce与Tez开发者还可以选择执行性能更佳的Spark。

因此在Spark大行其道的当下习惯使用Hive的团队与开发者更愿意去尝试和采用Spark作为后端的执行引擎。

熟悉了不同集成方式的区别与适用场景之后在日后的工作中当你需要将Spark与Hive做集成的时候就可以做到有的放矢、有章可循加油。

每课一练

1.在Hive on Spark的部署模式下用另外一套Spark部署去访问Hive Metastore比如通过创建SparkSession并访问Hive Metastore来扩充数据源。那么在这种情况下你能大概说一说用户代码的执行路径吗

2.尽管咱们专栏的主题是Spark但我强烈建议你学习并牢记Hive的架构设计。松耦合的设计理念让Hive本身非常轻量的同时还给予了Hive极大的扩展能力。也正因如此Hive才能一直牢牢占据开源数仓霸主的地位。Hive的设计思想是非常值得我们好好学习的这样的设计思想可以推而广之应用到任何需要考虑架构设计的地方不论是前端、后端还是大数据与机器学习。

欢迎你在留言区跟我交流互动,也欢迎把这一讲的内容分享给更多同事、朋友。