小肥柴的Hadoop之旅 快速实验篇(A4)Hive 数据仓库进阶:全站点干旱事件识别与多维统计分析
-
- 目录
- 前言
- [0. 背景知识:从单站点分析到全站点画像](#0. 背景知识:从单站点分析到全站点画像)
- [1. 任务概述与目标](#1. 任务概述与目标)
- [2. 磁盘危机:从 Spill 失败到 LVM 扩容(工程实战)](#2. 磁盘危机:从 Spill 失败到 LVM 扩容(工程实战))
-
- [2.1 资源基础:可供 Hive 消耗的内存上限](#2.1 资源基础:可供 Hive 消耗的内存上限)
- [2.1 故障现象](#2.1 故障现象)
- [2.2 初步排查:HDFS 报告显示空间充裕](#2.2 初步排查:HDFS 报告显示空间充裕)
- [2.3 深入定位:根分区只有 14 GB](#2.3 深入定位:根分区只有 14 GB)
- [2.4 无效的清理尝试](#2.4 无效的清理尝试)
- [2.5 根本原因定位:LVM 闲置了 14 GB](#2.5 根本原因定位:LVM 闲置了 14 GB)
- [2.6 根本解决:LVM 在线扩容](#2.6 根本解决:LVM 在线扩容)
- [.7 经验教训](#.7 经验教训)
- [3. 环境与资源背景](#3. 环境与资源背景)
- [4. 分析查询与结果](#4. 分析查询与结果)
-
- [4.1 会话参数设置](#4.1 会话参数设置)
- [4.2 步骤一:站点干旱日统计](#4.2 步骤一:站点干旱日统计)
- [4.3 步骤二:干旱标记表(全量)](#4.3 步骤二:干旱标记表(全量))
- [4.4 步骤三:干旱事件识别(gaps-and-islands)](#4.4 步骤三:干旱事件识别(gaps-and-islands))
-
- [执行机制深度分析:为什么两个 Stage 都有 Map 和 Reduce?](#执行机制深度分析:为什么两个 Stage 都有 Map 和 Reduce?)
- [慢启动机制:Map 未到 100%,Reduce 为何已有进度?](#慢启动机制:Map 未到 100%,Reduce 为何已有进度?)
- [4.5 步骤四:事件级统计](#4.5 步骤四:事件级统计)
- [4.6 步骤五:全站点干旱画像(稀疏补零)](#4.6 步骤五:全站点干旱画像(稀疏补零))
- [4.7 步骤六:排名与分布分析](#4.7 步骤六:排名与分布分析)
-
- [step 1:总体概览](#step 1:总体概览)
- [step 2:最长连续干旱天数 Top 20](#step 2:最长连续干旱天数 Top 20)
- [step 3:干旱事件总数排名 Top 20](#step 3:干旱事件总数排名 Top 20)
- [step 4:最长连续干旱天数分布](#step 4:最长连续干旱天数分布)
- [5. A4 阶段全部物化表](#5. A4 阶段全部物化表)
- [6. 问题与排查记录](#6. 问题与排查记录)
- [7. 阶段总结](#7. 阶段总结)
目录
前言
本文是"农业气象干旱分析"项目的第四阶段,记录在 Hive 中基于 A3 构建的数仓基础,对全部 102,430 个站点的 9,218,700 条气象观测数据进行干旱事件识别与多维统计分析的完整过程。
- 核心内容包括:
(1)使用窗口函数 + gaps-and-islands 算法识别连续干旱事件;
(2)在磁盘空间濒临耗尽的极端工况下,通过 LVM 逻辑卷扩容从根本上解决 Map 端 Spill 失败问题;
(3)通过左连接 + COALESCE 补零实现稀疏数据的全站点干旱画像;
(4)从作业日志中反推 Hadoop 慢启动机制的实际行为,揭示了 Shuffle 阶段 Copy 子阶段可与 Map 并行执行这一教材未覆盖的工程细节。 - 实验最终形成了一张包含全部站点干旱指标的全集画像表,并得出结论:该数据集覆盖的时空范围内,干旱是稀有且短促的现象,仅 3.75% 的站点出现过连续低降水事件。
0. 背景知识:从单站点分析到全站点画像
A3 阶段我们完成了数据概览、站点维度、时间维度分析,并选取了一个最干旱的站点进行单站点的连续低降水天数计算,结果为最长 1 天。
A3 留下了一个待解决问题:这个结论是否具有普遍性? 102,430 个站点中,有多少站点存在干旱事件?最严重的干旱持续了多久?干旱事件在空间和时间上是如何分布的?
A4 的任务就是将 A3 的单站点分析方法,系统化地推广到全部 102,430 个站点 ,形成完整的站点级干旱画像。在技术实现上,这需要解决一个经典的数据处理问题------如何从连续的时间序列中识别出每一次独立的"事件"(即连续干旱日构成的干旱事件)。SQL 领域将这类问题称为 gaps-and-islands(间隙与岛屿) 问题:将符合条件(降水 < 阈值)的连续日期合并为一个"岛屿"(干旱事件),并在不同站点间正确地划分"间隙"(非干旱期)。
1. 任务概述与目标
| 项目 | 说明 |
|---|---|
| 定位 | 承接 A3 的数仓基础与单站点分析思路,使用 Hive 窗口函数对全量站点进行干旱事件识别与多维统计 |
| 目标 | 1. 以 precip < 0.5mm 为干旱阈值,标记全部 921 万行数据 2. 使用 gaps-and-islands 算法识别每次独立的干旱事件(连续干旱日分组) 3. 计算站点级干旱指标:事件数、最长连续天数、平均持续天数 4. 通过左连接 + COALESCE 补零实现全站点画像 5. 输出干旱严重程度排名与分布统计 |
| 输入 | A3 物化表 drought_cleaned(9,218,700 行,22 列) |
| 输出 | 全站点干旱画像表 station_drought_profile(102,430 行)+ 排名与分布分析结论 |
2. 磁盘危机:从 Spill 失败到 LVM 扩容(工程实战)
- 【叠甲1】此处记录了 A4 实验中最严重的一次基础设施故障------Map 端 Spill 失败,以及从排查到根治的完整过程。这是大数据平台运维中极具代表性的场景:磁盘空间看似充裕,实则被分区规划不当所困。
- 【叠甲2】如果你觉得这些内容不值得你学习,可以直接跳到扩容操作环节,在对应虚拟机(VM)上执行命令,确保生效即可。
2.1 资源基础:可供 Hive 消耗的内存上限
2.1 故障现象
在后文步骤三中,我们执行了一条 CTAS(CREATE TABLE AS SELECT,即"建表并同时从查询结果插入数据") 语句,试图创建干旱事件表 station_dry_events:
sql
CREATE TABLE station_dry_events AS
WITH grouped AS (...)
SELECT ...
这条语句需要 Hive 启动 MapReduce 作业,对 dry_flag 表(约 293 MB)按站点进行窗口函数计算。作业提交后,反复崩溃,关键错误信息如下:
bash
Caused by: org.apache.hadoop.util.DiskChecker$DiskErrorException:
Could not find any valid local directory for
attempt_1780233593964_0008_m_000000_1003_spill_0.out
with requested size 39461383 as the max capacity in any directory is 9408512
这段日志的含义是:Map 任务在处理数据时,需要将内存中预聚合的临时数据溢写(Spill)到本地磁盘,申请写入约 39 MB 的文件,但 YARN 管理的本地临时目录中,最大的可用空间只有 约 9 MB,因此溢写失败,整个作业崩溃。
对于刚接触大数据的同学来说,CTAS 是 Hive 中非常常用的操作,CTAS一条语句可同时完成"建表"和"插入数据",避免了先 CREATE TABLE 再 INSERT INTO 的繁琐步骤。但在执行 CTAS 时,底层仍然要启动 MapReduce 作业,因此也会遭遇所有 MR 作业可能遇到的资源问题。这次 Spill 失败就是典型的"磁盘空间看似充足,实则被非数据文件占满"的运维故障。
2.2 初步排查:HDFS 报告显示空间充裕
bash
$ hdfs dfsadmin -report
| 节点 | DFS 已用 | DFS 剩余 |
|---|---|---|
| worker1 | 3.98 GB | 305 MB |
| worker2 | 3.98 GB | 321 MB |
| worker3 | 3.98 GB | 934 MB |
表面看每个节点有 300-900 MB 可用,且 NodeManager 状态均为 RUNNING。但为何 39 MB 的 Spill 写不进去?
2.3 深入定位:根分区只有 14 GB
bash
$ df -h /
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/ubuntu--vg-ubuntu--lv 14G 13G 304M 98% /
- 真相 :VM 分配了 30 GB 磁盘,但 Ubuntu 安装时 LVM 只给根分区划了 14 GB。Hadoop 软件(/usr/local/hadoop,约 5.4 GB)、Hive(/usr/local/hive,约 400 MB)、Java、系统文件(/usr,约 8.9 GB)、HDFS 数据(4 GB)全部挤在这 14 GB 里,剩余空间仅 304 MB。
这里有一个容易混淆的关键认知:hdfs dfsadmin -report 显示的"DFS 剩余"与 df -h 显示的"磁盘可用"是两个不同的概念:
| 命令 / 指标 | 含义 | 我们的数值 |
|---|---|---|
hdfs dfsadmin -report |
HDFS 数据目录内还可存储 HDFS 块的空间 | 305 MB |
df -h / |
根分区剩余的全部可用空间(含非 HDFS 文件) | 304 MB |
两者在同一个根分区上争夺空间。HDFS 报告的"剩余 305 MB"指的是 HDFS 数据块还能用多少,而 Map 端 Spill 写到的是 YARN 本地目录 (yarn.nodemanager.local-dirs,本次部署中未显式配置,使用默认路径,在根分区上)。39 MB 的 Spill 请求失败,因为根分区剩余空间连这个临时文件都放不下。
2.4 无效的清理尝试
依次尝试了以下清理手段,但收效甚微:
| 操作 | 释放空间 | 原因 |
|---|---|---|
sudo apt-get clean |
~0 MB | APT 缓存本就不大 |
sudo journalctl --vacuum-size=50M |
160 MB | 系统日志有一定积累 |
sudo snap remove lxd |
609 MB(未立即释放) | LXD 移除后有快照残留 |
| 清理 /tmp/* | 0 MB | /tmp 本就是空的 |
清理后根分区可用空间从 304 MB 微增到 354 MB,仅释放了 50 MB------**因为 14 GB 的天花板摆在那儿,清理只是杯水车薪。**所以还是要从根上解决问题,而不是选择规避问题,毕竟咱们在A0实验中是踏踏实实给VM分配了30GB的磁盘空间呢!怎么就没了?想想这事就邪乎。
2.5 根本原因定位:LVM 闲置了 14 GB
bash
$ sudo pvdisplay | grep -E "PV Name|PV Size"
PV Name /dev/sda3
PV Size <28.00 GiB
$ sudo vgdisplay | grep -E "VG Name|Free"
VG Name ubuntu-vg
Free PE / Size 3584 / 14.00 GiB
物理卷总大小 28 GB,卷组空闲 14 GB------一半的磁盘空间在 LVM 里闲置着。 根分区只用了其中的 14 GB。
LVM(Logical Volume Manager,逻辑卷管理器)是现代 Linux 发行版默认使用的磁盘管理方案。它在物理磁盘之上抽象出"物理卷→卷组→逻辑卷"三层结构,允许管理员动态调整分区大小。本次 Ubuntu 安装时,安装程序创建了一个 28 GB 的卷组,却只为根分区(逻辑卷)分配了 14 GB,剩余 14 GB 留在了卷组里从未使用;还是工程经验不足呢!
2.6 根本解决:LVM 在线扩容
不需要重启、不影响运行中的服务,两条命令将根分区从 14 GB 扩展到 28 GB:
bash
# 扩展逻辑卷,吃掉全部空闲空间
sudo lvextend -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv
# 在线扩展 ext4 文件系统
sudo resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv
扩容结果:
| 节点 | 扩容前 | 扩容后 | 可用空间变化 |
|---|---|---|---|
| worker1 | 14 GB | 28 GB | 304 MB → 14 GB |
| worker2 | 14 GB | 28 GB | 321 MB → 14 GB |
| worker3 | 14 GB | 28 GB | 934 MB → 15 GB |
此后所有 MR 作业稳定运行,不再出现 Spill 失败。
【追问】master和standby也需要检查吗?
.7 经验教训
hdfs dfsadmin -report只反映 HDFS 数据目录的空间,不代表整块磁盘 。YARN 本地目录、系统日志、软件包等"非 HDFS 文件"(Non DFS Used)同样消耗磁盘。运维中应同时使用df -h检查分区级别的实际可用空间。- 虚拟机模板的 LVM 配置可能只用了部分磁盘 。
pvdisplay/vgdisplay应该在集群部署时就检查,确认逻辑卷已分配全部卷组空间,避免后续业务中断。本次排查路径------从hdfs dfsadmin -report→df -h→du -sh /*→pvdisplay→vgdisplay------层层递进,是定位磁盘类问题的通用方法。 - 清理只是止痛,扩容才是根治 。当分区规划本身有问题时,删文件解决不了天花板问题。LVM 在线扩容(
lvextend+resize2fs)是 Linux 运维的基本功,整个过程无需重启服务,不影响正在运行的 DataNode 和 NodeManager。
3. 环境与资源背景
| 项目 | 配置 |
|---|---|
| 节点数 | 3(master + worker1/2/3) |
| 每节点内存 | 2 GB RAM |
| 每节点磁盘 | 30 GB → 根分区扩容后 28 GB 可用 |
| YARN 可用内存 | 每节点 1024 MB,单容器最大 384 MB |
| Hive 版本 | 3.1.3 |
| 元数据库 | Derby(内嵌) |
| 执行引擎 | MapReduce |
| 数据源 | drought_cleaned(9,218,700 行) |
| 干旱阈值 | precip < 0.5 mm |
4. 分析查询与结果
4.1 会话参数设置
每次进入 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;
4.2 步骤一:站点干旱日统计
sql
CREATE TABLE station_dry_days AS
SELECT
station_id,
COUNT(*) AS dry_day_count,
ROUND(COUNT(*) / 90.0 * 100, 2) AS dry_day_pct
FROM drought_cleaned
WHERE precip < 0.5
GROUP BY station_id;
| 指标 | 数值 |
|---|---|
| HDFS 写入 | 105,987 字节(约 103 KB) |
| 耗时 | 55.6 秒 |
写入大小异常分析 :预期约 2-3 MB(102,430 站点 × 30 字节/行),实际仅 103 KB。原因是绝大多数站点在 WHERE precip < 0.5 过滤后无数据,不出现在 GROUP BY 结果中。后续验证证实了这一点:
sql
SELECT
COUNT(*) AS stations_with_drought,
MIN(dry_day_count) AS min_dry_days,
MAX(dry_day_count) AS max_dry_days,
ROUND(AVG(dry_day_count),1) AS avg_dry_days
FROM station_dry_days;
| 指标 | 数值 |
|---|---|
| 有干旱日的站点数 | 3,845(仅占总站点 3.75%) |
| 最少干旱天数 | 1 |
| 最多干旱天数 | 27 |
| 平均干旱天数 | 3.0 |
发现 :96.25% 的站点在观测期内从未出现过 precip < 0.5 的日期。即使是有干旱日的 3,845 个站点,平均也仅 3 天。干旱在数据集中是稀有现象。
4.3 步骤二:干旱标记表(全量)
在进入 gaps-and-islands 逻辑之前,先物化全量干旱标记表,避免后续 CTE(Common Table Expression,公共表表达式,即 WITH ... AS 定义的临时查询块)被 Hive 内联展开(Inlining),导致 drought_cleaned 被重复扫描两次(一次 ROW_NUMBER(),一次 SUM() OVER)。先物化为独立的物理表,后续窗口函数直接从物化表读取,避免重复扫描全表:
sql
CREATE TABLE dry_flag AS
SELECT
station_id,
record_date,
CASE WHEN precip < 0.5 THEN 1 ELSE 0 END AS is_dry
FROM drought_cleaned;
| 指标 | 数值 |
|---|---|
| 作业类型 | Map-only(无 Reduce) |
| HDFS 写入 | 307,735,345 字节(约 293 MB) |
| 行数 | 9,218,700(与原表一致) |
| 耗时 | 34.8 秒 |
4.4 步骤三:干旱事件识别(gaps-and-islands)
这是 A4 最核心的 SQL,使用窗口函数为每个站点的连续干旱日生成分组标识:
sql
CREATE TABLE station_dry_events AS
WITH grouped AS (
SELECT
station_id,
record_date,
is_dry,
ROW_NUMBER() OVER (PARTITION BY station_id ORDER BY record_date) AS rn,
SUM(CASE WHEN is_dry = 0 THEN 1 ELSE 0 END)
OVER (PARTITION BY station_id ORDER BY record_date) AS grp
FROM dry_flag
)
SELECT
station_id,
grp,
COUNT(*) AS event_days,
MIN(record_date) AS event_start,
MAX(record_date) AS event_end
FROM grouped
WHERE is_dry = 1
GROUP BY station_id, grp;
| 指标 | 数值 |
|---|---|
| 作业数 | 2 个 Stage |
| Stage-1 | 窗口函数计算,Map: 2, Reduce: 3 |
| Stage-2 | GROUP BY 聚合,Map: 1, Reduce: 3 |
| 总耗时 | 163.3 秒 |
| HDFS 写入 | 182,648 字节(约 178 KB) |
gaps-and-islands 算法逻辑解释:
ROW_NUMBER() OVER (PARTITION BY station_id ORDER BY record_date):为每个站点的每行分配一个连续的行号rn,日期越早行号越小。SUM(CASE WHEN is_dry = 0 THEN 1 ELSE 0 END) OVER (PARTITION BY station_id ORDER BY record_date):累加非干旱日的数量,记为grp。关键在于------连续干旱日之间没有非干旱日 ,所以它们共享同一个累积值;一旦遇到一个非干旱日,累积值增加 1,后续干旱日的grp就与上一段干旱不同了。- 最终每个站点内,相同的
grp值对应一次独立的干旱事件(一个"岛屿"),grp的跳变处就是事件之间的"间隙"。
执行机制深度分析:为什么两个 Stage 都有 Map 和 Reduce?
Stage-1 执行窗口函数(ROW_NUMBER() OVER 和 SUM() OVER),是一个完整的 MapReduce:
- Map 端 :读取
dry_flag的 293 MB 数据,按station_id做 Partition,按record_date排序 - Reduce 端:每个分区内逐行计算行号和累加值
Stage-2 执行外层 GROUP BY station_id, grp,是另一个完整 MapReduce:
- Map 端 :读取 Stage-1 输出,以
(station_id, grp)为 Key - Reduce 端:对每个 Key 做 COUNT、MIN、MAX 聚合
不能合并的原因 :窗口函数和 GROUP BY 是两种不同的 Reduce 操作------前者需要按 station_id 分区排序,后者需要按 (station_id, grp) 分组聚合。Hive 优化器无法将它们合并为一个 Reduce 任务,必须串行执行两个 MR 作业。
慢启动机制:Map 未到 100%,Reduce 为何已有进度?
日志中观察到一个与教材理论相悖的现象:
Stage-1 map = 84%, reduce = 6%
Stage-1 map = 84%, reduce = 11%
Stage-1 map = 94%, reduce = 17%
教材说法:所有 Map 完成后,Reduce 才开始。
实际行为:Map 还差 16%,Reduce 已经推进到 17%。
这不是错误,而是 Hadoop 的 慢启动(Slow Start)机制 ,由参数 mapreduce.job.reduce.slowstart.completedmaps 控制,默认值 0.05------当 5% 的 Map 任务完成后,Reduce 即开始启动。本作业有 2 个 Map 任务,5% × 2 = 0.1,意味着第一个 Map 一完成(即 50% 进度),Reduce 就开始预热了。
慢启动的合理性在于 Reduce 端的三个阶段:
| 阶段 | 做什么 | 能否提前? |
|---|---|---|
| Copy(拉取) | 从各 Map 节点拉取输出文件 | ✅ 能------某个 Map 一完成,其输出即可被拉走 |
| Sort(排序) | 合并排序所有拉取的数据 | ❌ 不能------必须等全部到齐 |
| Reduce(聚合) | 逐条计算 | ❌ 不能------排序没完成没法算 |
日志中 reduce 进度从 6% 缓慢爬升到 17%,走的是 Copy 阶段------在拉取已完成 Map 的数据,与剩余 Map 完全并行。Copy 只是网络搬运,不涉及计算逻辑,因此不依赖全量数据。Reduce 进度的前三分之一(0%-33%)通常对应 Copy 阶段,33%-66% 对应 Sort 阶段,66%-100% 对应真正的 Reduce 计算。
教材为什么简化? 教材讲的是 Reduce 的"计算阶段"(Sort + Reduce)必须等所有 Map 完成,但通常不细分 Shuffle 内部的三个子阶段。这个细节揭示了 逻辑阶段 ≠ 物理执行 的工程真相。
慢启动机制是 Hadoop 从 0.x 时代就具备的生产级优化,并非 3.0 才引入。
4.5 步骤四:事件级统计
sql
CREATE TABLE station_dry_summary AS
SELECT
station_id,
COUNT(*) AS dry_event_count,
MAX(event_days) AS max_dry_days,
ROUND(AVG(event_days), 1) AS avg_dry_days,
SUM(event_days) AS total_dry_days
FROM station_dry_events
GROUP BY station_id;
| 指标 | 数值 |
|---|---|
| 耗时 | 30.8 秒 |
| HDFS 写入 | 117,696 字节(约 115 KB) |
4.6 步骤五:全站点干旱画像(稀疏补零)
station_dry_summary 只包含有干旱事件的 ~3,845 个站点。为形成完整的全站点画像,需将无干旱站点补零。在数据分析领域,这一操作有明确术语和理论依据:
术语:稀疏数据补全(Sparse Data Completion)/ 零填充(Zero-Filling),属于缺失值填充(Missing Value Imputation)的一种------用领域合理的常数(0 表示"没有干旱事件")填补外连接产生的 NULL。
理论依据:
- 语义正确性 :
LEFT JOIN产生 NULL 的原因不是"数据未知",而是"该站点确定没有发生过干旱事件"。干旱事件次数为 0 是确定性事实,补零是还原真相,而非捏造数据。这区别于传感器故障等造成的"真正未知"的缺失值。 - 关系代数语义 :在标准 SQL 中,
A LEFT JOIN B中未匹配行的 B 列用 NULL 填充,这是 SQL 规范定义的占位行为。COALESCE(col, 0)是在此基础上做语义修正------将 SQL 默认占位符替换为符合业务含义的真实值。 - 数据集完整性:补零后分布一目了然(0 占 96.25%,1 占 1.61%...),是完整的数据描述。不补零则只能描述 3.75% 的站点,无法回答"全量站点中干旱有多普遍"这一问题。
- 防止 NULL 传播 :SQL 的 NULL 具有传染性(
NULL + 1 = NULL,AVG(col_with_null)会跳过 NULL 行),补零消除了后续计算的隐患,使station_drought_profile可以被任意下游查询安全使用。
sql
CREATE TABLE station_drought_profile AS
SELECT
a.station_id,
COALESCE(s.dry_event_count, 0) AS dry_event_count,
COALESCE(s.max_dry_days, 0) AS max_dry_days,
COALESCE(s.avg_dry_days, 0.0) AS avg_dry_days,
COALESCE(s.total_dry_days, 0) AS total_dry_days
FROM (
SELECT DISTINCT station_id FROM dry_flag
) a
LEFT JOIN station_dry_summary s
ON a.station_id = s.station_id;
| 指标 | 数值 |
|---|---|
| 作业数 | 2 个 Stage |
| Stage-1 | SELECT DISTINCT,Map: 2, Reduce: 3 |
| Stage-2 | LEFT JOIN + COALESCE,Map: 2, Reduce: 3 |
| 总耗时 | 78.0 秒 |
| HDFS 写入 | 3,112,886 字节(约 3.0 MB) |
| 行数 | 102,430(全量站点) |
4.7 步骤六:排名与分布分析
step 1:总体概览
sql
SELECT
COUNT(*) AS total_stations,
SUM(CASE WHEN dry_event_count > 0 THEN 1 ELSE 0 END) AS stations_with_drought,
ROUND(SUM(CASE WHEN dry_event_count > 0 THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS drought_pct,
MAX(max_dry_days) AS overall_max_dry_days,
ROUND(AVG(total_dry_days), 1) AS overall_avg_dry_days
FROM station_drought_profile;
| 指标 | 数值 |
|---|---|
| 全站点数 | 102,430 |
| 有干旱事件站点数 | 3,845(3.75%) |
| 无干旱事件站点数 | 98,585(96.25%) |
| 全站点最长连续干旱天数 | 27 天 |
| 全站点平均干旱总天数 | 0.1 天 |
step 2:最长连续干旱天数 Top 20
sql
SELECT station_id, dry_event_count, max_dry_days, avg_dry_days
FROM station_drought_profile
ORDER BY max_dry_days DESC
LIMIT 20;
| 排名 | 站点 ID | 干旱事件数 | 最长连续天数 | 平均持续天数 |
|---|---|---|---|---|
| 1 | 6850186147108203597 | 1 | 27 | 27.0 |
| 2 | 8684056272254568774 | 1 | 25 | 25.0 |
| 3 | -8915429204746688844 | 1 | 24 | 24.0 |
| 4-5 | (2 个站点) | 1 | 23 | 23.0 |
| 6-7 | (2 个站点) | 1 | 22 | 22.0 |
| 8-9 | (2 个站点) | 1 | 21 | 21.0 |
| 10-12 | (3 个站点) | 1 | 20 | 20.0 |
| ... | ... | ... | ... | ... |
特征 :所有 Top 20 站点的干旱事件数均为 1。它们只经历了一次干旱事件,但此次事件持续了 18-27 天,是真正的"干旱站点"。
step 3:干旱事件总数排名 Top 20
sql
SELECT station_id, dry_event_count, max_dry_days, avg_dry_days
FROM station_drought_profile
ORDER BY dry_event_count DESC
LIMIT 20;
所有 Top 20 站点的 dry_event_count 均为 1 。这表明:整个数据集中不存在经历多次独立干旱事件的站点。干旱一旦发生,就是单次、连续的。
注 :Hive 在分布式排序中,
ORDER BY dry_event_count DESC LIMIT 20因大量并列第一(均为 1 次),返回的站点 ID 分布在数据分片中带有随机性。这不影响结论的统计有效性,但可在后续分析中加ORDER BY dry_event_count DESC, station_id保证确定性排序。
step 4:最长连续干旱天数分布
sql
SELECT
max_dry_days,
COUNT(*) AS station_cnt,
ROUND(COUNT(*) / 102430.0 * 100, 2) AS pct
FROM station_drought_profile
GROUP BY max_dry_days
ORDER BY max_dry_days DESC;
| 最长连续天数 | 站点数 | 占比 |
|---|---|---|
| 0 | 98,585 | 96.25% |
| 1 | 1,649 | 1.61% |
| 2 | 751 | 0.73% |
| 3 | 398 | 0.39% |
| 4 | 268 | 0.26% |
| 5 | 222 | 0.22% |
| 6 | 148 | 0.14% |
| 7 | 103 | 0.10% |
| 8 | 61 | 0.06% |
| 9 | 40 | 0.04% |
| 10 | 45 | 0.04% |
| 11 | 31 | 0.03% |
| 12 | 28 | 0.03% |
| 13 | 25 | 0.02% |
| 14 | 17 | 0.02% |
| 15 | 13 | 0.01% |
| 16 | 10 | 0.01% |
| 17 | 13 | 0.01% |
| 18 | 9 | 0.01% |
| 19 | 2 | 0.00% |
| 20 | 3 | 0.00% |
| 21 | 2 | 0.00% |
| 22 | 2 | 0.00% |
| 23 | 2 | 0.00% |
| 24 | 1 | 0.00% |
| 25 | 1 | 0.00% |
| 27 | 1 | 0.00% |
分布特征:严重右偏/长尾分布。绝大多数站点集中在 0 天,随着天数增加,站点数急剧下降。只有 1 个站点达到 27 天连续低降水。
【追问】咱们做了那么久数据分析,你是否想过这些分析结果其实是有可视化价值的呢?如果你认为有,可以做出漂亮、表达力强的可视化效果吗?若你认为不必做可视化,也请给出令人信服的说法。
5. A4 阶段全部物化表
| 表名 | 行数 | HDFS 大小 | 用途 |
|---|---|---|---|
station_dry_days |
3,845 | ~103 KB | 各站点干旱日总数 |
dry_flag |
9,218,700 | ~293 MB | 全量干旱标记(is_dry 列) |
station_dry_events |
~3,845+ | ~178 KB | 每次干旱事件的起止日期与持续天数 |
station_dry_summary |
3,845 | ~115 KB | 站点级干旱事件统计 |
station_drought_profile |
102,430 | ~3.0 MB | 全站点干旱画像(补零后,最终表) |
6. 问题与排查记录
| 现象 | 原因 | 解决 | 备注 |
|---|---|---|---|
CTAS 报 Table already exists |
Hive 的 CREATE TABLE ... AS 不覆盖已有表 |
DROP TABLE IF EXISTS ... 后重跑 |
保护机制,防止误覆盖 |
Map 端 Spill 失败:Could not find any valid local directory |
根分区仅 14 GB,Hadoop + 系统占满后剩 304 MB,39 MB 的 Spill 写不进去 | LVM 在线扩容:lvextend -l +100%FREE + resize2fs,根分区 14→28 GB |
本次最严重的故障,根本原因是 VM 模板 LVM 配置只用了一半磁盘 |
| 磁盘清理无效(清理后仅释放 50 MB) | 14 GB 天花板摆在那儿,清理治标不治本 | 扩容根治 | 教训:部署时就该检查 pvdisplay / vgdisplay |
扩容后 df -h 未立即显示空间释放 |
resize2fs 执行后才生效 |
两个命令都已正确执行,最终 28G 确认有效 | |
dry_flag CTAS 写入 293 MB 但源表 1.29 GB |
只选了 3 列(station_id, record_date, is_dry),而源表 22 列 | 正常,列裁剪减少数据量 | Map-only 作业,高效 |
station_dry_events CTAS 触发 2 个 Stage |
窗口函数(Stage-1)+ GROUP BY(Stage-2)无法合并为一个 Reduce | 正常行为,Hive 优化器不做此类合并 | 总耗时 163 秒,内存安全 |
| Map 未 100% 时 Reduce 进度已到 17% | Hadoop 慢启动机制(slowstart.completedmaps=0.05),Copy 阶段可提前 |
正常行为 | 教材理论简化所致,详见 4.4 节分析 |
ORDER BY dry_event_count DESC LIMIT 20 返回的站点 ID 带有随机性 |
所有站点干旱事件数均为 1(并列第一),分布式排序下 LIMIT 返回不同分片中的行 | 正常行为,不影响结论 | 可加 station_id 做二级排序保证确定性 |
7. 阶段总结
-
磁盘危机与根治 :本次实验最大的挑战来自基础设施------根分区仅 14 GB 的空间被 Hadoop 全家桶、Hive、Java、系统文件和 HDFS 数据塞满,Map 端 Spill 39 MB 临时文件失败。经过从
hdfs dfsadmin -report→df -h→du -sh /*→pvdisplay→vgdisplay的逐层排查,定位到 LVM 闲置 14 GB 的根本原因,通过在线扩容将根分区从 14 GB 扩展到 28 GB,彻底解决磁盘瓶颈。这一过程真实还原了生产环境中"报告显示有空间,实际写不进去"的排查路径,也印证了hdfs dfsadmin -report和df -h两个命令关注不同层面的磁盘使用情况的原理性区别。 -
干旱事件识别 :使用 gaps-and-islands 算法(
ROW_NUMBER+SUM(CASE ...) OVER差值分组)成功识别出全部站点的干旱事件。SQL 翻译为两个 MR Stage,窗口函数按station_id分区(每个分区仅 90 行),窗口缓冲极小,内存始终安全。先物化dry_flag避免了 CTE 内联导致的全表重复扫描,是低资源环境下的重要优化技巧。 -
慢启动机制发现:从作业日志中观察到 Map 进度 84% 时 Reduce 已有 6%-17% 进度的现象,追溯出 Hadoop 慢启动机制的工程实现------Reduce 的 Copy 阶段可在 Map 未全部完成时提前拉取数据,与教材"Map 完成后 Reduce 才开始"的简化描述形成对照。这一发现体现了对 MapReduce 执行模型三阶段(Copy-Sort-Reduce)的深入理解。
-
稀疏数据补零:针对 96.25% 站点无干旱事件的稀疏聚合结果,采用全站点左连接 + COALESCE 补零策略,将稀疏表补全为稠密的全站点干旱画像表。补零的理论依据在于"无干旱事件"是确定性事实而非数据缺失,0 是其统计量的真实值。这一操作在数据分析领域对应"稀疏数据补全"和"缺失值填充(Zero-Filling)"。
-
分析结论:数据覆盖的 102,430 个站点、105 个月中,96.25% 的站点从未出现过连续低降水(< 0.5mm)。即使是 3,845 个有干旱的站点,平均干旱总天数仅 3 天。Top 20 最干旱站点均只经历 1 次干旱事件,最长持续 27 天。干旱在该数据集中是稀有、短促的现象。这一结论为后续 A5-A7 的干旱等级划分、极端事件识别提供了明确的基线------分析重心应从"寻找长期连续干旱"转向"识别间歇性低降水异常"。
-
物化表体系 :A4 新增 5 张物化表,其中
station_drought_profile(102,430 行全站点画像)是核心输出,可直接供后续实验(A5-A15)复用,避免重复全表扫描。