☰
Spark without-hive 包在 Hive on Spark 引擎切换中的部署与避坑
2026/10/26 1:01:58 网站建设 项目流程

简介:这份 Spark 2.3.0(Hadoop 2.x)无 Hive JAR 包发行版面向大数据开发与运维人员,以及需要实践 Hive on Spark 的中高级学习者,重点解决在已有独立 Hive 集群、又不希望引入完整 Hive 依赖时,仍用 Spark 计算能力访问和处理 Hive 数据的集成需求。压缩包共 867 个文件,约 127.77MB,包含 235 个 py、187 个 scala、125 个 java 源码及 109 个 jar 依赖,并配有 txt 说明、sh/cmd 启动脚本、template 配置模板与少量 parquet、orc、avro 样例数据,便于理解发行版组成与集成边界。已有 659 人学习。读者可从中获得精简版 Spark 运行目录、示例程序、脚本与配置模板,对照 Hive metastore 连接配置、Spark SQL 访问 Hive 表及常见依赖缺失问题开展验证,适合用作 Hive on Spark 环境搭建与排错的基础素材。

1. 为什么有人专门找 without-hive 这个包:一次引擎切换的现场复盘

节点搬迁前一晚,一套运行了三年的 Hive 数仓要更换底层引擎,调度系统里几百条 SQL 一行都不能改。这是最典型的 Hive on Spark 改造场景。改造后遇到的第一个麻烦往往不是调优,而是下载 Spark 时发现官方发行版还分标准版和 without-hive 版。spark-2.3.0-bin-hadoop2-without-hive就是为这类改造准备的:它把 Hive 相关依赖整体移除,让 Hive 侧自己的 jar 生效,避免两套 Hive 类库在同一个 JVM 里发生冲突。这个包适合正在做引擎切换又不想改业务 SQL 的团队,也适合只是想把 Spark 2.3.0 稳定跑在 Hadoop 2 集群上的场景。

2. 拆开 without-hive 这个包:少了哪几个 jar、为什么少了反而是好事

拿到一个二进制包,我的习惯是先不急着解压到生产目录,而是在/opt下找一个临时位置解开,把jars/目录的清单看一遍。这一步的目的不是好奇,而是确认三件事:Scala 二进制版本是不是 2.11、Hadoop 客户端是 2.x 的哪条线、Hive 相关的模块到底有没有被摘干净。前两项决定了它能不能和集群里已有的服务共存,第三项决定了后续 Hive on Spark 的接入方式。

2.1 目录结构与 jar 清单核对

解包之后先做一次静态核对,不要直接启动。下面这几条命令能在两分钟内把这个包的“底板”摸清楚:

# 解包到临时目录,先不覆盖生产路径 tar -xzf spark-2.3.0-bin-hadoop2-without-hive.tgz -C /opt cd /opt/spark-2.3.0-bin-hadoop2-without-hive # 1) jar 总数,标准版一般在 200 上下,without-hive 会明显少一截 ls jars | wc -l # 2) 有没有 Hive 相关 jar,理想结果是空 ls jars | grep -i hive # 3) spark-hive 模块是否存在,这是判断能否 enableHiveSupport 的关键 ls jars | grep -i 'spark-hive' # 4) 确认 Scala 与 Hadoop 版本线 ls jars | grep -E 'spark-core|spark-sql|hadoop-client'

第 2 条和第 3 条如果都返回空,说明摘除得很彻底;第 4 条正常应该看到spark-core_2.11-2.3.0.jar、spark-sql_2.11-2.3.0.jar和hadoop-client-2.7.x.jar这一组。名字里的_2.11是 Scala 二进制版本后缀,它和集群里其他 Spark 应用必须一致,混用 2.10 和 2.11 编译的包会在运行期抛出NoSuchMethodError,而且报错位置离真正原因很远,排查起来很费劲。

具体少了什么,可以对着下表看:

构件标准发行版without-hive 变体实际影响
spark-core_2.11有有无差别,RDD 与调度核心都在
spark-sql_2.11有有无差别,DataFrame API 可用
spark-hive_2.11有无enableHiveSupport()直接不可用
spark-hive-thriftserver_2.11有无包内没有自带 HiveServer2
hive-exec、hive-metastore 等有无需要由外部 Hive 提供
calcite、datanucleus 等传递依赖有无版本冲突面大幅缩小

这张表里最容易被忽略的是第三行。很多人拿到 without-hive 包之后,按网上的教程写了一句SparkSession.builder().enableHiveSupport(),然后在启动阶段抛ClassNotFoundException,回头怀疑包损坏。其实不是包的问题,是这个变体从设计上就不打算让 Spark 自己去连元数据服务——元数据的活归 Hive 干,Spark 只当执行器。

2.2 为什么 Hive on Spark 要刻意避开自带 Hive

要理解这个变体的价值,得先看 Hive on Spark 的运行方式。Hive 2.x 走的是远程 Spark 客户端这套机制:HiveServer2 或 Hive CLI 收到一条 SQL,把它编译成 Spark 作业,然后通过SparkSubmit在一个独立进程里拉起 Spark driver,双方用 RPC 通信,作业最终提交到 YARN 上跑。这个链路里,Hive 自己的hive-exec、hive-metastore是必须存在的,因为执行计划、UDF、SerDe 全都依赖它们。

如果这时 Spark 的jars/目录里还躺着一份自带的hive-exec,同一个 JVM 的类路径上就出现了两份同名类。类加载器按顺序取第一个命中的,取到哪一份取决于 classpath 的排列,这就是典型的“玄学问题”:同一份 SQL,在 A 节点跑得好,换到 B 节点就报NoSuchMethodError。更麻烦的是 Calcite、Guava 这类被两边同时依赖的库,版本差一个小号就可能让 SQL 解析阶段直接崩掉,而报错栈指向的是解析器内部,跟你的 SQL 毫无关系。

我一般的判断是:只要这套集群里 Hive 是主角、Spark 只是执行引擎,就用 without-hive 变体;反过来,如果是要用 Spark SQL 直接读 Hive 表、不打算装独立 Hive 服务,那标准版更省事。这两个场景的选型不能混,混了就是给自己制造黑匣子。

2.3 部署前的版本对齐检查

摘除 Hive 只是解决了一半问题,另一半是版本对齐。Spark 2.3.0 这个年代的组合相对固定:JDK 8、Scala 2.11、Hadoop 2.7.x、Hive 2.3.x。上生产前把下面几条跑一遍,比事后看日志便宜得多:

# JDK 必须是 8,Spark 2.3 对 JDK 9+ 支持不完整 java -version 2>&1 | head -n 1 # Hadoop 客户端版本,注意要和集群 NameNode 的协议兼容 hadoop version | head -n 1 # 确认 HADOOP_CONF_DIR 指向的是集群真实配置,不是包内自带的空模板 echo $HADOOP_CONF_DIR && ls $HADOOP_CONF_DIR/core-site.xml # Hive 侧版本,决定后面 hive-site.xml 里哪些参数名有效 ls /opt/hive/lib | grep -E 'hive-exec|hive-common'

第三条是血泪经验。HADOOP_CONF_DIR没设或者指错,Spark 会退回本地文件系统模式,spark-submit照样能成功,任务也照样跑完,但读的是本地目录而不是 HDFS,等发现数据对不上时已经过去半天。启动前用hadoop fs -ls /验证一次配置是否真的生效,成本几乎为零。

3. 让这个包先自己跑通:standalone 与 YARN 两条路上的最小验证

接入 Hive 之前,必须先证明这个 Spark 包本身是健康的。顺序不能反——如果直接上 Hive on Spark,一旦失败,你无法判断是 Spark 包的问题还是 Hive 配置的问题,两边的日志搅在一起,排查成本翻倍。我的做法是先跑通一个纯 Spark 的 Pi 作业,再跑一个读 HDFS 的 DataFrame 作业,两个都过了,才动 Hive 那一侧。

3.1 环境变量与 spark-env.sh

环境变量我写在/etc/profile.d/spark.sh里,而不是塞进用户的.bashrc,这样通过sudo -u切换用户跑任务时也能继承到:

# /etc/profile.d/spark.sh export SPARK_HOME=/opt/spark-2.3.0-bin-hadoop2-without-hive export PATH=$SPARK_HOME/bin:$SPARK_HOME/sbin:$PATH export HADOOP_CONF_DIR=/opt/hadoop/etc/hadoop export YARN_CONF_DIR=/opt/hadoop/etc/hadoop

spark-env.sh是从模板复制出来的,只改必要的几项。注意这个文件里不要写SPARK_MASTER_IP这类 standalone 专用配置,走 YARN 时它们没有意义,反而会让人误以为集群是 standalone 模式:

cd $SPARK_HOME/conf cp spark-env.sh.template spark-env.sh cat >> spark-env.sh <<'EOF' JAVA_HOME=/usr/lib/jvm/java-8-openjdk HADOOP_CONF_DIR=/opt/hadoop/etc/hadoop YARN_CONF_DIR=/opt/hadoop/etc/hadoop SPARK_LOG_DIR=/var/log/spark SPARK_PID_DIR=/var/run/spark EOF

SPARK_LOG_DIR和SPARK_PID_DIR建议改到系统目录,默认落在$SPARK_HOME/logs下,一旦包被替换或重装,历史日志就跟着没了,出问题时想回溯上一次的表现都做不到。

3.2 spark-defaults.conf 逐项确认

spark-defaults.conf是这个包最需要认真对待的文件。下面这一组是我在 2.3.0 上验证过的保守配置,先照抄跑通,再按业务调:

# 提交目标,集群模式下统一走 yarn spark.master yarn spark.submit.deployMode client # 事件日志,出问题后靠它复盘,必开 spark.eventLog.enabled true spark.eventLog.dir hdfs:///spark/eventlog spark.history.fs.logDirectory hdfs:///spark/eventlog # 序列化,2.3 默认是 Java 序列化,改成 Kryo 能省不少网络和内存 spark.serializer org.apache.spark.serializer.KryoSerializer # 把依赖 jar 提前传到 HDFS,避免每次提交都分发 spark.yarn.jars hdfs:///spark/jars/* # shuffle 并行度,默认 200 对小作业偏大,先留着后面调 spark.sql.shuffle.partitions 200 # 动态资源分配在 2.3 上需要外部 shuffle service,没部署就别开 spark.dynamicAllocation.enabled false # 执行器规格,先给一组能跑的下限 spark.executor.memory 4g spark.executor.cores 2 spark.executor.instances 4

几个参数值得单独说。spark.yarn.jars指向 HDFS 路径时,集群里每个节点都能拿到同一份 jar,不用每次提交都从客户端上传几百兆;如果写成local:/opt/...这种本地路径,就必须保证每个 NodeManager 节点上都有同路径的文件,否则容器启动后立刻报找不到类。spark.eventLog.dir要提前在 HDFS 上建好目录并给写权限,目录不存在时 Spark 不会报错,只是悄悄不记录,等你出问题去翻历史时才发现是空的。

上传 jar 的命令很直接,但要注意 HDFS 上的目录内容要和本地保持同步,换包之后必须重传:

hadoop fs -mkdir -p /spark/jars /spark/eventlog hadoop fs -put -f $SPARK_HOME/jars/*.jar /spark/jars/ hadoop fs -ls /spark/jars | wc -l

最后一行输出的数量应该和本地ls $SPARK_HOME/jars | wc -l一致,不一致说明上传中断过,缺哪个补哪个,别指望 Spark 自己发现。

3.3 两次冒烟验证:Pi 与 DataFrame

第一次冒烟用自带的示例 jar,只验证提交链路和资源申请是否通畅:

spark-submit \ --class org.apache.spark.examples.SparkPi \ --master yarn \ --deploy-mode client \ --executor-memory 2g \ --executor-cores 1 \ --num-executors 2 \ $SPARK_HOME/examples/jars/spark-examples_2.11-2.3.0.jar 50

--deploy-mode client下 driver 跑在提交机本地,日志直接打在终端,适合第一次排查;50是切片数,数值大小不影响正确性,只影响运行时长。看到Pi is roughly 3.14...就说明 YARN 队列、容器、jar 分发这三条链路都通了。

第二次冒烟验证数据通路,用spark-shell手动读一个 HDFS 上的文本目录,注意这里刻意不启用 Hive 支持:

spark-shell --master yarn --deploy-mode client
// 读 HDFS 上的一个文本目录,验证文件系统连通性 val path = "hdfs:///tmp/smoke/input" val df = spark.read.text(path) // 触发一次真实计算,不要只看 schema println(df.count()) // 写回 HDFS,验证输出权限和压缩编解码 df.write.mode("overwrite").text("hdfs:///tmp/smoke/output")

count()这种 action 是关键,只打印 schema 不会触发任何分布式计算,很多人到这一步以为验证通过了,实际上一个 executor 都没起来。写回那一步则能暴露权限问题——提交任务的用户对输出目录没有写权限时,作业会在最后一个 stage 才失败,前面几十个 stage 白跑,这也是我坚持在冒烟阶段就写一次的原因。

4. 接上 Hive on Spark:把 hive-site.xml 指到这个包上

前面的验证通过之后,Hive 侧的接入就变成了一件配置工作。核心思路只有一句话:让 Hive 知道 Spark 装在哪、用什么模式提交、依赖从哪里取。Hive 会把hive-site.xml里所有以spark.开头的配置项透传进 SparkConf,所以这个文件里写的spark.*参数,最终等价于写在spark-defaults.conf里,区别是前者只对 Hive 发起的作业生效,粒度更可控。

4.1 Hive 侧的必备配置项

下面这几条是能让链路跑起来的最小集合,加在hive-site.xml的<configuration>里:

<configuration> <!-- 引擎切换,这一条是总开关 --> <property> <name>hive.execution.engine</name> <value>spark</value> </property> <!-- 指向 without-hive 这个包的安装目录 --> <property> <name>spark.home</name> <value>/opt/spark-2.3.0-bin-hadoop2-without-hive</value> </property> <property> <name>spark.master</name> <value>yarn</value> </property> <property> <name>spark.submit.deployMode</name> <value>client</value> </property> <!-- 执行器侧从 HDFS 取 jar,和 spark-defaults.conf 保持一致 --> <property> <name>spark.yarn.jars</name> <value>hdfs:///spark/jars/*</value> </property> <property> <name>spark.executor.memory</name> <value>4g</value> </property> <property> <name>spark.executor.cores</name> <value>2</value> </property> <property> <name>spark.driver.memory</name> <value>2g</value> </property> <!-- 会话建立超时,机器慢的时候默认值不够 --> <property> <name>hive.spark.client.server.connect.timeout</name> <value>300000ms</value> </property> </configuration>

spark.submit.deployMode=client在这个场景下几乎不能改。Hive 需要和 Spark driver 保持 RPC 连接,driver 跑在 HiveServer2 所在节点的本地进程里,通信才走得通;改成 cluster 模式后 driver 被扔到 YARN 容器里,Hive 侧根本连不上,表现为会话创建超时。spark.home必须是绝对路径,而且这个路径要在 HiveServer2 进程可见的文件系统上,容器化部署时这一点尤其容易出错。

4.2 Hive 依赖怎么进 Spark 的类路径

without-hive 包没有 Hive 类库,而 Hive 发起的作业在运行期确实可能用到hive-exec里的 UDF 和 SerDe。常见做法是把 Hive 侧必需的 jar 补进 Spark 的jars/目录,但补多少、补哪几个很讲究:

HIVE_LIB=/opt/hive/lib SPARK_JARS=/opt/spark-2.3.0-bin-hadoop2-without-hive/jars # 只补执行期真正需要的两个 cp $HIVE_LIB/hive-exec-*.jar $SPARK_JARS/ cp $HIVE_LIB/hive-metastore-*.jar $SPARK_JARS/ # 补完之后立刻查冲突,重点看 guava / calcite / jackson ls $SPARK_JARS | grep -Ei 'guava|calcite|jackson|commons-lang'

最后一条命令是给自己留的后悔药。如果输出里出现两个不同版本的guava,说明刚才的复制把传递依赖带进来了,必须删掉冲突的那一份。判断依据是该 jar 是不是spark-core已经在用的版本——拿不准的时候就先只补hive-exec,跑一条最简单的 SQL,缺什么补什么,别一次性把整个lib目录倒进去。整目录复制的后果是几十个冲突源同时进 classpath,最后连报错都看不出主线。

如果不想改动 Spark 安装目录,另一条路是用spark.yarn.dist.jars指定额外分发的 jar 列表:

# 不走复制,改由 YARN 分发,适合不希望在 Spark 目录里留痕的场景 spark.yarn.dist.jars hdfs:///spark/extra/hive-exec-2.3.0.jar,hdfs:///spark/extra/hive-metastore-2.3.0.jar

两种方式的差别在于:复制进jars/是全局生效,所有提交到这个包的作业都能看到;spark.yarn.dist.jars是作业级生效,粒度更细,但每个作业都要重新分发一次,作业量大时会有额外开销。

4.3 端到端验证一条 SQL

配置改完之后,用beeline连上 HiveServer2,跑一条最小可验证链路:

-- 强制切换到 spark 引擎,避免会话继承了服务端旧配置 set hive.execution.engine=spark; -- 建一张临时表,走本地存储,排除 HDFS 权限干扰 create table tmp_spark_smoke (id int, name string) stored as orc; insert into tmp_spark_smoke values (1, 'a'), (2, 'b'), (3, 'c'); -- 看执行计划里的引擎标识,确认走的是 Spark 而不是 MR explain select count(*) from tmp_spark_smoke;

explain的输出是判断成败最直接的依据。走 Spark 时执行计划会呈现 Spark 的算子树,走 MR 时则会看到 MapReduce 的 task 描述。同时去 YARN 的 ResourceManager 页面看,应该出现一个以SparkSubmit或 Hive 会话名开头的应用,点进去能看到 executor 数量与spark.yarn.jars指向的 HDFS 路径。

4.4 与 MR 引擎的对照观察

切换引擎之后有些变化是预期内的,不要当成故障排查。MR 引擎下每个作业的启动开销以十秒计,Spark 会话建立之后,后续多条 SQL 可以复用同一个 driver,小查询的响应时间会明显下降;代价是会话本身占用一批 executor 资源,即使空闲也不释放,多人共享的 HiveServer2 上这一点会放大成资源争抢。另外 Spark 引擎对mapreduce.*系列参数基本不敏感,那些在 MR 上调了多年的mapreduce.map.memory.mb之类配置,切过来之后全部失效,别再盯着它们找原因。

5. 避坑与排查:五个把作业卡死在半路的现场记录

下面这五条都是我在这个组合上真实遇到过的,按发生阶段排列。每一条都建议先看完现象再去翻日志,否则很容易被日志里的次级错误带偏。

5.1 提交阶段的坑

现象:spark-submit返回成功,YARN 上应用状态停在ACCEPTED,几分钟后变成FAILED,ApplicationMaster 日志里写着Could not resolve host或者容器启动后立刻退出,日志里只有一个ClassNotFoundException。

原因:spark.yarn.jars写成了本地路径,比如local:/opt/spark/jars/*,但只有提交机和 HiveServer2 节点上有这个目录,其他 NodeManager 节点上是空的。容器在别的节点启动时拿不到 jar,自然起不来。

解决:把 jar 统一传到 HDFS,配置改成hdfs:///spark/jars/*,并用hadoop fs -ls /spark/jars | wc -l与本地数量比对确认传全了。这一步每次换包都要重做,别指望覆盖安装能自动同步。

现象:在spark-shell里执行spark.sql("select 1")正常,一旦加上enableHiveSupport()就抛java.lang.ClassNotFoundException: org.apache.spark.sql.hive.HiveSessionStateBuilder。

原因:这个包里没有spark-hive模块,enableHiveSupport()在这条发行线上从设计上就不可用。这不是配置问题,换多少个参数都解决不了。

解决:确认使用场景。如果是 Hive 调 Spark 执行,就不该在 Spark 侧开 Hive 支持,元数据交给 Hive 管;反过来如果是要用 Spark SQL 直接查 Hive 表,那下载的包选错了,换成标准发行版。

5.2 运行阶段的坑

现象:作业跑到一半,容器被 YARN 杀掉,日志关键字是Container killed by YARN for exceeding memory limits,但 executors 的内存看起来并没有用满。

原因:YARN 统计的是容器总内存,等于 executor 内存加上 overhead。Spark 2.3 的 overhead 默认取max(executorMemory * 0.10, 384MB),跑 shuffle 密集的作业、或者用到 Python UDF 时,这 10% 经常不够,尤其是spark.executor.memory设得比较大的时候,10% 的绝对值看似不小,但 PySpark 进程的内存开销是独立计算的。

解决:显式抬高spark.yarn.executor.memoryOverhead,一般给到 executor 内存的 15%~20%。2.3 里spark.executor.memoryOverhead这个新名字也认,但 YARN 场景下用旧的spark.yarn.executor.memoryOverhead兼容性更稳。改完记得同步 Hive 侧的hive-site.xml,两边不一致时会以 Hive 透传的值为准,容易让人误以为改没生效。

现象:任务全部成功,但 shuffle 阶段磁盘溢写量极大,stage 详情里 spill 数值远超输入数据量。

原因:spark.serializer还是默认的 Java 序列化。这个包解压后spark-defaults.conf是空的(只有模板注释),所有参数都走代码里的默认值,Java 序列化在对象较多时体积能差出好几倍。

解决:改成org.apache.spark.serializer.KryoSerializer,同时对自定义的 case class 注册 Kryo 序列化器。注意这一步不能盲目开,自定义类型没注册时会退化成字段级反射,性能反而不如 Java 序列化。

5.3 结果与退出阶段的坑

现象:一条insert overwrite在 MR 下产出 20 个文件,切到 Spark 后变成 200 个甚至更多小文件,下游读取明显变慢。

原因:spark.sql.shuffle.partitions默认 200,与数据量、集群核数都没有关系。小数据量下 200 个分区意味着 200 个输出文件,每个可能只有几十 KB。这是 Spark on Hive 改造后最常见也最容易被忽略的变化。

解决:按数据量估算分区数,经验值是单个分区处理 128MB~256MB 数据。写入前用distribute by或者repartition控制,也可以在会话级临时调set spark.sql.shuffle.partitions=40,跑完再恢复。这个值改大容易改小难,因为调小之后要重新验证执行时间,没有压测数据支撑时不要凭感觉设。

现象:同一条 SQL,MR 和 Spark 跑出来的聚合结果在小数位上不一致。

原因:浮点聚合的执行顺序不同导致精度差异,另外 MR 与 Spark 对某些隐式类型转换的处理规则不完全一致,尤其在decimal与double混算时。

解决:金额类字段统一用decimal(38,6)显式声明,避免隐式转换;对比验证时对浮点列做一次round再比。这类差异不是 bug,但迁移验收时必须提前和业务方对齐口径,否则上线后对账会很难看。

6. 调参与验收:给 2.3.0 一组能跑批的默认值

配置跑通之后,真正决定这套组合能不能上生产的,是资源参数的换算而不是功能。Spark 2.3 的执行器规划有一个很实用的思路:先定单个 executor 的规格,再算集群能容纳多少个,最后定并行度。单个 executor 的核数我一般给 4~5,低于这个数时单个 JVM 内的任务调度开销占比过高,广播变量也要在每个 executor 上多存几份;高于 5 时单个 executor 的 GC 压力变大,一次 Full GC 影响的范围更广。内存按每核 2~3GB 配,4 核的 executor 给 12~16GB 比较稳。

并行度用这个公式粗算:总核数 × 2~3,得到的是spark.sql.shuffle.partitions的参考值。比如 10 个 executor、每个 4 核,总共 40 核,分区数取 80~120 比较合适。这个值和数据量要交叉验证,如果每个分区只有几 MB,说明分区数偏大;如果单个 task 跑了几十分钟,说明分区数偏小。下面这组是我在一个批处理集群上稳定运行过一段时间的配置:

# 执行器规格:4 核 12G,overhead 给到 2G spark.executor.cores 4 spark.executor.memory 12g spark.yarn.executor.memoryOverhead 2g # 并行度:40 核 × 2.5,取整到 100 spark.sql.shuffle.partitions 100 # 广播阈值调到 50MB,小表 join 走广播避免 shuffle spark.sql.autoBroadcastJoinThreshold 52428800 # 单次拉取结果上限,避免 collect 把 driver 打挂 spark.driver.maxResultSize 2g

spark.sql.autoBroadcastJoinThreshold这一项在数仓场景里收益最直接。Hive 的数仓模型里维表通常不大,广播之后能省掉一整轮 shuffle,但阈值不能无脑调大,超过 executor 内存的广播表会直接把内存打爆,症状是 executor 频繁 Full GC 然后被 YARN 杀掉。

验收阶段我会固定做一张对照表,把 MR 与 Spark 的同一批 SQL 放在一起比。比的不只是耗时,还有产出文件数、峰值内存、以及那条最容易出问题的浮点列:

验收项判定标准采集方式
结果一致性行数完全相等,浮点列 round 后相等对同一张结果表跑 diff 查询
产出文件数不超过 MR 版本的 1.5 倍hadoop fs -count对比目录文件数
单条耗时小查询明显下降,大查询持平或下降History Server 按 application 比对
资源峰值不超过队列配额的 80%YARN 页面看峰值内存与 vcore
会话稳定性连续 50 条 SQL 不出现会话超时HiveServer2 日志按 session 统计

这张表里“会话稳定性”是最容易漏的一项。Hive on Spark 的会话是长连接,前面几十条 SQL 都正常,跑到某一条突然报连接超时,回头看是 driver 端 GC 停顿太久导致 RPC 心跳错过。遇到这种情况,先把hive.spark.client.server.connect.timeout调大只能治标,根子还在 driver 内存和 GC 上。

从那以后我每次做引擎切换,都会先在灰度队列上用 10 条典型 SQL 跑满一整天,把峰值内存和文件数记下来,再决定要不要全量。这个习惯救过我两次,一次是发现某张宽表的广播阈值超了,一次是发现动态分区写出的目录数暴涨。希望这些记录能帮你在自己的集群上少走几段弯路。

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

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

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

立即咨询