☰
Spark 2.3.0 without-hive 发行包部署与避坑指南
2026/10/10 14:43:26 网站建设 项目流程

简介:面向熟悉 Hadoop 2.x 环境、需要把 Spark 与 Hive 集成但又不希望引入完整 Hive 依赖包的开发与运维工程师,这份精简版 Spark 2.3.0 发行包专为独立对接元数据服务的部署场景准备。压缩包共 867 个文件,大小约 127.77MB,以 Python、Scala、Java 源码和 JAR 依赖库为主体,另含 Shell 启动脚本、配置文件、R 脚本、测试样例与文档说明,整体结构贴近官方发行版,便于二次开发及集群扩展。目前已有 659 人浏览学习,适合已经部署独立 Hive 服务、又想借助 Spark 引擎处理海量数据的团队。借助包内脚本与样例,可以快速掌握 Spark SQL 对接 Hive 元数据服务的配置流程,理解 Hive on Spark 在无内置依赖情况下的组件取舍与常见踩坑点,无论是编写批处理任务、交互式查询还是流式作业,都能在此基础上快速落地,同时省去自行拼装组件、反复验证兼容性的时间。

1. 关于 spark-2.3.0-bin-hadoop2-without-hive:先把它翻译成人话

如果你下载过 Spark 的预编译发行包,一定见过这串长文件名。spark-2.3.0-bin-hadoop2-without-hive 是 Apache Spark 2.3.0 针对 Hadoop 2.x 生态打好的二进制包,编译目标是 Hadoop 2.6+ 的 YARN 客户端,并且明确不带 Hive 相关依赖。换句话说,这个包能让你在已有 Hadoop 2.x 集群的环境中直接跑 Spark 作业,但不会替你管理 Hive 元数据,也不会在启动时自动拉起 Hive 的 Metastore。

为什么有人专门找 without-hive 这个变体?最常见的原因是:目标服务器上已经有了独立的 Hive 服务,或者团队压根不用 Hive,只用 Spark 处理 Parquet/文本文件。带 Hive 的包会往 classpath 里塞一堆 Hive 1.2.1 的 jar,这些 jar 很可能和集群自带的 Hive 2.x 撞车,轻则告警刷屏,重则 NoSuchMethodError 直接杀掉作业。这个包解决的正是这类"依赖打架"问题。适合的读者是刚从 CDH 换到纯 Apache 发行版、或者准备在测试机上从零搭 Spark 的开发者和运维工程师。

2. without-hive 到底去掉了什么:发行包变体与选型逻辑

2.1 with-hive 与 without-hive:同一份源码的两个构建结果

Apache Spark 官方发布页面里,同一个版本通常给出多个预编译包。以 2.3.0 为例,常见的就是 bin-hadoop2.7、bin-hadoop2.7-with-hive 这类的组合。两者的 Spark 核心引擎完全相同,差别集中在两处。

第一个差别是依赖 jar。with-hive 变体会把 Hive 1.2.1 的 metastore、serde、hive-exec、hive-common 等 jar 打进 distribution 的 lib 目录,而 without-hive 里这些 jar 全部缺席。Spark SQL 在解析 Hive 语法、读写 Hive 表时依赖这些 jar,所以 without-hive 包默认不能用 CREATE TABLE 语句去建 Hive 表,也不能通过 spark.sql.catalogImplementation 把 catalog 切到 Hive。

第二个差别是配置文件的默认值。with-hive 包在 conf 目录下自带一个 hive-site.xml 模板的引用逻辑,启动时会把内存中的配置合并到 HiveConf。without-hive 包完全没有这层合并,你必须在外部准备好 metastore 地址,或者干脆放弃 Hive 表。真要说底层原理,Spark SQL 的 SessionState 会根据 spark.sql.catalogImplementation 这个配置项在 in-memory catalog 和 hive catalog 之间二选一,选 hive 时再去找 hive-site.xml。without-hive 包里没有 Hive jar,选 hive 就会直接抛 ClassNotFoundException。

2.2 为什么 Spark 2.3.0 还要区分 Hadoop 版本打包

Spark 的底层实现里,对 Hadoop 文件系统、YARN 调度器的调用是通过 hadoop-client 这个依赖完成的。不同 Hadoop 大版本之间,YARN ApplicationMaster 的协议、HDFS 的 RPC 机制都存在差异,Spark 必须基于具体的 Hadoop 版本来编译这层客户端代码。

打包时 bin-hadoop2 表示编译时用的是 Hadoop 2.6 或者 2.7 的 client jar,生成的包能跑在绝大多数的 Hadoop 2.x 集群上。你可能会问,为什么不直接编译一个啥都兼容的包?答案是不行,因为 Hadoop 2.x 到 3.x 之间,客户端 API 有一些破坏性变更。Spark 2.3.0 时代,很多生产集群还停留在 Hadoop 2.7,所以这个变体是覆盖率最高的选择。

此外还有一层考虑,就是 jar 冲突。Hadoop 的 guava、netty、jackson 版本跟 Spark 依赖的版本差异很大。若机器上装的是某个发行版 Hadoop,把 Spark 的 lib 目录整个塞进 classpath 时,这些 jar 往往不是被覆盖就是被跳过。without-hive 变体去掉的不是 Hadoop 相关 jar,只是 Hive 的,它同样保留了对 HDFS 和 YARN 的完整支持。

2.3 从包名读出版本血缘:2.3.0 与 Scala 2.11

spark-2.3.0-bin-hadoop2-without-hive 这个字符串,三段信息里的第三段最容易忽略:Spark 2.3.0 是 Scala 2.11 编译的。这直接决定了你用 Scala 写作业时,IDE 里必须把 Scala 版本锁在 2.11.x,用 2.12 编出来的 class 文件丢给 Spark 跑,会报 java.lang.NoSuchMethodError,排查起来相当难受。

另一个血缘要注意的是 JDK 版本。Spark 2.3.0 官方推荐 JDK 8,如果你机器上默认 JDK 是 11,启动时大概率会遇到 IllegalAccessError 或者 UnsupportedClassVersionError。这一点放在后面避坑章节细说。

选型上我的习惯是:如果只是本地验证代码逻辑,用 without-hive 包就够了,轻、干净、启动快;如果要做的是和 Hive 表强相关的数据仓库任务,老老实实用 with-hive 包,别自己手工往 lib 里补 hive jar,补来补去版本对不上,欠的债最后都是自己的。

3. 部署与初始配置:解压之后的前 30 分钟决定后边踩多少坑

3.1 解压与目录瘦身:必要的目录你要心里有数

拿到 tar.gz 之后,解压动作本身没什么好说的,重点在于解压后你要知道哪些目录是运行时必需的,哪些可以安全删掉。一个标准 Spark 2.3.0 发行包解出来之后大致是这样:

tar -xzf spark-2.3.0-bin-hadoop2-without-hive.tgz -C /opt/ ln -s /opt/spark-2.3.0-bin-hadoop2-without-hive /opt/spark ls -l /opt/spark/
# 输出里你会看到这几个关键目录 bin # 提交入口 spark-submit, pyspark, spark-shell sbin # 集群启停脚本 start-master.sh, start-slave.sh jars # 全部运行时 jar,共 250+ 个 conf # 配置模板,默认只有 log4j.properties.template 等 data # 部分示例数据,可删 examples # 示例代码,可删 python # PySpark 的 Python 端实现 R # SparkR 的 R 端实现

数据目录和 examples 目录对实际作业没有任何影响,磁盘吃紧的服务器可以删掉。真正不能动的是 jars、bin、sbin、python 这四个。特别是 jars 目录,后期如果你要手动加 MySQL 驱动或者别的第三方 jar,只会往这个目录复制,不会去删里面的东西。

3.2 环境变量与 spark-env.sh:四个必调的参数

解压之后第一件事不是马上跑示例,而是把 Spark 的运行环境固定下来。我见过太多人跳过这一步,结果跑到第 3 个作业时才发现 JVM 内存参数全用的默认值,executor 被 YARN 直接杀掉。spark-env.sh 从模板复制出来之后,至少改这四个位置:

cp /opt/spark/conf/spark-env.sh.template /opt/spark/conf/spark-env.sh cat >> /opt/spark/conf/spark-env.sh <<'EOF' # 指定 JDK,避免走到系统里多个 JVM 里不可控的那个 JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk # 当前机器是 master 时,Spark 会绑定这个地址对外提供 webUI SPARK_MASTER_HOST=10.0.0.11 # worker 上单个 executor 默认内存上限,不给大,留给操作系统缓冲 SPARK_WORKER_MEMORY=4g SPARK_WORKER_CORES=4 # driver 默认内存,本地跑测试时可以调小 SPARK_DRIVER_MEMORY=2g EOF

这里要解释一下为什么是这四个。JAVA_HOME 解决的是多版本 JDK 并存的服务器上的不确定性问题,Spark 脚本里找 JAVA_HOME 的优先级高于 PATH。SPARK_MASTER_HOST 不设的话,master 可能绑定到主机名解析出来的第一个 IP,内网环境容易绑到 127.0.0.1 以外的奇怪地址,导致客户端连不上。SPARK_WORKER_MEMORY 和 SPARK_WORKER_CORES 是 standalone 模式下每个 worker 能分出去的总资源,设得太大,多个 spark 作业同时跑时操作系统失去响应;设太小,executor 起不来直接被拒绝。

3.3 spark-defaults.conf:真实任务里我常改的键值

spark-defaults.conf 是 SparkConf 的文件化入口,优先级低于代码里显式配置的 SparkConf,但高于环境变量。一个稳定跑批的集群,我会在这个文件里至少写好以下键值:

cp /opt/spark/conf/spark-defaults.conf.template /opt/spark/conf/spark-defaults.conf cat > /opt/spark/conf/spark-defaults.conf <<'EOF' spark.master spark://10.0.0.11:7077 spark.driver.memory 2g spark.executor.memory 4g spark.executor.cores 4 spark.serializer org.apache.spark.serializer.KryoSerializer spark.sql.warehouse.dir file:///tmp/spark-warehouse spark.ui.port 4040 EOF

spark.serializer 换成 Kryo 是我个人的习惯,虽然需要给注册类做额外配置,但默认的 Java 序列化器在数据量大时不仅慢,还会产生大量临时对象,GC 压力直线上升。spark.sql.warehouse.dir 这个键值在 without-hive 包里特别关键,因为它默认是 /user/hive/warehouse——一个依赖 HDFS 和 Hive 权限模型的位置。没有 Hive 的集群上,启动 Spark Thrift Server 或者执行 SQL 时会尝试创建这个目录,如果当前用户对 HDFS 根目录没有写权限,作业直接失败。

不要小看这个配置,很多人在 without-hive 包上跑 spark-sql 明明读的是本地文件,却报出 Permission denied,原因就是 warehouse 目录指向了 HDFS 上自己没权限的位置。

4. 跑通第一个任务:从 spark-submit 到 standalone 集群

4.1 用 spark-submit 提交本地任务的完整命令

spark-submit 是 Spark 的通用提交入口,支持 local 和 cluster 两种运行位置。本地模式适合验证代码和调试依赖,一条命令就能跑起来:

cd /opt/spark ./bin/spark-submit \ --class org.apache.spark.examples.SparkPi \ --master local[2] \ --driver-memory 1g \ --conf spark.ui.port=4041 \ ./examples/jars/spark-examples_2.11-2.3.0.jar \ 10

命令拆开看:--class 告诉 Spark 入口类的全限定名,--master local[2] 表示在本机起 2 个线程模拟并行度,--driver-memory 限定 driver JVM 堆大小。最后的 10 是传给 SparkPi 的参数,表示要算 10 轮。这个命令能跑通,说明 Java 环境、jar 完整性、基本内存参数都没问题。

如果你想验证 HDFS 支持是否正常,把输入输出路径换成 hdfs:// 前缀就够了。本地模式下 Spark 会直接加载 hadoop-client 的 FileSystem 实现,不需要额外的启动动作。如果有问题,报错会集中在 ClassNotFound 和 FileSystem 未初始化两类。顺利的话,你应该能在终端看到计算出圆周率的结果,同时在 http://localhost:4041 看到作业的 event timeline。

4.2 standalone 模式:master、worker 启动与资源分配

本地模式跑通之后,下一步是搭一个真正能跑多机任务的 standalone 集群。standalone 是 Spark 自带的资源调度框架,不需要依赖 YARN,是这个 without-hive 包最容易上手的集群模式。

# 在 master 节点上 /opt/spark/sbin/start-master.sh # 在 worker 节点上(worker 数根据自己的机器数量来) /opt/spark/sbin/start-slave.sh spark://10.0.0.11:7077 # 停掉 /opt/spark/sbin/stop-master.sh /opt/spark/sbin/stop-slave.sh

start-master 默认绑定 7077 端口作为通信端点,8080 端口开 Web UI。start-slave 需要显式指定 master 的 Spark URL,这个 URL 可以在 master 的 Web UI 页面看到。启动后建议去 8080 页面确认 worker 是否注册成功,页面里会显示每个 worker 的可分配内存和核数,也就是你在 spark-env.sh 里配的 SPARK_WORKER_MEMORY 和 SPARK_WORKER_CORES。

提交任务时把 --master 换成 spark://10.0.0.11:7077,Spark 会把 executor 分布到各个 worker 上。这里有个容易忽视的点:standalone 模式下 worker 的资源是静态分配的,executor 内存会直接占掉 worker 预留的内存,如果两个作业同时提交,而它们的 executor 内存之和超过了 worker 的总内存,后提交的作业会一直等调度,看起来像卡死。这时候先看 8080 页面上的工人节点内存剩余,再决定等还是调参。

4.3 跑 PySpark / SparkR 时的 JVM 参数差异

PySpark 和 SparkR 都是通过 Python 或 R 进程作为 driver,再由 driver 去申请 executor JVM。这里的坑在于 PySpark 默认用的 Python 解释器是系统 PATH 里第一个叫 python 的可执行文件,经常不是你 conda 环境里那个。

# 指定 Python 解释器,避免用到 base 环境里版本不对的 python export PYSPARK_PYTHON=/opt/conda/envs/py36/bin/python export PYSPARK_DRIVER_PYTHON=/opt/conda/envs/py36/bin/python /opt/spark/bin/pyspark \ --master spark://10.0.0.11:7077 \ --executor-memory 4g \ --driver-memory 2g

PYSPARK_PYTHON 是 executor 上跑 UDF 时用的解释器,PYSPARK_DRIVER_PYTHON 是 driver 进程用的解释器。很多人在一个节点上配好了,到了另一个节点忘了配,worker 上就用默认 python,结果 UDF 里 import numpy 报错或者版本不对。最稳的做法是把这两个变量写进 worker 节点的 bashrc 里,而不是只在提交节点的命令行里 export。

SparkR 的情况类似,但没有 UDF 解释器这个层,主要注意 R 的版本和 sparkr 包的匹配。2.3.0 对应的 SparkR 包要 >= 3.4,否则连基本的数据框转换都会报错。

5. 避坑:without-hive 包从安装到跑任务的高频翻车点

5.1 现象:spark-sql 启动后报找不到 Hive 相关类

./bin/spark-sql # 报错示例:java.lang.ClassNotFoundException: org.apache.hadoop.hive.ql.metadata.Hive

原因在于 without-hive 包里压根没有 hive-exec 和 hive-metastore 这两个 jar。spark-sql 默认会去解析 HiveQL 语法,读取 hive-site.xml,建 SessionState。没有元数据服务,自然无法完成初始化。

解决办法需要先问自己一个问题:是不是真的存在 Hive 外的替代方案,让你不需要走 Hive 的 catalog。如果只是临时查 Parquet 文件,用 Spark SQL 的 DataSource API 就够了,不必启动 Hive 元数据。把 SQL 里建表的语句换成读外部文件即可。如果确实需要 Hive 表,那就别省,换 with-hive 包吧,手动补 jar 只会引入更大的黑匣子。

5.2 现象:java.lang.NoSuchMethodError: com.google.common.base.Optional.isPresent

这个报错是 Guava 版本冲突的典型症状,经常出现在 without-hive 包和集群已有的 Hadoop 客户端共存时。原因在于 Spark 自己的 lib 目录里带了一个 Guava 版本,Hadoop 的 lib 里带了另一个,classpath 顺序决定了先加载谁。

解决思路是统一 classpath 里的 Guava 版本。操作上先检查 $SPARK_HOME/jars 里 guava 的版本,再用集群 Hadoop 的 lib 里的版本做对照。常见的做法是删除 Spark 目录里低版本的 guava jar,然后复制一个高版本的进去。但注意 Spark 自身也需要 guava 提供某些类,版本不能跳到太激进,选用和集群一致的版本最稳。改完之后用 spark-submit 重新跑一次之前的作业,看报错是否消失。

5.3 现象:提交任务后 executor 一直被 YARN kill

如果你把 --master 设成了 yarn,任务会在 YARN 容器里运行。executor 申请的内存超过了 YARN 单个容器的上限时,ApplicationMaster 会不断重试,最后显示 FAILED。

关键参数是 yarn.nodemanager.resource.memory-mb 和 spark.executor.memory。你在 spark 里申请的 executor 内存是 JVM 堆,实际容器内存还要再加上 overhead,默认是堆的 10%。所以一个 8g 的 executor,实际占的容器内存接近 9g。如果 YARN 的容器上限是 8g,作业必挂。把 executor 内存调到 6g 或者把 overhead 调小都能解决,但调 overhead 有风险,更推荐调 executor 内存。另外检查这个集群是否开了资源池限制,不同队列的 memory limit 也不一样。

5.4 现象:spark-shell 在 JDK 11 环境下直接崩溃

Spark 2.3.0 官方只支持 JDK 8,在 JDK 11 上跑会遇到模块化限制导致的大量 IllegalAccessError。现象是启动进程正常,但一执行 Action,就抛 Unexpected error。

解决办法是安装 JDK 8 并在 spark-env.sh 里显式指定 JAVA_HOME。不要在命令行里用 export JAVA_HOME 临时覆盖,因为 spark 脚本内部会重置这个变量。直接改 spark-env.sh 是最可靠的。如果你机器上同时有 Java 8 和 Java 11,把 JAVA_HOME 指向 Java 8 之后,spark-shell 和 spark-submit 都正常,其他 Java 应用也还能用 Java 11,互不干扰。

5.5 现象:首次写 Parquet 时创建一个目录,但目录属主是另一个用户

without-hive 包在没有 Kerberos 的普通集群上,默认走 Hadoop 的简单认证。这时文件的属主取决于提交作业的操作系统用户。如果你用 root 启动 spark-submit,生成的 Parquet 文件属主就是 root,后续别的用户来读,就会遇到权限不足。

解决方法是约定一个统一的作业运行账号,所有提交任务都用这个用户执行,避免文件四处散落、权限不一致。分发 spark 安装包时,把这个目录 chown 给约定账号。另外 spark.sql.warehouse.dir 配置的路径也要提前建好并授权,否则 FsPermission 问题会一直烦你。

6. 进阶:把 without-hive 变成一套真正可用的离线计算环境

6.1 Hive 依赖缺失时的三个替代方案

without-hive 包用久了你会发现,缺少的 Hive 支持可以用三招补回来。第一招是使用 Spark SQL 的 DataSource V1 API,直接对文件系统上的数据做 SQL 查询,把目录当成表。第二招是引入外部的 Hive Metastore 服务,但需要自己写 hive-site.xml 交给 Spark,得确认 metastore 版本兼容性。第三招是放弃 Hive 语法,全面转向 DataFrame API 和 SQL 里的文件格式指定。

-- 直接用 SQL 查 Parquet 目录,不经过 Hive 元数据 CREATE OR REPLACE TEMPORARY VIEW user_log USING parquet OPTIONS (path "/data/logs/user_log");

这里的核心思想是 Temporary View,它与 Spark 的 session 生命周期绑定,不产生持久化元数据,因此不需要 Hive Metastore。对于多数离线分析场景,这种方案完全够用。

6.2 从 Spark 2.3.0 升级时要有的心理预期

Spark 2.3.0 发布之后版本迭代很多,如果你的项目还在用这个包,建议评估升级路径。升级时要跟着 release note 走,重点关注三类变化:结构化流 API 的改动、SQL 函数的行为变化、YARN 集成参数的改名。升级步骤的常见做法是先在一个测试环境跑通全量 SQL 脚本和 UDF 回归。

6.3 验证应用是否真的把 Spark 用起来了

部署完一套环境,总要确认它在干活而不是耗着资源空转。有一个最简单也最有效的验证手段:在 spark-defaults.conf 里设置 spark.eventLog.enabled=true,然后跑一个作业,去 Web UI 看 event log 里的每个 stage 的耗时和 shuffle 数据量。如果 shuffle 数据量和输入量不成比例,说明数据聚合策略有问题上,该调并行度或 repartition。这个习惯我一直保留,升级后也会先跑一遍 event log 再做对比。

最后说一个我的习惯:每次拿到一个新 Spark 环境,我都会在集群上跑一遍 SparkPi 和一份真实数据集的 count。前者验环境,后者验数据路径和权限。跑不通的时候,先看日志文件里后 30 行,而不是在 Spark Web UI 上点来点去。这个习惯帮我省掉了太多排查时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询