ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

云主机四节点Hadoop集群与OBS对象存储大数据实验链路

云主机四节点Hadoop集群与OBS对象存储大数据实验链路

简介:面向高校大数据课程学习者和华为云初学者,这份实验报告围绕云上大数据平台的部署与查询展开,覆盖弹性云服务器、OBS对象存储、Hadoop与Spark集群搭建,以及BigData Pro综合方案和交互式查询等实践环节。包内含1个docx文档,大小约4.36MB,以实验记录形式组织,便于按实验模块查阅环境准备、节点配置、集群互联和查询验证等内容。目前已有2541人学习,说明其在课程实验与入门实践中有一定参考价值。读者可从中了解ECS与OBS的协同配置、多节点SSH互信与Hadoop集群搭建流程、Spark环境配置及与OBS互联的测试思路,也能看到BigData Pro环境下从资源购买到集群管理的完整链路,以及使用SQL进行大规模数据探索的基本方法,适合用作实验复盘和操作对照。

1. 云主机跑 big data:这份实验报告真正值得抄的是那条完整链路

四台云主机、一个对象存储桶、一套 Hadoop、一个 Spark,把这几样东西串起来,就是一份典型云上 big data 大数据实验报告的全部骨架。很多人拿到这类资源第一反应是翻到最后看结论,其实真正值钱的是中间那段集群搭建与对象存储互联的操作序列——它决定了后面所有实验能不能跑起来,也决定了你踩坑时有没有参照物。这份报告覆盖的四个实验分别是云主机实现大数据、云服务实现大数据、BigData Pro 综合实验和大数据交互式查询,拆开看是四个任务,连起来是一条从裸机分配到 SQL 出结果的完整链路。它适合正在做课程大作业的学生,也适合第一次把本地 Hadoop 搬到云上的工程师——本地集群和云上集群最大的差别不在软件,而在网络拓扑、密钥权限和对象存储这三件事上。

2. 四节点拓扑与云资源选型:ECS 规格、VPC 网段和 OBS 桶怎么定

动手之前先把拓扑定下来,比装到一半发现内存不够再重建要省事得多。这一章讲的是资源账:几台机器、什么规格、放哪个子网、桶开在哪个区域,以及这些选择在后面的 Hadoop 和 Spark 阶段会带来什么连锁反应。

2.1 为什么是四节点而不是三台或八台

最小可验证的 Hadoop 集群其实两台就够,一台当主节点跑 NameNode 和 ResourceManager,一台当从节点跑 DataNode 和 NodeManager。但这样做有两个问题:一是副本数只能设 1,等于没验证 HDFS 的容错逻辑;二是所有角色挤在一台机器上,内存稍紧就容易出现 DataNode 被 NodeManager 抢资源后假死的情况,排查起来非常费劲。

四节点是比较舒服的分法:node1 承担 NameNode、SecondaryNameNode、ResourceManager,node2 到 node4 只做 DataNode 和 NodeManager。副本数可以设 2,坏一台仍有完整数据,同时三台从节点的算力也足够跑 wordcount 和几个小的 Spark 任务。八节点当然更接近生产,但对课程实验来说纯属浪费额度,而且每多一台机器,SSH 互信、hosts 维护、配置分发的工作量都线性增长。

规格上有个血泪经验:主节点和从节点不要用同一个规格。主节点内存吃紧会直接导致集群启动失败,从节点内存吃紧最多是任务变慢。我一般会这么配:

节点角色vCPU / 内存系统盘说明
node1NameNode / SecondaryNameNode / ResourceManager4 核 8G高 IO 60G元数据与调度都在这里,内存不能省
node2DataNode / NodeManager2 核 4G高 IO 60G数据盘按数据量估,实验阶段 60G 够用
node3DataNode / NodeManager2 核 4G高 IO 60G与 node2 同规格,方便横向对比
node4DataNode / NodeManager2 核 4G高 IO 60G也可临时兼做 Spark 客户端

磁盘类型要选高 IO 或者 SSD 类,普通盘在格式化 NameNode、写入大量小文件时那个等待时间会让人怀疑人生。四台机器必须落在同一个 VPC、同一个子网内,最好连可用区都一致,跨可用区通信虽然能通,但延迟抖动会让 YARN 的心跳超时判断变得很玄学。

还有一个容易被忽略的点:购买时一次性把四台都买在同一安全组下,或者先建好安全组再挂机器。后面再改安全组规则,如果改错了方向,机器之间的 SSH 会突然断掉,而你又没有控制台之外的入口,只能重置。

2.2 OBS 桶与并行文件系统:区域、命名和密钥三件事

对象存储这一层最容易出问题的不是代码,是配置。先把三件事定死:桶在哪个区域、桶叫什么名字、密钥怎么生成。

桶的区域必须和 ECS 同区域。同区域内网访问走的是内网端点,不计公网流量费,延迟也低;跨区域访问不但慢,还会产生额外的流量费用,实验做到一半发现账单异常往往就是这个原因。桶名全局唯一,只能用小写字母、数字和短横线,长度 3 到 63 个字符,命名时最好带上用途前缀,比如bdp-lab-2024,避免和别人撞名后反复重试。

密钥这块,控制台上创建访问密钥后会得到一个包含 AK/SK 的 CSV 文件,这个文件只允许下载一次,关掉页面就再也拿不到了,只能删除重建。所以下载后立刻存到本地密码管理器或者加密盘里。AK 是身份标识,SK 是签名密钥,两者都不要写进代码仓库,实验场景下用环境变量或者配置文件加 600 权限最稳妥。

如果实验涉及 Spark 或 Hive 直接读写,建议直接用并行文件系统类型的桶。它对外提供类 POSIX 语义,重命名、追加这类操作的行为比普通对象桶更接近本地文件系统,Spark 写临时文件时不容易出现 rename 失败。普通桶也能用,只是在_temporary目录的处理上要多留个心眼。

参数建议值说明
区域与 ECS 同区域跨区域会产生流量费且延迟高
桶类型并行文件系统类 POSIX 语义,适合大数据读写
访问方式仅内网端点公网端点实验阶段不必开
密钥存放环境变量 / 配置文件 600不落代码库,不写进 shell 历史

提示:把 AK/SK 写进~/.bash_history是新手最常犯的错,用export之前先set +o history,或者干脆写进独立的env.sh再source。

2.3 安全组与 hosts:把内网互通一次做对

安全组是云上和本地集群差别最大的地方。本地机器默认互相能通,云上默认全封。四台机器要组成集群,必须放通内网段之间的若干端口,而不是简单地放通 0.0.0.0/0。

正确的做法是新建一个安全组,入方向规则里把源地址写成子网网段,比如192.168.0.0/24,然后逐条放通需要的端口。这样即使某台机器误开了公网 IP,也不会把 HDFS 的 9000 端口暴露出去。需要的端口大致是这些:

端口用途
22SSH 与配置分发
9000HDFS NameNode RPC
9870NameNode Web UI
9864 / 9866DataNode HTTP / 数据传输
8088YARN ResourceManager Web UI
8030-8033YARN 调度相关 RPC
7077 / 8080 / 4040Spark 集群模式与 UI

配置完安全组,再统一写 hosts。这一步必须在装 Hadoop 之前做完,因为 Hadoop 的配置文件里到处是主机名,等装完再改主机名会导致 DataNode 注册不上。四台机器执行同一段命令:

# 四台机器分别执行,hostnamectl 后的名字各自改 hostnamectl set-hostname node1 # 关闭防火墙,云上由安全组负责,机器内 firewalld 会干扰端口通信 systemctl stop firewalld systemctl disable firewalld # 写入统一的 hosts,四台机器内容完全一致 cat >> /etc/hosts <<'EOF' 192.168.0.10 node1 192.168.0.53 node2 192.168.0.169 node3 192.168.0.77 node4 EOF

这里用<<'EOF'而不是<<EOF,是为了让变量不被提前展开,内容原样写入。IP 全部用内网地址,不要写公网地址——一旦 hosts 里是公网 IP,Hadoop 的 DataNode 会通过公网互相注册,走的是公网带宽,延迟高还收费,而且公网 IP 在重启后可能变化,集群直接崩。

注意:改完 hostname 要重新登录一次 shell,否则hostname命令显示的可能是旧值,后续ssh node2之类的操作会用到旧的解析结果。

3. Hadoop 集群落地:从 OpenJDK 到 wordcount 跑通的十四个配置项

资源准备好了,接下来是最费时间的部分。这一章的每一步都能直接照抄,但抄之前建议先看清每个参数为什么这么填,因为实验报告里那些配置项在换机器规格、换 Hadoop 版本之后不一定还能用。

3.1 环境基线:OpenJDK、时间同步和安装包分发

Hadoop 对 JDK 的要求比想象中严格。2.x 和 3.x 系列在 JDK 8 上最稳,JDK 11 部分版本会有反射相关的警告,JDK 17 则直接跑不起来。所以统一装 OpenJDK 8,四台机器版本必须完全一致,否则编译出来的 class 版本不匹配,任务会报UnsupportedClassVersionError。

在线安装是最省事的方式,前提是机器能访问软件源。如果实验环境的机器只通了内网,那就得先在一台机器上下载好 rpm 包,再用 scp 分发。四台机器分别执行:

# 安装 OpenJDK 8,四台机器都要执行 yum install -y java-1.8.0-openjdk java-1.8.0-openjdk-devel # 确认版本,四台输出应完全一致 java -version # 配置 JAVA_HOME,写进全局 profile 避免每次登录都要 export echo 'export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk' >> /etc/profile echo 'export PATH=$JAVA_HOME/bin:$PATH' >> /etc/profile source /etc/profile

java-1.8.0-openjdk-devel这个包不能省,只装 JRE 的话后面编译 Java 程序会失败。路径/usr/lib/jvm/java-1.8.0-openjdk是软链接,实际指向带版本号的目录,用它比用真实路径更保险,升级小版本不会失效。

时间同步也顺手做掉。四台机器时间差超过几分钟,YARN 的资源申请就会莫名其妙被拒,日志里只留下一句含糊的InvalidResourceRequestException。实验环境里直接用 chrony 指向同一个内网时间源就行,或者至少保证四台机器都在同一个时区、误差在一分钟内。

安装包分发用 scp 就够了。把 Hadoop 压缩包传到 node1 的/opt/soft下,解压到/opt/module,然后循环分发:

# 在 node1 上执行,把解压好的目录同步到其余三台 for h in node2 node3 node4; do scp -r /opt/module/hadoop-2.7.7 root@$h:/opt/module/ done

解压后先别急着配,在 node1 上执行一次bin/hadoop version,确认它能读到你设置的JAVA_HOME。如果报JAVA_HOME is not set,说明hadoop-env.sh里的配置没生效,这一步一定要在分发前解决,否则四台机器都得改一遍。

3.2 四节点 SSH 互信:一条循环搞定,但有两个前提

Hadoop 的启动脚本靠 SSH 去各节点拉进程,所以 node1 必须能免密登录到 node2、node3、node4,同时也要能免密登录自己。其他三台之间是否互信,取决于你后续要不要从从节点发起操作,实验阶段建议四台两两互信,省得后面 Spark 的 worker 启动时卡在认证上。

第一步是生成密钥对。四台机器分别执行,直接回车不设密码短语:

# 四台机器分别执行,-N '' 表示空密码,-f 指定路径 ssh-keygen -t rsa -b 2048 -N '' -f ~/.ssh/id_rsa # 确认权限,这是免密失败最常见的原因 chmod 700 ~/.ssh chmod 600 ~/.ssh/id_rsa chmod 644 ~/.ssh/id_rsa.pub

权限这一步是玄学重灾区。.ssh目录给 755 或者私钥给 644,SSH 会直接拒绝使用这对密钥,日志里只写Authentication refused: bad ownership or modes,不提具体是哪个文件。所以每次改完权限,用ls -l确认一遍再往下走。

第二步是分发公钥。一条循环就能搞定,前提是先把每台机器的/etc/hosts配好,并且当前 shell 能解析这些主机名:

# 在 node1 上执行,把本机公钥追加到四台机器的 authorized_keys for h in node1 node2 node3 node4; do ssh-copy-id -o StrictHostKeyChecking=no root@$h done # 四台机器都做一遍同样的循环,才能两两互信 # 验证:任意两台之间互相 ssh,不应出现密码提示 ssh node2 'hostname'

-o StrictHostKeyChecking=no是为了跳过首次连接的指纹确认,批量操作时很有用。验证环节别只看 node1 到其他节点,随便挑一台从节点反向登录 node1 试一次,能发现单向信任的问题。还有一个隐蔽的坑:第一次ssh某台机器时如果没有输入 yes,known_hosts里就没有记录,后面 Hadoop 脚本用-o BatchMode=yes去连接会直接失败,所以验证阶段务必把四台两两连一遍。

3.3 核心配置文件:改哪几个参数、为什么改

Hadoop 的配置文件看着多,真正必须改的就那么几个。下面这四组是底线,剩下的用默认值基本能跑。

core-site.xml里最关键的是文件系统入口。这个地址写什么,hadoop fs -ls /就访问什么:

<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://node1:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/opt/module/hadoop-2.7.7/data/tmp</value> </property> </configuration>

fs.defaultFS用主机名而不是 IP,这样即使内网 IP 变了,只要 hosts 更新就还能用。hadoop.tmp.dir必须显式指定到一个有足够空间的目录,默认值在/tmp下,机器重启后大概率被清空,格式化信息一丢,集群就起不来了。

hdfs-site.xml决定副本数和元数据存放位置:

<configuration> <property> <name>dfs.replication</name> <value>2</value> </property> <property> <name>dfs.namenode.name.dir</name> <value>/opt/module/hadoop-2.7.7/data/namenode</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>/opt/module/hadoop-2.7.7/data/datanode</value> </property> </configuration>

三台 DataNode 的情况下副本数填 2 是折中:填 1 失去容错意义,填 3 意味着任何一台下线都没法写入新块,反而降低了可用性。目录分开写是必须的,NameNode 和 DataNode 的数据混在一个目录,格式化时容易误删。

yarn-site.xml决定资源调度,两个参数必须改:

<configuration> <property> <name>yarn.resourcemanager.hostname</name> <value>node1</value> </property> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> <property> <name>yarn.nodemanager.resource.memory-mb</name> <value>3072</value> </property> </configuration>

aux-services忘了配的典型现象是:MapReduce 任务能启动,Map 阶段跑完 100% 之后卡死不动,最后超时失败。原因就是 Shuffle 阶段没有服务接手,任务在等一个永远不来的数据。resource.memory-mb建议设成节点内存的 75% 左右,2 核 4G 的从节点填 3072 比较稳,填满 4096 会让系统本身没有余量,NodeManager 端起不来。

最后是workers文件(2.x 里叫slaves),写上三个从节点的主机名,每行一个。这个文件决定了start-dfs.sh会去哪些机器拉 DataNode 进程,写成 IP 或者写错主机名都会导致启动脚本静默跳过那台机器。

3.4 格式化与启动:wordcount 是唯一的验收标准

配置文件分发到四台机器之后,第一次启动前必须格式化 NameNode,而且只能格式化一次:

# 只在 node1 执行,且只在首次搭建时执行 hdfs namenode -format # 启动 HDFS 与 YARN,脚本在 node1 上执行即可 start-dfs.sh start-yarn.sh # 四台机器分别执行,确认进程齐全 jps

jps的输出是判断集群健康最直接的方式。node1 上应该看到 NameNode、SecondaryNameNode、ResourceManager,从节点上应该看到 DataNode 和 NodeManager。少哪个进程,就去对应的日志目录里翻logs/*.log,通常前二十行就能看到原因。

格式化的坑值得单独说:如果集群跑起来之后因为配置错误又格式化了一次,NameNode 会生成新的 clusterID,而 DataNode 的 VERSION 文件里还是旧的,结果是 DataNode 启动后立刻退出,日志里写着Incompatible clusterIDs。解决办法是把所有节点的 data 目录清空再重新格式化,而不是反复执行-format。

最后用 wordcount 做端到端验收。这一步同时验证了 HDFS 读写、YARN 调度和 MapReduce 执行三条链路:

# 创建输入目录,把配置文件本身当输入数据 hadoop fs -mkdir -p /input hadoop fs -put $HADOOP_HOME/etc/hadoop/*.xml /input # 提交 wordcount 作业 hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar \ wordcount /input /output # 查看结果,应能看到 xml 标签被统计出来 hadoop fs -cat /output/part-r-00000 | head -20

作业跑完的时间取决于机器规格,四节点 2 核 4G 的配置下通常两三分钟。如果卡在map 0% reduce 0%超过五分钟,先去 8088 端口看作业状态,再去 NodeManager 日志里找容器启动失败的原因,八成是内存参数给太小或者 Shuffle 服务没配。输出目录/output必须是不存在的,重复提交同一个路径会直接报FileAlreadyExistsException,换个名字或者先hadoop fs -rm -r /output。

4. 打通对象存储:Hadoop 与 Spark 读写 OBS 的接法与参数

Hadoop 集群自己跑通只是第一步,实验报告里的重头戏是让计算层直接读写对象存储。这一层做不通,后面所有的 Spark 和交互式查询都是空谈。

4.1 走 s3a 还是走厂商连接器:差异与选择

云上的对象存储大多兼容 S3 协议,Hadoop 生态里对应的实现叫s3a,它是 Apache 官方维护的,随 Hadoop 一起发布,不需要额外装东西。另一种做法是用云厂商提供的对象存储连接器,通常是一个 jar 包,放进$HADOOP_HOME/share/hadoop/common/lib下即可。

两者的差别在细节上。s3a的优势是通用、随版本走、文档全,缺点是它对文件系统的语义模拟有限,重命名操作本质上是复制加删除,处理大量小文件时性能很差。厂商连接器一般会针对自家存储做优化,比如支持更高效的重命名、更好的并发控制,但版本和 Hadoop 版本强绑定,升级 Hadoop 时经常要跟着换包。

我的建议是:实验和课程作业用s3a,因为不用折腾 jar 包依赖;如果后续要做正经的数据管道,再评估厂商连接器。判断标准很简单——如果你发现 Spark 写数据时commit阶段耗时占比超过一半,那就是重命名开销的问题,该换连接器了。

不管走哪条路,都需要在core-site.xml里补一组访问配置:

<configuration> <property> <name>fs.s3a.endpoint</name> <value>obs.&lt;region&gt;.example-cloud.com</value> </property> <property> <name>fs.s3a.access.key</name> <value>${env.OBS_AK}</value> </property> <property> <name>fs.s3a.secret.key</name> <value>${env.OBS_SK}</value> </property> <property> <name>fs.s3a.path.style.access</name> <value>true</value> </property> <property> <name>fs.s3a.connection.ssl.enabled</name> <value>true</value> </property> </configuration>

endpoint填内网端点,path.style.access设成 true 是因为部分对象存储不支持虚拟主机风格的访问,设成 false 会报域名解析失败。AK/SK 用${env.XXX}的形式引用环境变量,避免明文落在配置文件里——如果某台机器的环境变量没配,启动时会直接报找不到变量,比默默用一个错的密钥要好排查。

4.2 连通性测试:从 put 到 get 走一遍

配置改完先做最小验证,不要直接上 Spark 作业。用hadoop fs命令访问对象存储路径是最快的探针:

# 列出桶内内容,这一步验证 endpoint 与密钥是否正确 hadoop fs -ls s3a://bdp-lab-2024/ # 上传一个本地文件,验证写权限 hadoop fs -put /opt/module/hadoop-2.7.7/etc/hadoop/core-site.xml \ s3a://bdp-lab-2024/test/core-site.xml # 读回来,验证下载链路 hadoop fs -cat s3a://bdp-lab-2024/test/core-site.xml | head -5

三条命令分别对应三个失败面:-ls失败通常是 endpoint 或桶名写错,报UnknownHostException或者 404;-put失败是密钥权限不足,报 403;-cat失败一般是网络策略问题。分步验证的好处是错误信息指向明确,比在一个 Spark 作业里看到一堆堆栈要好处理得多。

执行前记得在每台机器上source一下环境变量文件。只在 node1 上配了 AK/SK,从节点跑任务时就会报认证失败,而这个错误在 YARN 的容器日志里往往被埋在几十行 WARN 之后。

4.3 性能与计费上的现实约束

对象存储和 HDFS 的模型完全不同,这一点直接决定了作业怎么设计。HDFS 是块存储,追加和重命名是元数据操作;对象存储是键值对,重命名等于复制整个对象再删掉原对象。这个差异带来的直接影响是:Spark 或 MapReduce 写输出时,_temporary目录到最终目录的提交阶段会非常慢,输出文件越多越慢。

所以第一个约束是控制输出文件数量。用repartition或者coalesce把结果压到几十个文件以内,比生成几千个小文件再想办法合并要划算。第二个约束是不要把对象存储当 HDFS 用,中间结果还是落在 HDFS 的本地盘上,只在最终输出和跨作业共享数据时用对象存储。

计费上要留意的点:同区域内网访问不计流量费,这个是大前提;跨区域访问按流量计费;请求次数也计费,大量小文件的 GET/PUT 请求数量会很可观。实验阶段数据量小感觉不到,一旦数据规模上去,把几千个几 KB 的文件怼进桶里,请求费用可能比存储费用还高。

提示:判断是否走了内网,看fs.s3a.endpoint的域名用的是内网还是公网地址。用错了不会报错,只是慢,而且账单上会体现出来。

5. 避坑排查:四节点集群最容易翻车的五处

这一章是从实际搭建过程里攒出来的,每一条都对应一个具体现象、一个根因和一个可操作的解决办法。排查顺序建议按现象匹配,不要按顺序试。

5.1 免密登录昨天还好,今天就要密码

现象:集群跑了一晚上,第二天start-dfs.sh卡在某台机器上要求输入密码,输入后又提示Permission denied。

原因:.ssh目录或密钥文件权限被修改。常见触发是scp -r分发目录时把权限带过去了,或者有人为了"方便"把私钥改成 644。

解决:四台机器统一执行chmod 700 ~/.ssh && chmod 600 ~/.ssh/id_rsa && chmod 644 ~/.ssh/id_rsa.pub,然后用ssh -vvv node2看详细握手过程,输出里会明确写出哪个文件的权限被拒绝。改完权限重新试一次,不需要重新生成密钥。

5.2 DataNode 启动后立刻退出,日志写 clusterID 不匹配

现象:start-dfs.sh执行完,node1 上只有 NameNode,从节点的 DataNode 起不来,jps看不到进程。

原因:NameNode 被格式化过不止一次,新生成的 clusterID 和 DataNode 本地 VERSION 文件里的旧 ID 对不上。

解决:不要反复格式化。先停掉所有进程,删除所有节点上的dfs.datanode.data.dir和dfs.namenode.name.dir目录内容,然后只在 node1 上执行一次hdfs namenode -format,再统一启动。如果集群里已经有数据,格式化前必须确认数据可以丢,这一步没有后悔药。

5.3 集群看似正常,但任务提交后容器全部失败

现象:wordcount 作业提交后,map 0%卡住,8088 页面上看到所有容器都是 FAILED,NodeManager 日志里有大量Container exited with a non-zero exit code 1。

原因:NodeManager 分配的内存超过节点实际可用量,容器启动时被系统 OOM killer 杀掉;或者yarn.nodemanager.aux-services没配,Shuffle 服务没起来。

解决:先把yarn.nodemanager.resource.memory-mb降到节点内存的 75% 以下,同时把mapreduce.map.memory.mb和mapreduce.reduce.memory.mb显式设小一点。再确认aux-services的值是mapreduce_shuffle,改完同步到四台机器并重启 YARN。

5.4 访问对象存储报 403,但密钥明明是对的

现象:hadoop fs -ls s3a://bucket/报AccessDenied,控制台上看密钥状态正常。

原因:三种可能——密钥所属的账号没有该桶的读权限;endpoint 用了另一个区域的地址;机器时间偏差超过签名有效期允许的范围。

解决:先确认桶的权限策略里给了这个账号读权限,再核对 endpoint 的区域与桶的区域一致,最后用date看四台机器的时间,偏差大于五分钟就同步一下。签名机制对时间敏感,机器时间漂移是这类 403 里最隐蔽的一种。

5.5 Spark 提交到 YARN 报找不到主类或 jar 包

现象:spark-submit --master yarn提交后立刻失败,日志里是ClassNotFoundException或者Failed to locate the jar。

原因:集群模式(cluster)下 driver 跑在 NodeManager 上,本地路径的 jar 在那边不存在;或者HADOOP_CONF_DIR没设,Spark 找不到 YARN 的配置。

解决:用--master yarn --deploy-mode client先跑通,确认逻辑没问题再切 cluster 模式;切 cluster 时把依赖用--jars明确列出,或者打成 fat jar。同时在spark-env.sh里显式设置HADOOP_CONF_DIR指向 Hadoop 配置目录,四台机器保持一致。

6. 交互式查询进阶:Spark SQL 直查 OBS 数据与结果校验

前面几章搭起来的东西,最终要落到"能查"这件事上。实验报告里的大数据交互式查询环节,本质就是让 Spark SQL 直接对着对象存储里的数据建外表,然后用 SQL 把结果算出来。这一步跑通,整份实验的价值才算闭环。

做法上,先确认 Spark 能读到 OBS 的配置。把第 4 章的core-site.xml内容合并进$SPARK_HOME/conf下,或者用--files在提交时带上,然后启动spark-sql:

# 启动交互式 SQL,带上对象存储配置 spark-sql --master yarn \ --conf spark.hadoop.fs.s3a.endpoint=obs.<region>.example-cloud.com \ --conf spark.hadoop.fs.s3a.access.key=$OBS_AK \ --conf spark.hadoop.fs.s3a.secret.key=$OBS_SK

进去之后建外表。关键点是LOCATION指向对象存储路径,且表类型用 EXTERNAL,删表不会删数据:

CREATE EXTERNAL TABLE access_log ( dt STRING, uid STRING, url STRING, status INT ) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' STORED AS TEXTFILE LOCATION 's3a://bdp-lab-2024/warehouse/access_log/'; -- 先看分区/文件是否被正确识别 SELECT count(*) FROM access_log; -- 按状态码聚合,验证计算链路 SELECT status, count(*) AS cnt FROM access_log GROUP BY status ORDER BY cnt DESC LIMIT 10;

字段类型要和实际文件对齐,CSV 里如果带引号或者有表头,count(*)会把表头也算进去,数字看着多一行。STORED AS TEXTFILE适合实验数据,真实场景下改成 Parquet 或 ORC,列式存储能让聚合查询快一个数量级,而且对象存储上的读取量会小很多。

验证结果是否可信,我的习惯是做一次交叉校验:用hadoop fs -cat把原始文件取下来,用awk数一遍某个状态码的行数,再和 SQL 结果对一下。对不上就去看EXPLAIN输出,重点看PartitionFilters和PushedFilters有没有生效——如果过滤条件没有下推,Spark 会把整个目录的数据拉回来再过滤,数据量一大就表现为"查询很慢但结果是对的"。

还有一个容易忽略的技巧:把查询结果写回对象存储时,加一个DISTRIBUTE BY或者手动repartition,控制输出文件数。我一般会按结果规模估算,每 128MB 一个文件比较合适,太小就是大量小文件,太大则单任务压力过高。写完用hadoop fs -ls数一下文件个数,超过预期就说明分区策略有问题。

从那以后我每次做完这类实验,都会先用一条count(*)和一次-cat交叉验证,再去写复杂的聚合逻辑——顺序反了,后面所有结论都建立在一个可能读错的数据集上。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进