又到了一年一度的毕设季,每年这个时候后台私信最多的问题就是:大数据方向的毕业设计到底怎么选技术栈,才能既扛得住答辩提问,又不会把自己折腾到崩溃。这几年最流行的一个配方,就是标题里这串组合——拿 Hadoop 做存储、Hive 做数仓、Spark 做清洗和特征工程、LLM 大模型做智能问答和推荐解释,最后用 Django 把整个系统串成 Web 应用,落到智慧农业场景里做农产品价格预测、销量预测和推荐系统。
这套方案之所以流行,是因为它覆盖面广:有大数据生态、有机器学习算法、有前沿的大模型应用、还有完整的 Web 工程实践。对一个本科毕业设计来说,技术广度够了,展示效果也够炫。但问题恰恰出在“组合”上——技术栈越多,版本冲突、环境问题和数据流断点就越多。今天我就完整复盘一下我当时从零搭这套系统的全过程,把设计思路、关键代码、填过的坑,一次性讲清楚。
1. 系统整体设计:五层架构怎么串成一条线
很多同学拿到这种题目第一反应是“这不就是把一堆技术名堆一起吗”,其实不然。一套合格的毕设系统,最关键的不是某个技术多牛,而是数据在系统里怎么流动、每一层到底解决什么问题。我在动手之前,先画了一条清晰的数据链路:数据采集 → 存储与数仓 → 计算与特征工程 → 算法与模型 → 应用展示。每一层只干一件事,层与层之间通过明确的接口衔接。
1.1 从一条真实的数据流说起
以农产品价格预测为例,我采集的数据源包括:全国主要农产品批发市场的每日价格与成交量、天气数据(气温、降雨、风力)、节假日数据(例如春节前蔬菜价格普遍上涨)、以及历史同期价格数据。原始数据先以文件形式落地到 HDFS,再用 Hive 映射成结构化表,之后由 Spark 做清洗、去重、缺失值填充和特征拼接,生成一份可以直接喂给模型的宽表。模型跑完后,预测结果写得 MySQL,Django 负责从 MySQL 读数据并展示到前端页面。
数据源(爬虫/API采集) ↓ 原始文件(JSON/CSV) HDFS(分布式存储) ↓ 映射 Hive(原始表/明细表) ↓ Spark SQL + DataFrame(清洗/特征工程) 宽表(特征+标签) ↓ 训练/预测 模型输出(价格/销量预测值) ↓ MySQL(结果库) ↓ Django REST API → 前端可视化这条链路并不复杂,但它把每个技术都放到了真实场景里:Hadoop 负责扛住历史积累的海量原始数据,Hive 负责把杂乱的文件“结构化”,Spark 负责计算,LLM 负责给用户提供自然语言交互,Django 负责把一切变成可操作的产品。答辩时,面试官问“为什么用 Hadoop”,你可以明确回答:因为需要存全量历史明细,而且数据的写入是追加型、离线批处理为主,这正是 HDFS 的适用场景,不是 MySQL 不合适,是场景不同。
1.2 为什么不是 MySQL 一把梭
很多不做大数据的同学会问:价格数据一年也就几十万条,MySQL 完全存得下,为什么要绕这么大一圈?这个问题我也会在答辩前自问。答案其实分两层。
第一层是“工程学习价值”。毕设项目的核心目标之一是展示你掌握了大数据处理的一般方法,所以即使数据量不大,也要按照分布式思维去做全链路。第二层是“真实场景的扩展性”:如果数据源扩展到全国几百个批发市场、覆盖过去十年的历史数据,同时接入气象、物流、舆情等外部数据,数据量会快速膨胀到几个 TB 甚至更大。单机 MySQL 在扩容、备份、计算下推上很快到达瓶颈,而 HDFS + Hive + Spark 的横向扩展能力是天然优势。我的做法是:HDFS 存全量原始文件和中间结果,MySQL 只存“给 Web 应用使用的最终结果”,两边职责分明,谁也不抢谁的活。
2. Hadoop 与 Hive 环境搭建:版本匹配是第一道坎
环境搭建是这套系统第一个大坑。网上搜“hadoop 伪分布式搭建”“从零安装 hadoop”这类教程,十有八九版本混乱,因为同一个教程可能 Hadoop 是 2.x 而 Hive 是 3.x,装完各种不兼容。我的建议非常直接:统一用 CDH 系或 Apache 系的稳定版本组合,不要混搭发行版。我最终使用的是 Hadoop 3.3.4 + Hive 3.1.3 + JDK 8,这三个版本组合经过大量验证,兼容性最稳。
2.1 版本匹配与核心配置
Hadoop 3.x 对 JDK 的要求是 8 或 11,Hive 3.1.x 官方推荐 JDK 8,所以 JDK 8 是这套组合的底座。别为了追新用 JDK 17,会在 Hive 启动阶段碰到各种反射异常,排查起来非常痛苦。Hadoop 伪分布式模式下,核心配置文件有三个:core-site.xml、hdfs-site.xml、yarn-site.xml。我当时踩过的典型错误是hdfs-site.xml里忘了配置dfs.replication,伪分布式只有单节点,默认值 3 会导致数据块始终处于欠副本状态,启动后dfsadmin -report看到一堆 UNDER_REPLICATED,看着就心烦。
core-site.xml 关键配置 fs.defaultFS=hdfs://localhost:9000hdfs-site.xml 关键配置 dfs.replication=1 dfs.namenode.name.dir=/opt/hadoop/data/namenode dfs.datanode.data.dir=/opt/hadoop/data/datanode另外,集群模式下如果要把 Hadoop 和 ZooKeeper 整合起来管理高可用,还需要单独配 zookeeper 地址,但本科学位论文不追求高可用,伪分布式足够。别在 HA 上浪费大量时间,后面还有更多功能要写。
2.2 Hive 建表:分区、存储格式和 ORC 的重要性
Hive 的本质是把 HDFS 上的文件映射成表。对于农产品价格数据,我建的表结构是这样的:
CREATE EXTERNAL TABLE ods_market_price ( market_id STRING COMMENT '市场编码', market_name STRING COMMENT '市场名称', product_id STRING COMMENT '农产品编码', product_name STRING COMMENT '农产品名称', price DOUBLE COMMENT '批发均价(元/公斤)', volume DOUBLE COMMENT '成交量(吨)', unit STRING COMMENT '计量单位' ) PARTITIONED BY (dt STRING COMMENT '日期分区,格式yyyy-MM-dd') ROW FORMAT SERDE 'org.apache.hadoop.hive.orc.OrcSerde' STORED AS ORC LOCATION '/warehouse/ods/ods_market_price';这里有几个关键选择。第一,用外部表而不是内部表,因为数据是采集程序先写入 HDFS、再通过 Hive 映射访问,外部表删除表不会动原始文件,更安全。第二,用 ORC 而不是文本格式,ORC 列式存储压缩率高、读取快,价格数据日增量几 MB 不明显,但整年累积下来差别很大。第三,按天做分区,查询某一天或某一段日期可以分区裁剪,不用全表扫描。
分区之后,我最开始犯的一个典型错误是:每天跑采集任务都生成一个小文件,时间一长,Hive 表下堆积了几百个小文件。这就直接撞上了热搜词里那个高频问题——“hive 优化小文件”。小文件会让 NameNode 内存吃紧,也会让 Spark/Hive 的 task 数量爆炸。后来我在采集任务里加了合并逻辑:
INSERT OVERWRITE TABLE ods_market_price PARTITION (dt='2024-05-20') SELECT ... FROM temp_table DISTRIBUTE BY rand();这个DISTRIBUTE BY rand()的思路是让数据随机分散到少数几个 reducer,每个 reducer 各写一个文件,最终在分区下只生成少数几个大文件而不是几十个小文件。处理完已有小文件后,还可以用ALTER TABLE ... PARTITION ... CONCATENATE做 ORC 文件合并,这个命令实测对 ORC 格式很有效。
2.3 Hive 查询优化:窗口函数和 UDAF
数据清洗和特征拼接过程中,Hive 窗口函数用得极多。比如要计算每种农产品过去 7 天的移动平均价格,最方便的就是LAG()和AVG() OVER():
SELECT product_id, dt, price, AVG(price) OVER (PARTITION BY product_id ORDER BY dt ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7 FROM ods_market_price WHERE dt >= '2024-01-01';窗口函数解决的是“跨行计算”问题,这在时间序列特征里非常常用。至于热搜词里的 “hive 自定义 udaf 函数”,在毕设里一般用不到,但如果答辩时想展示你的深度,可以把“计算某个产品的价格波动率”写成一个简单的 UDAF,或者用STDDEV内置函数实现。没必要非要自己造轮子,除非题目明确要求。
3. Spark 数据清洗与特征工程:代码怎么写最省心
环境搭好、Hive 表建好后,真正的工作量集中在 Spark 这一层。我用的是 PySpark,理由是开发速度快、写起来接近 pandas,对不熟悉 Scala 的同学更友好。虽然大数据界对 PySpark 和 Scala 有争论,但一个主攻 Web 的毕设,PySpark 完全够用。
3.1 数据质量问题比想象中多
真实采集到的价格数据非常脏。我遇到过的典型问题包括:
- 缺失值:某市场周末不上传数据,导致部分日期没有记录。
- 异常值:芹菜价格一天内从 2.5 元跳到 25 元,换算单位错误。
- 重复记录:同一个市场同一种产品同一天被采集程序抓了两次。
- 编码问题:部分农产品名称出现乱码,尤其是生鲜品类里的特殊字符。
清洗逻辑我写成几步:先按主键去重,再对数值列做范围校验(价格不能小于 0.5 元、不能高于 200 元),然后用“相邻日期均值”填充小范围缺失。范围阈值的设定需要结合业务知识,比如水果和蔬菜价格正常区间完全不同,我会先算每个品类的分位数,再把超出 99% 分位数的值标记为异常。
from pyspark.sql import functions as F # 按分区读取 df = spark.read.format("hive").table("ods_market_price") # 去重 df = df.dropDuplicates(["market_id", "product_id", "dt"]) # 异常值替换:把超过 99% 分位数的值置空,后面统一填充 quantile = df.approxQuantile("price", [0.99], 0.01)[0] df = df.withColumn("price", F.when(F.col("price") > quantile, None).otherwise(F.col("price")))这种用分位数做异常检测的办法,实现成本低,答辩时也能讲清楚原理。如果换用复杂的统计模型(如孤立森林),反而增加了部署和解释的复杂度,对毕设来说性价比不高。
3.2 特征工程:把价格序列变成模型能吃的结构
做完清洗,接下来就是生成特征宽表。时间序列预测里最经典的特征有五类:
| 特征类别 | 具体特征 | 说明 |
|---|---|---|
| 滞后特征 | lag_1, lag_2, lag_7, lag_14 | 前 1/2/7/14 天的价格,用LAG()生成 |
| 滑动统计 | ma7, ma30, std7 | 7 日和 30 日移动平均与标准差 |
| 日历特征 | 月份、星期、是否节假日 | 蔬果价格有明显的季节和节假日效应 |
| 外部特征 | 当日气温、降雨量 | 恶劣天气会推高蔬菜运输成本 |
| 交叉特征 | 上一年同期价格 | 捕捉年度周期性 |
这里所有特征我都统一在 Spark 里用 DataFrame API 完成,输出宽表结构类似:
market_id, product_id, dt, price, lag_1, lag_2, ..., ma7, ma30, temp, rainfall, is_holiday, label最后一个label是未来一天的实际价格,用来监督模型学习。写好之后把宽表写回 Hive(或者直接写 MySQL),供后续训练使用。
3.3 Spark 任务跑不动的排查思路
Spark 调优这类问题卡住过不少人。我当时最常遇到的现象是:任务启动后一直卡在“RUNNING”状态,或者执行中报Container exited with non-zero exit code 143。原因基本都是内存不够或者 executor 资源分配不合理。
- 如果只有 8GB 内存的笔记本,executor 内存不要超过 4GB,driver 内存不要超过 2GB。
- 分区数量太少会导致单个任务处理数据量过大,分区太多又会导致调度开销高。一个合理起点是控制每个分区在 100MB 左右。
- 数据倾斜表现为某些 task 执行特别慢,可以优先检查是不是某个市场的数据量远大于其他市场,必要时用
repartition(columns)做二次分区。
调优时有条件的话可以盯一下 Spark UI 的 executor 页面,看看 GC 时间和 shuffle 数据量。还有一个现成思路是给 Spark 配上监控指标,网上搜“spark 内存线程监测工具”能看到不少现成方案,但是毕设阶段把 Spark UI 看懂就够用了。
spark-submit \ --master yarn \ --deploy-mode client \ --driver-memory 2g \ --executor-memory 4g \ --executor-cores 2 \ --num-executors 2 \ etl_job.py4. 价格与销量预测:模型不用贪多,把链路跑通最重要
预测是这套系统的“算法核心”,也是答辩时最容易被追问的部分。很多同学一上来就想堆 LSTM、Transformer,但真实情况是:农产品价格受供需、天气、政策、运输成本等多重因素驱动,复杂模型未必比简单模型更准。我当时做了模型对比实验,最后线上采用的是 XGBoost,而不是深度学习模型。
4.1 价格预测:XGBoost 的实战参数
XGBoost 在中小规模表格数据上有很强的优势:训练快、支持特征重要度解释、不容易过拟合。我按时间顺序划分训练集和验证集,而不是随机划分——这一点在时间序列里非常重要。用随机划分会引入未来信息泄漏,看似验证集分数很高,一上真实环境立刻“翻车”。
训练时几个关键点的选择如下:
| 参数/操作 | 我的取值 | 原因 |
|---|---|---|
| 时间划分比例 | 前 80% 训练,后 20% 验证 | 模拟真实预测场景 |
| 验证方式 | 滚动预测 | 每次预测未来 1 天,逐步向后推 |
| 评估指标 | RMSE、MAE | 关注误差的绝对值,便于业务解释 |
| 主要特征 | 滞后值、移动平均、天气、节假日 | 滞后值和均线已能解释大部分价格惯性 |
训练代码大致是这样:
import xgboost as xgb from sklearn.metrics import mean_absolute_error train = df[df["dt"] < "2024-04-01"] valid = df[(df["dt"] >= "2024-04-01") & (df["dt"] < "2024-05-01")] features = ["lag_1", "lag_2", "lag_7", "ma7", "ma30", "temp", "rainfall", "is_holiday", "month", "weekday"] model = xgb.XGBRegressor( n_estimators=300, max_depth=6, learning_rate=0.05, subsample=0.8, colsample_bytree=0.8, random_state=42 ) model.fit(train[features], train["label"]) pred = model.predict(valid[features]) print("MAE:", mean_absolute_error(valid["label"], pred))实际跑下来,西红柿、黄瓜这类常用蔬菜的 MAE 在 0.2~0.4 元/公斤左右,比基线模型(直接用昨天价格当预测值)降低了约 20%。这个结论在论文里很有说服力:不是拿模型精度吹牛,而是做了对比实验,说明模型相对于“朴素预测”是有增益的。
4.2 销量预测:LSTM 怎么做到不“炸”训练
销量预测我做了两版:一版是用和价格预测相同的 XGBoost,另一版是 LSTM。LSTM 适合序列关系较强的数据,但训练起来比较“娇气”,特征尺度不一致很容易导致 loss 直接冲到 NaN。所以我给销量特征做了标准化,同时也把窗口长度固定为 30 天。
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train.reshape(-1, X_train.shape[-1])).reshape(X_train.shape)LSTM 的输入形状是(样本数, 时间步长, 特征数),我把每 30 天的历史特征编码成一个样本,预测未来 3 天销量。训练时用 Early Stopping 控制轮数,patience 设为 10,目的是防止在验证集上过拟合。
对比实验的结果是:在这批数据上,LSTM 相比 XGBoost 没有明显优势,反而训练时间多了 3 倍。最后我线上预测仍然用 XGBoost,论文里把 LSTM 当作“对照算法”写,既展示了深度学习的尝试,又体现了判断力。这里有个经验:毕业设计的重点不是“用最复杂的模型”,而是“有理有据地选择合适模型”。
4.3 模型上线与定时预测
预测模型不能只活在 Notebook 里,还得接进系统。我的做法是:训练好的 XGBoost 模型用joblib.dump存成文件,Django 后台用 Celery 的定时任务每天早上 6 点执行一次预测流程——从数据库读最新特征、加载模型、生成未来三天的价格与销量预测、把结果写入 MySQL。这样整个系统看起来像是“活的”,用户每次打开看到的都是当天最新的预测结果。
5. LLM 大模型:不是噱头,是这四个真实功能
如果说 Hadoop 那套是“传统大数据”,那么 LLM 大模型就是这套系统里的“亮点担当”。但大模型怎么落,很多同学没想清楚。我的原则很简单:让大模型做它擅长的事(自然语言交互、语义理解、文本生成),而不是强行让它做数值预测。数值预测依旧交给 XGBoost,大模型只在三个场景出现。
5.1 模型选型:本地部署还是调用 API
毕设阶段选 LLM,先看自己的硬件。如果显卡显存在 8GB 以上,可以本地部署量化后的开源模型,比如 ChatGLM 或 Qwen 系列的小尺寸版本;如果没有 GPU,就直接调用在线 API。网上各种“open llm leaderboard 等公开榜单”可以参考选型,但对于一个毕设项目,我更建议用 API,原因很简单:方案稳定、开发快、不占本地内存,答辩现场也演示更顺畅。
我当时在本地部署了 Qwen 的 7B 量化版,用transformers库加载。推理慢的问题后面会专门讲。这里先提醒一句:7B 模型即使量化,吃满内存也是 6GB 起步,如果用 CPU 推理,生成一段回答可能要几十秒,用户体验会很差。
5.2 功能一:农产品智能问答
这是 LLM 最核心的落点。用户问“近期西红柿价格为什么波动这么大”,系统不能只返回一句“这是由供需关系决定的”这种废话。我的实现方式是:先把用户问题通过一个意图识别模块分类——是问价格、问销量、问推荐,还是问天气影响——再根据意图从 MySQL/Hive 动态取数,最后把数据拼接成 prompt 交给 LLM 生成有数据支撑的回答。
比如用户问西红柿价格,后端先查出近 30 天的价格序列和 7 日移动平均,拼出这样的 prompt:
以下是西红柿近30天批发均价数据(元/公斤): 日期: 价格 2024-04-21: 3.20 ... 请分析近期价格变化趋势,并从季节性、天气等角度给出简要解释,控制在150字以内。这种“查数 + 拼 prompt”的模式,实际上是简化版的 RAG(检索增强生成),不涉及复杂的向量库,但一样能让大模型的回答“有根有据”。答辨时一说这是 RAG 的思路——从结构化数据库检索上下文再交给 LLM 生成——老师基本都会点头认可。
5.3 功能二:基于语义的农产品推荐
推荐系统的常规做法是协同过滤或者基于物品的相似度召回。我在这个基础上做了个小创新:用 LLM 做用户意图的语义解析。传统推荐需要用户在页面上点击按钮选品类,而我的系统支持用户直接输入一句话:“推荐两种适合夏天吃、价格在 3 块钱以内的蔬菜”。
推荐流程是这样的:用户自然语言 → Django 接口 → LLM 抽取三个关键槽位(品类倾向、价格上限、营养价值要求)→ 从数据库召回候选农产品 → 用价格预测模型算未来价格和波动 → 按“价格低、波动小、新鲜度高”的规则排序 → 输出 Top 推荐。
这里需要理解 LLM 的 token 机制,网上有个很形象的说法:token 关注的三个维度,key 代表“我是谁”,query 是“我在找什么”,value 是“我能提供什么”。放到这个场景里:模型要识别当前对话的目标是“推荐农产品”,要抽取的属性是“价格范围、季节、品类偏好”,匹配到的候选农产品就是 value。理解了这套语义机制,LLM 的调度逻辑就顺了。
5.4 功能三:推荐解释生成
推荐结果出来后,系统不能只给一行列表,而是给一句人话解释:“根据近期行情,西红柿价格处于近 30 天最低位,未来一周预计小幅上涨,建议现在购买”。这个解释就是 LLM 生成的。我先把后端算好的数据传进去,要求 LLM 以不超过 50 字的口播文案输出。这一步虽然简单,但极大提升了系统 demo 的“观感”——用户觉得自己面对的不只是一个数据库查询工具。
5.5 LLM 集成的三个工程细节
第一,prompt 要加输出约束。比如“只输出 JSON”“不要输出多余解释”,这样后端可以稳定解析。如果答应格式不稳定,可以在解析失败时加一层 fallback 逻辑,重新请求一次。
第二,控制上下文长度。对话历史如果无限累积,token 会迅速加长,推理时间变慢,成本也上涨。我保留了最近五轮对话作为上下文,更早的历史直接截断。为了避免“失忆”,我在截断时会把上一轮总结出的用户偏好(比如“用户偏好降价商品”)作为额外上下文传入。
第三,异步化。LLM 推理速度慢,如果前端同步等结果,几秒到几十秒的等待会让人以为系统挂了。我的方案是接口先返回“任务已提交”,后台用 Celery 完成调用,结果通过 WebSocket 推给前端。这样体验上就跟聊天软件一样,消息一条条蹦出来,而不是白屏转圈。
6. Django 应用层:把大数据系统变成看得见的网页
Django 这部分是整个系统的“临门一脚”。很多同学把精力全花在配置 Hadoop、调 Spark 上,结果最后 Web 页面很简陋,答辩时观感大打折扣。我的建议是:宁可大数据部分少玩花活,也要把 Web 页面做得丰满、好看、可交互,毕竟评委第一眼看到的是页面而不是 Hive SQL。
6.1 Django 项目结构与 APP 划分
Django 项目我喜欢按业务域拆 app,方便后期维护,也方便答辩讲“工程规范”:
apps/users:用户注册、登录、偏好管理apps/market:展示价格曲线、销量排行、市场行情apps/predict:价格与销量预测的前端展示apps/recommend:推荐结果展示apps/ai_chat:LLM 问答聊天界面
创建 app 的命令就是最基础的那个python manage.py startapp xxx,但要注意记得在settings.py里注册,初学时最容易漏掉这一步。
6.2 后端接口:DRF 怎么封装模型结果
Django 生态里写 API 首选 DRF(Django Rest Framework)。我设计了一批 RESTful 接口,举两个核心的例子:
GET /api/market/price?product_id=1001&days=30 # 获取价格历史序列 GET /api/predict/forecast?product_id=1001&days=3 # 获取未来三天预测DRF 的视图层我直接用APIView封装,没有过度依赖视图集,因为这种以查询为主、返回 JSON 给前端的场景,手写逻辑更直观。返回结构保持统一:
{ "code": 0, "message": "success", "data": { "dates": ["2024-05-01", "2024-05-02", ...], "prices": [3.2, 3.1, ...], "forecast": [3.15, 3.22, ...] } }前端拿这个 JSON 直接用 ECharts 绘制,非常省事。
6.3 异步任务与 WebSocket:后台数据怎么实时推给前端
热搜词里高频出现的“python django websocket 实现后台有数据前端推送”,这其实是我系统里最出彩的一个点。我用了channels库实现 WebSocket,它的作用在于:当后台 Celery 完成一次预测或者 LLM 生成了一部分回复时,服务端能主动把数据推送到前端,而不是靠前端轮询。
pip install channels channels-redis配置好ASGI_APPLICATION之后,新建一个consumers.py,核心逻辑是处理 WebSocket 连接和推送消息。比如 LLM 问答场景里,用户输入问题后前端通过 WebSocket 发送消息,后端收到后触发 LLM 推理任务,推理过程中每生成一批 token 就通过group_send推送到前端,前端收到后逐步渲染出来——这就是类似 ChatGPT 打字机效果的实现来源。
class ChatConsumer(AsyncWebsocketConsumer): async def connect(self): await self.accept() await self.channel_layer.group_add("chat", self.channel_name) async def receive(self, text_data): # 触发后台 LLM 任务 await self.channel_layer.group_send("chat", { "type": "chat.token", "message": "价格正在逐步回升..." }) async def chat_token(self, event): await self.send(text_data=json.dumps({"token": event["message"]}))6.4 前端可视化:把预测结果做得像产品
前端我选的是 Vue + ECharts,因为 ECharts 对时间序列图的支持非常完善,折线图、热力图、饼图都能开箱即用。页面整体规划分四块:首页是大盘概览,显示今日均价、涨跌排行、预测预警;价格趋势页是核心,用双折线图展示“历史价格 vs 预测价格”,让用户一眼看出未来三天的走势;推荐页放推荐结果和文字解释;AI 问答页就是聊天窗口。
从实用角度说,这些页面不需要多复杂的设计,但一定要让数据和预测结果在视觉上“说话”。一个简单的技巧:在未来预测的部分用虚线展示,历史部分用实线,用户一看就明白哪里是“预测”区间。
7. 常见问题与排查方法:这套系统最全的避坑清单
整套系统做下来,前前后后踩的坑没有三十个也有二十个。我把最典型的问题整理成速查表,你如果也打算做这个方向,直接对着排查。
| 问题现象 | 根本原因 | 解决办法 |
|---|---|---|
| Hadoop 启动后 DataNode 起不来 | namenode 格式化 ID 不一致 | 删除 data 目录重新格式化,或使用hdfs namenode -bootstrapStandby |
| 访问 Hive 表出现中文乱码 | Hive Metastore 的 MySQL 字符集不是 utf8 | 建库时指定DEFAULT CHARACTER SET utf8,并排除乱码分区后重建 |
| HDFS 下有海量小文件 | 采集任务频繁写入 | 离线合并文件,后续在写入前先合并或使用CONCATENATE |
| Spark 任务 OOM | 分区数过少或 executor 内存太小 | 增加分区、调整内存参数,监控 Spark UI 的 GC |
| Spark 读取 JSON 数据失败 | JSON 文件格式不规范,有多层嵌套或数组 | 使用from_json明确 schema 定义结构后再展开 |
| LLM 推理非常慢 | 模型过大 / 使用 CPU / 上下文过长 | 改用量化模型、限制上下文长度、启用异步推送 |
| Django 数据库查询慢 | 结果表没建索引 | 在 product_id、dt 字段上加联合索引 |
| WebSocket 连接失败 | channels 配置缺失或 redis 未启动 | 检查 ASGI 配置、确认 redis 服务状态 |
| Hive 删除乱码分区后数据还存在 | 外部表数据不在 HDFS 受管范围 | 手动清理 HDFS 目录hdfs dfs -rm -r对应分区路径 |
| 大规模数据迁移卡在中间 | 直接用hadoop fs -cp不具备断点续传能力 | 用hadoop distcp并传-update参数实现增量同步 |
这里面有几个值得展开说。第一,Hive 的乱码分区是甲骨文数据库故弄玄虚的祖传问题,核心原因是 metastore 初始化时字符集不对。新人碰到这个容易想到跑ALTER TABLE,实际上大乱麻的根子在 MySQL 库层面。第二,distcp参数是数据迁移的硬技能,它支持-update、-delete、-m并行度设置,做跨集群或跨目录迁移比cp稳得多。
还有一个容易忽视的坑:Hive 和 MySQL 都在跑的时候,电脑内存常常吃紧,我当时用的 16G 内存,Hadoop NameNode、DataNode、Hive Metastore、Spark Executor、MySQL、Django、Redis 全压在一台机器上,系统动不动就卡死。后来养成了随手关掉不用的服务的习惯:写代码时只开 MySQL + Django + Redis,跑数时再临时启动 Hadoop/Hive/Spark。别嫌麻烦,这套组合的内存管理本身就是毕设的一部分经验。
8. 最后再分享三个实用心得
项目做到收尾阶段,有几个体会非常深,写在这里供你参考。
第一个体会:整个系统最花时间的不是模型调参,而是数据准备和环境维护。到后期我统计过,大约 60% 的时间花在清洗脏数据、调整 hive 表结构、排查 spark 内存问题上,真正训练模型的时间不超过两成。所以计划里至少预留两周只做“环境稳定 + 数据质量”,否则后面很容易被反复的报错拖垮进度。
第二个体会:答辩演示时要准备一个“最小可用环境”。我一次真实场景,答辩教室网络差、电脑 CPU 也一般,本地 Qwen 模型跑了半分钟没出结果,非常尴尬。后来我把方案调整为:讲解阶段用录屏展示完整功能,现场演示只跑轻量接口,LLM 切换成调用 API。演示的稳定性永远优先于演示的花哨程度。
第三个体会:这套系统的价值不止于毕设本身。做完之后,采集数据、离线数仓、特征工程、模型服务、Web 展示这一整套链路,几乎覆盖了数据岗位日常工作的核心流程。面试时你把这条链路讲清楚,比背一百道“Hadoop 面试题”都管用。以后如果想继续进阶,还可以把方向延伸到实时流处理(用 Flink 替换批处理)、把价格预测改成日级实时更新、把 LLM 换成更大的模型做行情分析报告自动生成。这个项目的扩展方向非常多,能讲的后续动作也足够多,无论作为毕设还是作为作品集项目,性价比都很高。
就说这么多,剩下的就靠你自己动手去踩一遍了。毕设这东西,真正做完一整套,远比围观一百篇教程学得多。