小肥柴的Hadoop之旅 快速实验篇(A3)基于 Hive 的气象数据数仓构建与干旱指标初步分析
-
- 目录
- [0. 背景知识:为什么要用 Hive?](#0. 背景知识:为什么要用 Hive?)
- [1. 任务概述与目标](#1. 任务概述与目标)
- [2. 低资源环境下的内存与执行策略论证(核心工作)](#2. 低资源环境下的内存与执行策略论证(核心工作))
-
- [2.1 资源基础:可供 Hive 消耗的内存上限](#2.1 资源基础:可供 Hive 消耗的内存上限)
- [2.2 MR 作业内存消耗的通用分析/估算](#2.2 MR 作业内存消耗的通用分析/估算)
-
- [step 1:确定作业类型与阶段数](#step 1:确定作业类型与阶段数)
- [step 2:估算 Map 阶段内存](#step 2:估算 Map 阶段内存)
- [step 3:估算 Reducer 内存消耗公式](#step 3:估算 Reducer 内存消耗公式)
- [step 4:估算 Shuffle 数据传输量](#step 4:估算 Shuffle 数据传输量)
- [step 5:物化表写入开销](#step 5:物化表写入开销)
- [2.3 综合评总结](#2.3 综合评总结)
-
- [2.3.1 评估矩阵](#2.3.1 评估矩阵)
- [2.3.2 需要避免的操作](#2.3.2 需要避免的操作)
- [2.3.3 本地模式的真实作用范围](#2.3.3 本地模式的真实作用范围)
- [3. 环境与资源背景](#3. 环境与资源背景)
- [4. Hive 最小化安装与配置(实际操作记录)](#4. Hive 最小化安装与配置(实际操作记录))
-
- [4.1 下载与解压](#4.1 下载与解压)
- [4.2 配置环境变量](#4.2 配置环境变量)
- [4.3 创建必要目录](#4.3 创建必要目录)
-
- [4.4 hive-site.xml配置](#4.4 hive-site.xml配置)
- [4.5 初始化元数据库](#4.5 初始化元数据库)
-
-
- [4.5.1 第一次尝试](#4.5.1 第一次尝试)
-
- [5. 建表与数据加载(实际操作记录)](#5. 建表与数据加载(实际操作记录))
-
- [5.1 会话参数设置](#5.1 会话参数设置)
- [5.2 建表 DDL](#5.2 建表 DDL)
- [6. 分析查询与结果(含数据驱动的迭代调整过程)](#6. 分析查询与结果(含数据驱动的迭代调整过程))
-
- [6.1 数据概览](#6.1 数据概览)
- [6.2 站点维度分析](#6.2 站点维度分析)
- [6.3 时间维度分析](#6.3 时间维度分析)
- [6.4 干旱指标初步分析(含阈值迭代调整)](#6.4 干旱指标初步分析(含阈值迭代调整))
-
- [6.4.1 查询降水量最少月份](#6.4.1 查询降水量最少月份)
- [6.4.2 站点级干旱分析(含阈值探索与调整)](#6.4.2 站点级干旱分析(含阈值探索与调整))
- [7. 查询优化原则与方法总结](#7. 查询优化原则与方法总结)
- [8. 问题与排查记录](#8. 问题与排查记录)
- [9. A3阶段总结](#9. A3阶段总结)
目录
0. 背景知识:为什么要用 Hive?
在前面两个阶段(A2-1 和 A2-2),我们使用 Java 和 Python 编写 MapReduce 程序完成了数据清洗。MapReduce 虽然强大,但每完成一个统计任务都需要编写几十上百行代码、编译、打包、提交作业,门槛高、效率低。
(1)在实际的海量数据处理中,工程师们更习惯用 SQL (结构化查询语言)来分析数据。Hive 就是这样一个工具------它把 SQL 语句自动翻译成 MapReduce 作业,让用户在熟悉的表格操作模式下完成大数据分析,而不用写一行 Java 代码(其背后被隐藏起来的引擎等底层原理请自行深入研究)。
打个比方:如果 MapReduce 是"汇编语言",那 Hive 就是"高级语言"。你只需要告诉它"我要按站点统计观测次数",它就会自动生成 MapReduce 任务去执行。
(2)需要注意的是:对Hive的理解不能简单的认为它是MR程序的替身,而应该从数据的角度去认知;且在数据视角下,仍需要意识到:Hive 不是一个数据库,而是一个数据仓库工具 。它本身不存储数据,数据仍然在 HDFS 上;它存储的是"元数据"------即表名、列名、列类型等信息。这些元数据需要一个小的数据库来管理,且在当前资源紧张场景下我们选择了 Derby。
(3)Derby 是一个纯 Java 的嵌入式数据库,非常轻量(几 MB 大小),不需要单独安装和启动服务,适合单用户学习实验。Hive 会把表结构等信息存在 Derby 里,而真正的海量数据仍然留在 HDFS 上,查询时再从 HDFS 读取。在 2GB 内存的集群中,Derby 内嵌模式是最合适的选择------它不占用额外内存,不需要额外进程,让每一兆内存都用在刀刃上。
1. 任务概述与目标
| 项目 | 说明 |
|---|---|
| 定位 | 承接 A2-2 的清洗结果,使用 Hive 替代手写 MapReduce,构建数据仓库,对气象数据进行多维聚合分析 |
| 目标 | 1. 在 2GB 集群上搭建最小化 Hive(Derby 内嵌元数据库) 2. 创建外部表加载 cleaned 数据(9,218,700 行) 3. 使用 SQL 安全执行数据概览、站点聚合、时间聚合及干旱指标初步查询 4. 所有查询均在内存安全边界内完成,不触发 OOM 5. 总结低资源环境下的内存估算规程与查询优化原则 |
| 输入 | HDFS 路径 /drought/output_a2_2/cleaned-m-*(1.23 GB,22 列 CSV) |
| 输出 | 聚合结果表 + 物化中间表 + 分析结论 + 内存估算模型 |
2. 低资源环境下的内存与执行策略论证(核心工作)
在 2GB 内存集群上运行 Hive,任何一条复杂 SQL 都可能触发 OOM。我们需从资源基础、估算规程、具体计算最终得到安全设置策略,形成完整的推演链条。
2.1 资源基础:可供 Hive 消耗的内存上限
| 资源项 | 数值 | 说明 |
|---|---|---|
| 单节点物理内存 | 2 GB (2048 MB) | 虚拟机分配 |
| OS + 基础服务占用 | ~700 MB | 内核、缓存、DataNode、NodeManager 进程 |
| YARN 可管理内存 | 1024 MB/节点 | 由 yarn.nodemanager.resource.memory-mb 控制 |
| 集群总 YARN 内存 | 3072 MB (3节点) | --- |
| 单容器最大值 | 384 MB | 由 mapreduce.map.memory.mb 和 mapreduce.reduce.memory.mb 统一设定 |
| 单容器 JVM 堆 | 256 MB | 由 -Xmx256m 控制,剩余 128 MB 用于 JVM 堆外、进程开销 |
【结论2-1】 :任何 YARN 容器(Map 或 Reduce)可用堆内存上限为 256 MB ,可用堆外内存约为 128 MB,所有后续计算必须基于此边界。 ⇒ so,分布式难就难在计算机底层原理的十八般武艺,要样样精通啊!学习成本非常高。
2.2 MR 作业内存消耗的通用分析/估算
针对 Hive 翻译后的 MR 作业,按以下步骤逐项估算:
step 1:确定作业类型与阶段数
- Map-only 作业 (如
COUNT(*)、WHERE过滤):仅 Map 阶段,无 Shuffle,最安全。 - Map + Reduce 作业 (如
GROUP BY、ORDER BY):Map 阶段 + Shuffle 传输 + Reduce 聚合。
step 2:估算 Map 阶段内存
Map 阶段逐行流式读取输入数据,不积攒行。内存消耗 = 输入缓冲(由 io.sort.mb 控制,已设为 64 MB)+ 行解析临时对象(约 10~20 MB)。总和通常 < 100 MB,远小于容器 384 MB 上限。
【结论2-2】:Map 阶段在实验中始终安全,无需重点建模。
step 3:估算 Reducer 内存消耗公式
Reducer 阶段的内存消耗由三部分组成:
bash
Reducer 内存 ≈ 分组状态大小 + Shuffle 缓冲 + 排序缓冲
- 分组状态大小 = 该 Reducer 处理的唯一键数量 × 每个键的状态大小
- Shuffle 缓冲 :接收 Map 输出的缓冲区,由
mapreduce.reduce.shuffle.input.buffer.percent(默认 0.7)控制,约为 JVM 堆的 70% = 179 MB(以 256 MB 堆计算) - 排序缓冲:对拉取的数据进行排序,与 Shuffle 缓冲复用同一内存区域
关键变量:分组状态大小。本实验中,所有聚合操作的分组键数量均可预估:
| 聚合操作 | 分组键 | 预估键数 | 单键状态 | 分组状态大小 | 是否安全(< 256 MB 堆) |
|---|---|---|---|---|---|
GROUP BY station_id |
站点 ID | ~10 万 | 一个 long 计数器(8 字节)+ HashMap 开销 ≈ 40 字节 | ~4 MB | ✅ |
GROUP BY month |
月份 (yyyy-MM) | 12~24 个 | 同上 | < 1 MB | ✅ |
ORDER BY ... LIMIT N |
无需分组,仅保留前 N 条 | N ≤ 100 | 整行数据 ≈ 1 KB | < 1 MB | ✅ |
COUNT(DISTINCT station_id) |
维护全局去重集合 | 10 万 | 每个键约 40 字节 + 内存膨胀 | 1020 MB | ✅(但 Hash 碰撞可能增加) |
| 窗口函数(单站点,几千行) | 单分区内排序 | 几千行 | 窗口缓冲区 ≈ 几万行 × 100 字节 | 15 MB | ✅ |
【结论2-3】(核心结论):分组状态大小(4~20 MB)远小于 Shuffle 缓冲(179 MB),更远小于容器堆上限(256 MB),且Reducer 内存始终安全。
step 4:估算 Shuffle 数据传输量
Map 端输出键值对(如 <station_id, "1">),经序列化后每条约 30 字节。全量 9.2M 行 → 约 276 MB。均匀散列到 3 个 Reducer 后,每个 Reducer 接收约 92 MB 的 Shuffle 数据,在 179 MB 缓冲区内完全可容纳,不会触发磁盘溢写。
step 5:物化表写入开销
CTAS 作业的最后阶段是将 Reducer 结果写入 HDFS 表文件(数据库数据的落盘/物化,这块知识点如果不知晓,请自行补全《数据库原理及应用》课程内容,尤其是需要了解分布式数据库的一些基础套路)。
- 物化表
station_counts输出约 10 万行 × 30 字节 ≈ 3 MB。 - 物化表
monthly_summary输出几十行,< 1 MB。 - I/O 缓冲占用堆外内存,不影响 JVM 堆。
【结论2-4】:写入阶段无额外内存风险。
2.3 综合评总结
2.3.1 评估矩阵
用矩阵形式给出所有考虑因素的疑点+结论,方面后续作为语料投喂给AI做他用。
| 查询 | 作业类型 | Map 内存 | Reducer 键数 | 分组状态 | Shuffle/Red | 物化后大小 | 安全结论 |
|---|---|---|---|---|---|---|---|
SELECT COUNT(*) |
Map-only | < 100 MB | 无 Reduce | --- | --- | --- | ✅ 安全 |
approx_count_distinct |
Map+Red | < 100 MB | 内部 hash sketch | < 10 MB | ~92 MB/Red | --- | ✅ 安全 |
CTAS station_counts |
Map+Red | < 100 MB | ~10 万 | ~4 MB | ~92 MB/Red | ~3 MB | ✅ 安全 |
CTAS monthly_summary |
Map+Red | < 100 MB | 12~24 | < 1 MB | ~92 MB/Red | < 1 MB | ✅ 安全 |
ORDER BY + LIMIT |
Map+Red | < 100 MB | TopN 堆 | < 1 MB | ~92 MB/Red | --- | ✅ 安全 |
| 窗口函数(单站点) | Map+Red | < 100 MB | 单分区 | ~5 MB | < 10 MB | --- | ✅ 安全 |
2.3.2 需要避免的操作
| 操作 | 原因 |
|---|---|
COUNT(DISTINCT col) 在大表上 |
需维护全量去重集合,若唯一值数百万则分组状态可能膨胀至百 MB 级 |
| 多表 JOIN | 需要多次 Shuffle 或 Map 端全量加载小表,在 384 MB 容器中极易 OOM |
全量 ORDER BY 无 LIMIT |
Reducer 需对全量数据排序,9.2M 行完全塞不进 256 MB 堆 |
| 窗口函数在全量数据上 | 需要按分区排序,若分区数多且数据倾斜,某个 Reducer 可能接收大量行 |
2.3.3 本地模式的真实作用范围
Hive不一定必须运行在子节点上:
- 虽然
hive-site.xml中开启了hive.exec.mode.local.auto=true; - 但其触发条件是:输入数据量 <
hive.exec.mode.local.auto.inputbytes.max(默认 128 MB); - 本实验输入 1.23 GB,几乎所有查询都会提交为 YARN 作业。
实际执行日志也验证了这一点:
bash
Cannot run job locally: Input Size (= 1291086910) is larger than
hive.exec.mode.local.auto.inputbytes.max (= 134217728)
Starting Job = job_1780156095426_0001 ...
本地模式仅对从物化表(如 station_counts,仅 3 MB)读取的查询或 LIMIT 子查询有效。自此,所有 SQL 均已按 MR 作业进行内存规划。
3. 环境与资源背景
| 项目 | 配置 |
|---|---|
| 节点数 | 3(master + worker1/2/3) |
| 每节点内存 | 2 GB RAM |
| 每节点磁盘 | 30 GB |
| YARN 可用内存 | 每节点 1024 MB,单容器最大 384 MB |
| Hive 版本 | 3.1.3 |
| 元数据库 | Derby(内嵌) |
| 执行引擎 | MapReduce |
| 数据路径 | /drought/output_a2_2/cleaned-m-*(1.23 GB) |
4. Hive 最小化安装与配置(实际操作记录)
4.1 下载与解压
清华镜像站返回 404,改用华为云镜像站(也可以考虑跟换为其他镜像):
bash
cd /usr/local
sudo wget https://mirrors.huaweicloud.com/apache/hive/hive-3.1.3/apache-hive-3.1.3-bin.tar.gz
sudo tar -xzf apache-hive-3.1.3-bin.tar.gz
sudo mv apache-hive-3.1.3-bin hive
sudo chown -R hadoop:hadoop /usr/local/hive
4.2 配置环境变量
以追加形式填写hive相关环境变量:
bash
echo '' >> ~/.bashrc
echo '# ========== Hive 环境变量 ==========' >> ~/.bashrc
echo 'export HIVE_HOME=/usr/local/hive' >> ~/.bashrc
echo 'export PATH=$HIVE_HOME/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
执行上述动作后验证:
bash
hive --version
输出Hive版本号后,确认安全成功;当前环境中,实际输出为"Hive 3.1.3 "。
4.3 创建必要目录
主要是为使用Derby做准备工作:
bash
# HDFS 仓库目录
hdfs dfs -mkdir -p /user/hive/warehouse /tmp/hive
hdfs dfs -chmod g+w /user/hive/warehouse /tmp/hive
# 本地 Derby 元数据库目录
mkdir -p /home/hadoop/hive_metastore
4.4 hive-site.xml配置
若初始未创建配置文件可使用 cat > 直接覆盖写入(其他情况自行咨询AI解决):
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property>
<name>javax.jdo.option.ConnectionURL</name>
<value>jdbc:derby:;databaseName=/home/hadoop/hive_metastore/metastore_db;create=true</value>
</property>
<property>
<name>javax.jdo.option.ConnectionDriverName</name>
<value>org.apache.derby.jdbc.EmbeddedDriver</value>
</property>
<property>
<name>hive.metastore.warehouse.dir</name>
<value>/user/hive/warehouse</value>
</property>
<property>
<name>hive.exec.mode.local.auto</name>
<value>true</value>
</property>
<property>
<name>hive.auto.convert.join</name>
<value>false</value>
</property>
<property>
<name>hive.exec.dynamic.partition.mode</name>
<value>nonstrict</value>
</property>
<property>
<name>hive.exec.scratchdir</name>
<value>/tmp/hive</value>
</property>
</configuration>
【关键经验,仅供参考】 :Derby 路径必须使用绝对路径 。使用相对路径时 schematool 会在当前工作目录创建数据库,导致后续 Hive CLI 启动时找不到元数据。⇒ 瞧!哪里都有CLI,命令行才是正统!哈哈。
4.5 初始化元数据库
- 【叠甲】一下是我做实验的真实记录,含排错过程 ,自己实践时可以先将这段内容抛给AI,给出精简版本的指导书;但我个人还是秉持那个观点:数据清洗、分析和处理的过程,在实际工程落地中不会一次到位的,反反复复,拉扯纠结才是常态。
4.5.1 第一次尝试
- 初始化 :
从这里开始,所有操作均在master上完成。
bash
schematool -dbType derby -initSchema
报错:
bash
Error: FUNCTION 'NUCLEUS_ASCII' already exists. (state=X0Y68,code=30000)
- 排错与修复 :
(1)发现hive-site.xml未创建,导致 Derby 使用相对路径metastore_db创建在用户主目录
(2)创建正确的hive-site.xml(见 4.4)
(3)彻底清理残留,后重新初始化:
bash
rm -rf /home/hadoop/hive_metastore/metastore_db
rm -rf ~/metastore_db
rm -f ~/derby.log
bash
schematool -dbType derby -initSchema
最后看到:Initialization script completed 和 schemaTool completed字样,代表初始化成功。
- 根本原因 :首次初始化时未指定
hive-site.xml,Derby 使用相对路径创建了不完整的元数据库。后续创建了配置文件,但残留的相对路径数据库文件仍存在,导致重复初始化冲突;彻底删除所有残留后重置解决。 - 启动验证:
bash
hive
成功进入 Hive CLI,输出 Hive Session ID = ...,确认 Hive 安装完成。
具体而言,应根据实验阶段分步启动服务:
| 阶段 | 需启动服务 | 原因 |
|---|---|---|
| Hive CLI 建表 | 仅 HDFS(start-dfs.sh) |
外部表注册仅需 HDFS 可访问 |
| 执行分析查询 | HDFS + YARN(start-yarn.sh) |
MR 作业需要 YARN 调度容器 |
【实际执行经验参考】:建表阶段仅启动 HDFS,建表完成并验证表结构后,再启动 YARN 执行查询。这样可以隔离故障,且建表阶段节省 YARN 进程所占的约 300 MB 内存。 ⇒ 还是内存限制闹的。
5. 建表与数据加载(实际操作记录)
5.1 会话参数设置
因为每次关闭hive会话,缓存会被清空,所以每次进入 Hive CLI 后首先执行:
sql
SET hive.execution.engine=mr;
SET mapreduce.map.memory.mb=384;
SET mapreduce.map.java.opts=-Xmx256m;
SET mapreduce.reduce.memory.mb=384;
SET mapreduce.reduce.java.opts=-Xmx256m;
SET mapreduce.job.reduces=3;
SET hive.auto.convert.join=false;
以配置运行参数。
【注意】 :第一条 SET hive.execution.engine=mr 会触发弃用警告 Hive-on-MR is deprecated in Hive 2...,这是正常提示,不影响使用;在 2GB 集群上 MR 是唯一可行的引擎。
5.2 建表 DDL
在 Hive 中,DDL 的全称是 Data Definition Language(数据定义语言),用于定义、修改或删除数据库对象(如数据库、表、视图等)。
sql
CREATE EXTERNAL TABLE drought_cleaned (
station_id STRING COMMENT '站点ID',
record_date STRING COMMENT '日期 yyyy-MM-dd',
col_2 STRING,
col_3 STRING,
col_4 STRING,
precip DOUBLE COMMENT '降水量 PRECTOT',
col_6 STRING,
col_7 STRING,
temp DOUBLE COMMENT '温度 T2M',
col_9 STRING,
col_10 STRING,
col_11 STRING,
col_12 STRING,
col_13 STRING,
col_14 STRING,
col_15 STRING,
wind_speed DOUBLE COMMENT '风速 WS10M',
col_17 STRING,
col_18 STRING,
col_19 STRING,
col_20 STRING,
col_21 STRING
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
STORED AS TEXTFILE
LOCATION '/drought/output_a2_2'
TBLPROPERTIES ("skip.header.line.count"="0");
若执行结果显示OK,则说明建表成功;且初学者可以看到:HiveQL其实与SQL区别不大,就当强化学习了。接着需要查询:表结构验证,进一步确认数据正常加载。
sql
DESCRIBE drought_cleaned;
【预期】输出 22 个字段,耗时 0.453 秒,字段名、类型、注释均正确。
6. 分析查询与结果(含数据驱动的迭代调整过程)
【注】以下实践过程,都是我在集群上跑过一轮,确认之后的展示;所谓"预期结果"其实就是供参考的实际执行效果;相关查询过程就是对数据操作和研究其特点的一个过程示范。
6.1 数据概览
- step 1:查看总行数
sql
SELECT COUNT(*) AS total_rows FROM drought_cleaned;
| 指标 | 数值 |
|---|---|
| 返回结果 | 9,218,700 |
| 与 A2-2 一致性 | ✅ 完全一致 |
| Map 数 | 5 |
| Reduce 数 | 1(Hive 优化为单 Reducer 汇总计数) |
| 总耗时 | 49.626 秒 |
| 累计 CPU 时间 | 20.94 秒 |
| HDFS 读取 | 1,291,175,507 字节 |
| 执行模式 | YARN 集群模式(本地模式因数据量超 128 MB 自动切换) |
- step 2:站点数(去重)
尝试使用 approx_count_distinct 失败(函数不存在),改用标准 COUNT(DISTINCT) 并增加 Reducer 数保障安全:
sql
SET mapreduce.job.reduces=5;
SELECT COUNT(DISTINCT station_id) AS exact_stations FROM drought_cleaned;
| 指标 | 数值 |
|---|---|
| 返回结果 | 102,430 |
| 与 A2-2 唯一键数量 | ✅ 完全匹配 |
| Reduce 数 | 1(Hive 对 COUNT(DISTINCT) 强制单 Reducer 以保证正确性) |
| 耗时 | 34.265 秒 |
| 内存安全性 | 10.2 万个站点 ID 的去重集合约 10~20 MB,远低于 256 MB 堆上限 |
- step 3:查询日期范围
sql
SELECT MIN(record_date) AS min_date, MAX(record_date) AS max_date FROM drought_cleaned;
| 指标 | 数值 |
|---|---|
| 最小日期 | 2012-04-03 |
| 最大日期 | 2020-12-29 |
| 数据跨度 | 约 8 年 9 个月 |
| 耗时 | 37.142 秒 |
【预期执行结果】:数据总量 9,218,700 行,覆盖 102,430 个站点,时间跨度 2012-04 至 2020-12。
6.2 站点维度分析
- step 1:物化站点统计表
sql
CREATE TABLE station_counts AS
SELECT station_id, COUNT(*) AS obs_count
FROM drought_cleaned
GROUP BY station_id;
| 指标 | 数值 |
|---|---|
| Map 数 | 5 |
| Reduce 数 | 3(mapreduce.job.reduces=3 生效) |
| 耗时 | 41.971 秒 |
| HDFS 写入 | 2,395,232 字节(约 2.28 MB) |
- step 2:Top 20 站点观测数
sql
SELECT station_id, obs_count
FROM station_counts
ORDER BY obs_count DESC
LIMIT 20;
本地模式执行,耗时 3.616 秒。结果显示 Top 20 站点的观测数均为 90 次,表明数据集中观测次数高度一致。
- step 3:站点观测数分布
【注】认真观察正经的SQL编写是有缩进的,思考下为什么这会称为行业习惯?赶紧抛弃那种一行成仙的SQL写法。
sql
SELECT
CASE
WHEN obs_count < 10 THEN '1-9'
WHEN obs_count < 30 THEN '10-29'
WHEN obs_count < 50 THEN '30-49'
WHEN obs_count < 70 THEN '50-69'
WHEN obs_count < 90 THEN '70-89'
ELSE '90'
END AS obs_range,
COUNT(*) AS station_cnt
FROM station_counts
GROUP BY
CASE
WHEN obs_count < 10 THEN '1-9'
WHEN obs_count < 30 THEN '10-29'
WHEN obs_count < 50 THEN '30-49'
WHEN obs_count < 70 THEN '50-69'
WHEN obs_count < 90 THEN '70-89'
ELSE '90'
END
ORDER BY obs_range;
| 观测次数范围 | 站点数量 |
|---|---|
| 90 | 102,430 |
【预期结果】 :发现所有 102,430 个站点的观测次数均为 90 次。数据呈高度规整,平均每个站点每天一次观测,覆盖 90 天(约 3 个月),分散在 2012-2020 年间的不同时段。这一特征为后续分析提供了重要基线。
6.3 时间维度分析
- step 1:物化月度汇总表
sql
CREATE TABLE monthly_summary AS
SELECT
substr(record_date, 1, 7) AS month_key,
COUNT(*) AS obs_count,
AVG(temp) AS avg_temp,
SUM(precip) AS total_precip
FROM drought_cleaned
WHERE temp IS NOT NULL
GROUP BY substr(record_date, 1, 7);
| 指标 | 数值 |
|---|---|
| Map 数 | 5 |
| Reduce 数 | 3 |
| 耗时 | 39.313 秒 |
| HDFS 写入 | 5,590 字节(约 5.5 KB) |
- step 2:月度数据查询
sql
SELECT * FROM monthly_summary ORDER BY month_key;
本地模式,耗时 1.822 秒。数据覆盖 105 个月(2012-04 至 2020-12)。
【预期结果】:将会有关键发现
- 温度最低月份 :2014-03,平均温度 -0.30℃ ;其次为 2014-02,平均温度 -0.25℃。
- 温度最高月份 :2012-09,平均温度 24.93℃。
- 数据整体呈现温带气候特征,冬冷夏热,年温差约 25℃。
6.4 干旱指标初步分析(含阈值迭代调整)
6.4.1 查询降水量最少月份
sql
SELECT month_key, total_precip
FROM monthly_summary
ORDER BY total_precip ASC
LIMIT 5;
| 排名 | 月份 | 降水量 (mm) |
|---|---|---|
| 1 | 2014-03 | 271,840.87 |
| 2 | 2015-02 | 278,634.47 |
| 3 | 2014-02 | 292,937.57 |
| 4 | 2013-03 | 313,575.06 |
| 5 | 2012-04 | 327,628.77 |
降水量最少的月份集中在冬末春初(2~4 月),符合气候规律。
6.4.2 站点级干旱分析(含阈值探索与调整)
- step 1:首次尝试时,选取任意站点计算连续无降水天数
选择一个观测数为 90 的站点(此处设定为-1000799230488340175,你也可以根据实际情况自定义站点),创建单站点数据子集:
sql
CREATE TABLE single_site_data AS
SELECT record_date, precip
FROM drought_cleaned
WHERE station_id = '-1000799230488340175';
上述操作耗时 55.876 秒,HDFS 写入仅 1,442 字节。
接着,执行连续无降水天数查询(precip = 0),返回 NULL。于是检查该站点降水量范围:
sql
SELECT MIN(precip) AS min_precip, MAX(precip) AS max_precip
FROM single_site_data;
(1)实际执行结果 :最小降水量 2.1 mm ,最大降水量 13.69 mm。
(2)表明 :该站点在 90 天观测期内没有一天的降水量为 0。
由此引出下一步思考。
- step 2:全局探索:是否存在降水量为 0 的记录?
sql
SELECT station_id, COUNT(*) AS zero_days
FROM drought_cleaned
WHERE precip = 0
GROUP BY station_id
ORDER BY zero_days DESC
LIMIT 5;
操作耗时 33.074 秒,且返回空集 ,说明整个数据集中没有任何降水量为 0 的记录。这意味着数据集可能经过了预处理,仅保留了有降水的观测,或者数据来源的地区/季节本身不存在绝对无降水日;这暗示我们的检测规则要根据数据实际情况做出必要的调整。
- step 3:策略调整,从"无降水天数"改为"低降水天数"
既然没有零值,我们转向寻找降水量最低的站点,并以接近 0 的阈值定义"干旱倾向"。查询降水量最低的站点:
sql
SELECT station_id, MIN(precip) AS min_precip, AVG(precip) AS avg_precip
FROM drought_cleaned
GROUP BY station_id
ORDER BY min_precip ASC
LIMIT 5;
| 站点 ID | 最小降水量 (mm) | 平均降水量 (mm) |
|---|---|---|
| 6812413573084174289 | 0.10 | 1.28 |
| -838090275501305730 | 0.10 | 1.28 |
| -8020525461484131147 | 0.11 | 1.33 |
| -6442896892518939203 | 0.11 | 2.10 |
| 1046629422960063977 | 0.11 | 1.47 |
从商标可知:几乎所有站点的最小降水量均为 0.1 mm,平均降水量在 1.28~2.10 mm 之间。这些站点降水极少,干旱倾向明显;由实际情况触发,可以调整分析阈值的设定。
- step 4:重新定义干旱阈值,计算连续低降水量天数(< 0.5 mm)
选择最小降水量最低的站点 6812413573084174289,替换单站点表:
sql
DROP TABLE single_site_data;
CREATE TABLE single_site_data AS
SELECT record_date, precip
FROM drought_cleaned
WHERE station_id = '6812413573084174289';
使用阈值 precip < 0.5 作为"近似无降水",计算连续低降水天数:
sql
WITH daily_precip AS (
SELECT record_date, precip FROM single_site_data
),
precip_flag AS (
SELECT
record_date,
CASE WHEN precip < 0.5 THEN 0 ELSE 1 END AS is_dry,
ROW_NUMBER() OVER (ORDER BY record_date) -
SUM(CASE WHEN precip < 0.5 THEN 0 ELSE 1 END) OVER (ORDER BY record_date) AS grp
FROM daily_precip
)
SELECT MAX(consecutive_dry) AS max_dry_days
FROM (
SELECT grp, COUNT(*) AS consecutive_dry
FROM precip_flag
WHERE is_dry = 0
GROUP BY grp
) t;
| 指标 | 数值 |
|---|---|
| 最大连续低降水天数 | 1 天 |
| 执行模式 | 3 个 Stage,前两个为 YARN 模式,最后一个为本地模式 |
| 总耗时 | 63.882 秒 |
【结果解读】:即使是最干旱的站点,降水量低于 0.5 mm 的天数也是零散分布的,从未连续两天达到此阈值。这表明数据集中不存在传统意义上"连续无降水"的干旱事件,或数据集的时间粒度(非连续日)和筛选条件限制了此类分析。
7. 查询优化原则与方法总结
以下原则基于标题2的内存估算论证得出,适用于本实验所有 SQL:
| 原则 | 方法 | 目的 |
|---|---|---|
| 增加 Reducer 数 | SET mapreduce.job.reduces=3~5 |
分散单 Reducer 数据量与键数量,避免单点 OOM |
| 避免 COUNT(DISTINCT) | 使用 approx_count_distinct(若不可用则增加 Reducer 数) |
减少 Reducer 内存排序/去重压力 |
| 善用 ORDER BY + LIMIT | 聚合查询末尾加 LIMIT N |
Reducer 仅保留前 N 条,无需全量排序 |
| WHERE 尽早过滤 | 聚合前用 WHERE 滤除 NULL 或无关行 | 减少 Map 输出量和 Shuffle 传输量 |
| 积极使用物化中间表 | 将常用聚合结果用 CTAS 保存为表 | 避免重复全表扫描,后续查询极快且轻量 |
| 单站点/小范围测试 | 复杂逻辑先用一个站点或一个月的数据验证 | 防止未知内存爆炸拖垮集群 |
| 禁止多表 JOIN | 本实验不涉及任何 JOIN 操作 | JOIN 在 2GB 节点上几乎必然 OOM |
| 监控 YARN WebUI | 查询期间观察 8088 端口容器状态 | 及时发现 Container killed 并调整参数 |
8. 问题与排查记录
| 现象 | 原因 | 解决 | 备注 |
|---|---|---|---|
| 清华镜像站下载 404 | 路径不存在或版本已迁移 | 改用华为云镜像站 mirrors.huaweicloud.com |
|
hive-site.xml: No such file or directory |
未创建配置文件 | 使用 cat > 直接写入完整配置 |
|
NUCLEUS_ASCII already exists |
首次未指定配置文件,Derby 用相对路径创建了不完整库;后续配置指定绝对路径后,残留文件与新初始化冲突 | 删除所有路径下的 metastore_db 和 derby.log,重新 schematool -initSchema |
核心教训:Derby 路径必须使用绝对路径,且初始化前确保无残留 |
Hive-on-MR is deprecated 警告 |
Hive 3.x 推荐 Tez/Spark,但 MR 仍可用 | 忽略,MR 是 2GB 集群唯一可行引擎 | |
Cannot run job locally 信息 |
输入 1.29 GB 超过本地模式阈值 128 MB,自动切换 YARN | 正常行为,无需处理 | 验证了阈值设置的合理性 |
Invalid function approx_count_distinct |
Hive 3.1.3 默认不支持此 UDF | 改用 COUNT(DISTINCT),增加 Reducer 保证安全 |
|
rm -rf 在 Hive CLI 内误执行 |
在 hive> 提示符下执行了 shell 命令 |
先 quit; 退出 CLI,再执行 shell 命令 |
CLI 与 shell 环境隔离 |
连续无降水天数返回 NULL |
所选站点无 precip = 0 的记录 |
检查数据后发现全数据集均无零值,调整阈值到 < 0.5,重新分析 |
体现"以数据为准,迭代调整分析策略"的原则 |
| 窗口函数触发 3 个 Stage 且 YARN 模式 | 即使单站点数据极小,Hive 仍可能因 Reducer 数 >1 而提交 YARN | 正常执行,耗时略长但内存安全 | 后续可考虑临时设置 mapreduce.job.reduces=1 并强制本地模式 |
9. A3阶段总结
- Hive 环境搭建与数据加载:通过华为云镜像站下载 Hive 3.1.3,使用 Derby 内嵌模式存储元数据,经过路径配置错误和元数据库残留问题排查后成功初始化,创建外部表指向 A2-2 清洗数据,总行数 9,218,700 与上一阶段一致。
- 内存估算模型有效 :所有 MR 作业均稳定运行,无任何容器被杀或 OOM 发生。Map 阶段内存消耗约 100 MB,Reducer 分组状态仅 4~20 MB,远低于 256 MB 堆上限。物化表
station_counts(2.28 MB)和monthly_summary(5.5 KB)极大加速了后续查询。 - 分析结果摘要:数据覆盖 102,430 个站点、105 个月(2012-04 ~ 2020-12),每个站点均匀观测 90 次。温度范围 -0.30℃ ~ 24.93℃,降水量最低月份在冬末春初。最干旱站点的最小降水量为 0.1 mm,平均 1.28 mm;连续低降水天数(<0.5mm)最长为 1 天,表明数据集中无显著连续干旱事件。
- 低资源优化原则有效:增加 Reducer 数、积极使用物化表、小范围测试、避免 JOIN 等原则在实践中得到验证。窗口函数即使在极小数据集上也可能触发 YARN 模式,但内存安全。
- 数据驱动的迭代分析思路贯穿始终 :从发现数据中无零降水量记录,到调整干旱阈值为
< 0.5,最终得出符合数据实际的结论。这种"以数据为准,持续调整分析策略"的理念是数据工程的核心能力,也在本实验中得到了充分体现。 - 技术门槛的跨越:在短短几天的实践周内,从手写 MapReduce 代码到使用 SQL 进行大数据分析,完成了从"汇编"到"高级语言"的跨越;Derby 内嵌模式的轻量化和 Hive 的 SQL 抽象,使得在 2GB 内存集群上进行海量数据分析成为可能。