近两年“数据可视化”“大数据实战”这类词在招聘JD里出现的频率越来越高,但真正能把整套链路讲清楚的教程并不多。绝大多数新手要么卡在环境搭建,要么在分析完数据之后不知道怎么做可视化,更有不少人直接把Kaggle上的案例搬过来,跑通就以为完事了。这次拿淘宝化妆品销售数据做一个完整的大数据实战项目,把Hadoop、Spark、数据清洗、指标计算、可视化展示全部串起来,给准备做课程设计、毕设或者想熟悉大数据开发流程的朋友一条能直接照抄的路线。
先说清楚这个项目能解决什么问题。很多人学完Hadoop和Spark理论之后,最大的困惑是“这些组件到底怎么组合在一起干活”。这个项目正好覆盖了从数据采集落地到HDFS、Spark SQL做清洗分析、最后用ECharts把结果可视化的完整链路,相当于把大数据开发中最常用的一套组合拳打了一遍。项目本身不算难,但信息量很大,适合有一定Linux和Java基础、但对大数据生态还比较陌生的人,学完以后你对整个离线数仓的雏形会有一个非常具体的感知。
1. 项目定位与整体设计思路
1.1 为什么选择淘宝化妆品销售数据做载体
做数据分析项目最难的不是技术,而是找不到一份“既真实又可落地”的数据。化妆品类目的销售数据在整个电商数据里非常典型:有明确的商品属性(品牌、品类、功效)、有时间维度(日销、月销、活动大促)、有地域分布(省份、城市)、有价格区间,这些维度天然适合做多角度分析。更重要的一点,化妆品销售数据和“人”的关联度很高,你可以引入复购率、客单价、购买频次这些用户行为指标,让分析结果更有业务感。
项目选这个主题还有一个现实原因:淘宝开放平台早期提供过部分类目的公开数据接口,网上也有很多脱敏后的商业数据集流传。相比自造数据,这些数据在字段的丰富度和真实分布上要好得多,处理起来也更接近工业场景。我用的这份数据一共包含30万条左右订单记录,字段包括订单编号、买家ID、商品标题、品牌、类目、价格、销量、成交时间、收货省份、付款金额等,拿到手是CSV格式,大小约1.2GB,解压后接近2GB。这个体量对于学习Hadoop分布式存储和Spark内存计算来说非常合适,既能体现大数据的“大”,又不会因为数据量过大导致个人电脑跑不动。
1.2 技术选型背后的取舍逻辑
整套系统的核心技术栈是Hadoop 3.3.4 + Spark 3.4.1 + Hive 3.1.3,另外用Sqoop做数据导入导出,用ECharts做可视化展示。选这套组合有几个具体考虑。
首先是Hadoop版本。Hadoop 3.x相比2.x在NameNode性能、纠删码、YARN资源管理上都有明显提升。3.3.4是目前稳定性和社区口碑都比较好的版本,坑相对少。Spark选3.4.1是因为它和Hadoop 3.x的兼容性做得比较好,而且对SQL语法支持已经非常完善,很多业务计算直接用Spark SQL就能搞定,不需要像老版本那样写一堆RDD算子。
Hive在这个项目里的作用容易被忽略,但实际很关键。Spark可以直接读HDFS上的文件做分析,但如果数据的字段含义、分区策略、清洗规则没有一套统一的元数据管理机制,项目后期会非常混乱。Hive把表结构定义清楚之后,Spark SQL通过Hive Metastore直接读取表结构,三层之间的协作关系就很清晰了。这是生产环境里数仓建模的标准姿势。
存储和计算的部分确定之后,可视化的选择就自由很多了。项目里用了ECharts的柱状图、折线图、饼图、地图,配合Spring Boot提供数据查询接口,前端用Vue写展示页面。这套方案的好处是每一层都能单独调试,排查问题的时候不用一头扎进前端代码里。
1.3 系统架构与数据流转逻辑
整个系统的数据流转分为五层。
数据接入层:通过Python脚本采集或下载原始CSV数据,检查数据完整性后上传到HDFS。存储采用HDFS默认的128MB Block大小,副本数设为2,兼顾容错和存储效率。
数据清洗层:利用Spark SQL对原始数据进行ETL,包括去重、缺失值处理、格式标准化、异常值过滤。清洗后的结果写入Hive分区表,按天分区。这一层是整个项目的核心,后面所有分析能不能得出靠谱的结论,全靠这里把关。
数据计算层:基于清洗后的Hive表,用Spark SQL完成各类指标计算,包括销售总额、销量趋势、品牌排行、地域分布、价格区间分析、复购率等。计算结果统一输出到MySQL,方便查询和可视化对接。
数据展示层:Spring Boot后端从MySQL读取聚合结果,通过RESTful接口返回JSON数据。前端页面用ECharts渲染图表,支持按时间范围、品牌、类目做交互式筛选。
任务调度层:用Crontab定时调度Spark任务和Sqoop导入任务,让整个流程在每天凌晨自动跑一遍,模拟生产环境中的离线数仓调度场景。
这套架构没有引入消息队列和实时计算框架,因为项目定位是离线分析场景,引入Kafka、Flink反而会冲淡主线。先把离线链路吃透,再往实时方向扩展会轻松很多。
2. 基础环境搭建与集群规划
2.1 硬件与网络规划建议
很多人在环境搭建阶段就翻车,不是因为操作不对,而是集群规划不合理。如果是自己学习,完全没必要硬凑三台服务器,一台16G内存的台式机或者云主机就能跑起来。我这边用的是4核8G的云主机加一台16G内存的本地电脑组了两节点的集群,NameNode和ResourceManager在主节点,SecondaryNameNode和NodeManager分布在两个节点上,勉强能跑完整流程。
如果预算有限,伪分布式模式也是可以的。但这里要提醒一句,伪分布式跑Spark任务时,Executor和Driver都在同一台机器上,资源竞争问题会比较明显,一些分布式环境才会出现的经典问题(比如数据倾斜、Shuffle调优)很难完整复现。所以我更推荐两台机器,主节点做Master,从节点做Worker,既能体现分布式特性,性能上也够用。
HDFS的副本数在测试环境建议设为2,不要用默认的3。两个节点的集群配3副本必然导致部分副本无法写入,虽然任务还能跑,但日志里会刷大量的警告,排查其他问题时会干扰视线。
2.2 关键组件的安装与配置要点
Hadoop和Spark的安装网上教程很多,不展开写每个细节,只把几个容易出问题的地方强调一下。
JDK版本务必选择JDK 8。JDK 11和JDK 17虽然也能跑Hadoop,但有些老组件在兼容性上会出幺蛾子,没必要给自己挖坑。Hadoop的core-site.xml、hdfs-site.xml、yarn-site.xml这三个配置文件的参数要检查仔细。core-site.xml里fs.defaultFS的地址不能用localhost,要用主节点的hostname,否则从节点的DataNode注册时会找不到NameNode。yarn-site.xml里yarn.nodemanager.resource.memory-mb要根据实际内存设置,不要超过服务器物理内存的75%,否则NodeManager启动后容易自动退出。
Zookeeper在项目中不是必须的,但如果你的DataNode比较多,或者想启用HDFS HA高可用,那就得把Zookeeper集成进来。在配置hadoop和zookeeper整合时,需要把hadoop-env.sh里的ZOO_HOME路径配好,然后在hdfs-site.xml里加上HA相关的参数。前面列的热搜词里也有人问“hadoop和zookeeper整合实战”,确实这块很容易踩坑,之后可以单独写一篇。
Spark的部署我选了YARN模式,而不是Standalone模式。原因很简单:Standalone模式虽然配置简单,但资源管理和任务调度都是Spark自己说了算,跟你Hadoop集群的YARN是两套东西,维护起来别扭。YARN模式下Spark作业会统一提交给ResourceManager,由它分配Container去跑Driver和Executor,这是生产环境最主流的方式,学它不亏。
2.3 大数据集群部署策略总结
这里整理了一份适合课程设计和毕设场景的部署策略表,供参考。
| 组件 | 主节点 | 从节点 | 说明 |
|---|---|---|---|
| NameNode | 是 | 否 | HDFS元数据管理,内存要够 |
| DataNode | 否 | 是 | 实际数据存储节点 |
| ResourceManager | 是 | 否 | YARN资源调度中枢 |
| NodeManager | 是 | 是 | 每台机器都要有 |
| Spark | 是 | 是 | 客户端在主节点,Executor跑在从节点 |
| Hive | 是 | 否 | Metastore在主节点 |
| MySQL | 是 | 否 | 存储结果数据 |
| Zookeeper | 可选 | 可选 | 需要HA时才部署 |
配置优先级上,先搞Hadoop,再搞Zookeeper,最后搞Spark。顺序反了会导致各种诡异的连接失败,因为Spark启动时要访问HDFS地址,YARN要访问Zookeeper地址,底层服务没起来,上层肯定挂。
3. 数据准备与清洗流程
3.1 数据字段设计与业务含义
开始编码之前,先把业务字段梳理清楚。这份淘宝化妆品销售数据的主要字段如下:
| 字段名称 | 示例值 | 业务含义 |
|---|---|---|
| order_id | 283748291023 | 订单唯一标识 |
| buyer_id | 18293847 | 买家ID,脱敏处理过 |
| item_id | 772839102 | 商品ID |
| item_title | 兰蔻小黑瓶精华肌底液30ml | 商品标题 |
| brand_name | 兰蔻 | 品牌名称 |
| category_name | 面部精华 | 一级类目 |
| price | 760.00 | 商品单价 |
| quantity | 2 | 购买数量 |
| pay_amount | 1456.00 | 实付款金额 |
| order_time | 2024-03-15 12:34:56 | 成交时间 |
| province | 广东省 | 收货省份 |
拿到数据的第一件事不是急着清洗,而是先统计数据的数量和基本分布,对数据有一个整体感知。我用Python的pandas做了快速探查,发现原始数据里有两个明显问题:一是存在少量重复订单,同一个order_id出现了两三次;二是部分记录的pay_amount为0,可能是优惠券抵扣或者订单取消导致的无效订单。还有一个比较隐蔽的问题是order_time的格式不统一,有的是“2024/3/15 12:34”这种斜杠格式,有的是标准横杠格式,需要在清洗时统一转换。
3.2 清洗规则与Spark SQL实现
清洗逻辑的设定直接决定分析结果的质量。这个项目的清洗规则整理下来主要五条:
第一,去重。基于order_id做去重,保留最新一条记录。第二,过滤异常价格。把pay_amount为0或小于0的订单剔除,这类订单没有分析价值。第三,格式化时间。所有order_time统一为“yyyy-MM-dd HH:mm:ss”格式,为后续时间维度分析打基础。第四,处理缺失值。brand_name为空的统一填“未知品牌”,province为空的根据常用规则填“未知省份”。第五,类型转换。price、quantity、pay_amount统一转为Decimal类型,避免后续聚合计算时出现精度丢失。
清洗代码用Spark SQL写起来很清爽。先把原始CSV文件映射成临时表,然后执行一段清洗SQL:
CREATE TABLE dwd_sales_clean AS SELECT order_id, buyer_id, item_id, item_title, COALESCE(brand_name, '未知品牌') AS brand_name, COALESCE(category_name, '其他') AS category_name, CAST(price AS DECIMAL(10, 2)) AS price, CAST(quantity AS INT) AS quantity, CAST(pay_amount AS DECIMAL(10, 2)) AS pay_amount, CAST(order_time AS TIMESTAMP) AS order_time, COALESCE(province, '未知省份') AS province FROM ods_sales_raw WHERE pay_amount > 0 AND order_id IS NOT NULL QUALIFY ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY order_time DESC) = 1;这里用QUALIFY子句实现的窗口函数去重很高效,只扫一遍数据就能完成排序和去重。清洗完成之后,再按天做分区写入Hive表,后续查询可以走分区裁剪,效率提升非常明显。
实际开发里还有一个容易被忽略的环节:清洗前后的数据量对比统计。我习惯在ETL之后跑一条count,记录原始表和清洗表的条数差异。这不仅是数据质量的记录,也是答辩时向老师展示你对数据敏感度的有力证据。这个项目里原始30万条记录清洗后剩余28.7万条,剔除比例约4.3%,属于正常范围。
3.3 ETL实现中的性能优化细节
数据量不大时怎么跑都行,但养成优化的好习惯还是有必要,毕竟你以后要面对的数据量可不会这么温柔。
第一个优化是采用Hive分区表。清洗后的结果按日期分区,每天一个分区目录。查询时只要带上日期过滤条件,Spark SQL就只会扫描对应的分区,不需要全表扫描。第二个优化是使用ORC列式存储格式。ORC相比CSV或JSON,压缩比高、读取速度快,在后续分析中性能提升非常明显。建表语句核心部分如下:
CREATE TABLE dws_sales_info ( order_id STRING, buyer_id STRING, ... province STRING ) PARTITIONED BY (dt STRING) STORED AS ORC;第三个优化是在Spark作业里合理设置并行度。默认情况下Spark会按照文件大小自动推断分区数,但数据量不大时分区数过多反而浪费资源。我在ETL作业里用repartition(8)把数据分成8个分区,让每个Executor都能拿到活儿干,又不至于频繁切换任务上下文。
4. 基于Spark的销售指标分析与核心代码实现
4.1 分析指标的选取与业务口径
分析指标的选取是整个项目最具业务感的环节。项目核心指标确定为六类,每一类都对应一个明确的业务问题:
销售总览:整体销售额、总销量、客单价(销售额除以订单数)。对应的问题是“这个类目在淘宝上有多大体量”。
时间趋势分析:按天、按月统计销售额和订单量变化。对应的问题是“销量有没有季节性波动,大促活动的拉动效果如何”。
品牌竞争分析:TOP20品牌的销售额、销量、均价对比。对应的问题是“哪些品牌在化妆品类目里占据头部地位”。
地域分布分析:各省销售额排名和客单价排名。对应的问题是“化妆品消费的地域差异有多大,哪些省份是重点市场”。
价格区间分析:按价格档位划分统计销量和销售额占比。对应的问题是“消费者最能接受的价格带在哪里”。
用户行为分析:复购率、人均购买次数、高价值用户群体画像。对应的问题是“用户对化妆品品牌的忠诚度高不高”。
这些指标的业务口径需要提前定义清楚,比如客单价是“销售额/订单数”还是“销售额/买家数”,得到的结论差别很大。项目中客单价定义为销售额除以有效订单数,复购率定义为购买次数大于等于2次的买家人数除以总买家人数。
4.2 Spark SQL核心分析代码
整个分析过程中最核心的几个SQL写出来给大家参考。
销售趋势按月统计:
SELECT DATE_FORMAT(order_time, 'yyyy-MM') AS month, COUNT(DISTINCT order_id) AS order_cnt, SUM(pay_amount) AS total_amount, SUM(quantity) AS total_quantity FROM dws_sales_info GROUP BY DATE_FORMAT(order_time, 'yyyy-MM') ORDER BY month;品牌TOP15排行:
SELECT brand_name, COUNT(DISTINCT order_id) AS order_cnt, SUM(pay_amount) AS total_amount, AVG(pay_amount) AS avg_price FROM dws_sales_info GROUP BY brand_name ORDER BY total_amount DESC LIMIT 15;地域销售额分布:
SELECT province, SUM(pay_amount) AS total_amount, COUNT(DISTINCT buyer_id) AS buyer_cnt, AVG(pay_amount) AS avg_order_amount FROM dws_sales_info GROUP BY province ORDER BY total_amount DESC;用户复购率计算:
SELECT COUNT(CASE WHEN buy_cnt >= 2 THEN 1 END) AS reorder_user_cnt, COUNT(*) AS total_user_cnt, COUNT(CASE WHEN buy_cnt >= 2 THEN 1 END) / COUNT(*) AS reorder_rate FROM ( SELECT buyer_id, COUNT(DISTINCT order_id) AS buy_cnt FROM dws_sales_info GROUP BY buyer_id ) t;有个细节要提醒一下:COUNT(DISTINCT order_id)在数据量大时会触发Shuffle,如果数据规模到了亿级,建议改用approx_count_distinct做近似去重,误差率可以控制在2%以内,但性能能快上一个数量级。
4.3 分析结果的解读与业务启示
数据算出来只是第一步,能把它讲成业务故事才是亮点。这个项目跑出来的几个结论很有意思。
从时间趋势看,全年的销售额呈“W”型分布,2月、6月、11月是三个高峰,分别对应年货节、618和双11。6月和11月的大促效应尤其显著,月销售额是平峰月份的2倍以上。这说明化妆品在线上的销售节奏高度依赖平台大促节点。
从品牌竞争看,头部效应非常明显。TOP15品牌贡献了约55%的销售额,其中欧莱雅、兰蔻、雅诗兰黛名列前茅,而国产品牌花西子、完美日记在特定价格带表现抢眼,正在快速崛起。尾部品牌数量庞大但销售额极低,市场竞争烈度很高。
从地域分布看,广东、江苏、浙江三个省份贡献了超过30%的销售额,而新疆、青海、宁夏等地虽然销售额占比低,但客单价反而偏高。这个现象可以解读为西北地区线下专柜覆盖不足,消费者更倾向于线上一站式购买高端产品。
从价格带看,100到300元区间是销量最大的价格带,占比接近40%。而1000元以上的高端护肤品虽然销量占比不到10%,销售额占比却达到22%,客单价极高,属于典型的高利润贡献区。
4.4 数据倾斜问题的规避方案
如果在更大规模的数据上跑同样的指标,数据倾斜是必现的经典问题,尤其在地域分布统计这个场景里。比如“广东省”的订单量如果占到30%甚至更多,那么Spark按province分组时,管理广东数据的Core就会比其他Core晚很多结束,整个Stage都被拖慢。
常规处理思路有两个。一是两阶段聚合,先加随机前缀把Key打散,做第一轮局部聚合,再去掉前缀做第二轮全局聚合。这个方法适合count、sum这类支持交换律和结合律的聚合操作。二是过滤掉热点Key,单独计算。如果只是为了让整体作业能跑完,有些时候把头部省份单独拎出来计算,其他省份正常聚合,最后合并结果,效率也很理想。
这在Spark面试题里属于高频考点,题目通常是“Spark数据倾斜怎么处理”。建议做项目的同时把这套思路理解透,答出两阶段聚合的原理,再结合你在项目里踩过的坑说一遍,面试官基本就认可了。
5. 可视化展示与前端对接
5.1 可视化图表的选型逻辑
指标算好了,最后一步就是用更直观的方式把它呈现出来。可视化不是把柱状图、折线图堆在页面上就完事,每个图表都要承担一个独立的分析视角。
销售总览区用数字卡片(KPI卡片)展示总销售额、总销量、客单价和订单总数,让看板的人第一眼就抓到核心数据。时间趋势图用双轴折线图,左轴销售额、右轴订单量,因为两个指标的量纲不一样,单轴会把其中一个压成一条平线。品牌排行用横向柱状图,品牌名很长时横向柱状图的可读性远好于纵向。地域分布用中国地图,颜色深浅映射销售额大小,鼠标悬停显示具体数据。价格区间分布用饼图,直观展示各价格段的销售占比。用户复购分析用漏斗图,展示从全部买家人数到多次购买人数再到高频购买人数的逐级收缩。
选ECharts还有一个现实原因:它的地图、漏斗图、KPI效果都不需要额外写太多样式代码,官方示例直接改数据就能用,开发效率很高。
5.2 后端接口设计与数据查询优化
后端我用Spring Boot写了一个轻量级的RESTful服务,每个分析指标对应一个接口。比如品牌排行的接口路径是/brand/top15,返回的JSON结构是:
{ "code": 200, "data": [ {"brand_name": "欧莱雅", "total_amount": 12800000.00, "order_cnt": 35000}, {"brand_name": "兰蔻", "total_amount": 9600000.00, "order_cnt": 12000} ], "message": "success" }前端页面的逻辑很简单:Vue组件初始化时通过axios请求后端接口,拿到数据后填充到ECharts的option配置里,调用setOption方法渲染图表。页面顶部放时间筛选器和类目筛选器,筛选条件变化时重新向后端请求数据,ECharts实例通过clear后再setOption的方式刷新。
MySQL里存放的是Spark算好的聚合结果,数据量都不大,查询效率非常高。但我在项目里还是给order_time、brand_name这两个高频查询字段建了索引,毕竟可视化页面可能要被反复刷新,接口响应时间控制在200ms以内体验才够顺滑。
5.3 前后端分离部署的关键细节
项目采用了前后端分离的部署方式。Spring Boot后端打包成jar包运行在8080端口,前端Vue项目通过npm run build生成静态文件,用Nginx托管在80端口。Nginx配置里需要设置反向代理,把/api开头的请求转发到后端服务:
server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html; } location /api/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里有个前后端分离的经典坑:如果不配置代理,前端页面在80端口,后端接口在8080端口,浏览器会提示跨域错误。解决方案就是后端接口加@CrossOrigin注解,或者用Nginx反向代理把两个服务揉在同一个域下面。项目里我选择了Nginx方案,因为生产环境更推荐这种做法,安全性更好。
6. 常见问题排查与优化实录
6.1 Hadoop启动与运行高频问题速查
这个项目做完的过程中踩过的坑不算少,整理成表格分享给各位,每一行都是真实检验过的。
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| NameNode无法启动 | 未执行hdfs namenode -format | 检查core-site.xml的地址是否配错,格式化前备份namenode目录 |
| 格式化失败 | 目录权限不足或端口被占用 | 检查/tmp/hadoop-*目录权限,用lsof查看9000端口占用 |
| DataNode启动后闪退 | 集群ID不一致 | 删除DataNode的current目录后重启,或者重新执行格式化 |
| Spark作业地址冲突 | spark.driver.host配置成localhost | 改为主节点的hostname |
| YARN Container一直Pending | 资源不足或调度器配置限制 | 检查yarn-site.xml内存配置和队列资源上限 |
| Hive查询报Table not found | Spark和Hive共用Metastore的配置缺失 | 确认hive-site.xml已复制到Spark的conf目录 |
Hadoop启动格式化失败这个问题出现概率最高,我和身边不少同事第一次都遇到过。原因主要是两块:一是启动前没有按要求删掉dfs.namenode.name.dir和dfs.datanode.data.dir目录下的旧数据,格式化命令会直接报错;二是端口冲突,Hadoop默认会使用9870、9000这些端口,如果电脑上已经有其他服务占用,格式化的时候会卡住。解决思路很简单,确认为什么要删除旧数据,理解它是在初始化元数据,然后把端口查一遍就好。
6.2 Spark on YARN只有1个CPU核的排查过程
热词里出现了“spark on yarn cpu只能用1个是为什么”,这个问题我在项目初期也踩过一次,值得单独写一段分析。
现象是提交Spark作业后,YARN的Web UI显示每个Executor只获得1个vCore,明明在spark-submit的配置里写了--executor-cores 2。排查下来,问题出在YARN的调度器配置上。我用的Capacity Scheduler默认配置里,yarn.scheduler.capacity.maximum-am-resource-percent的值是0.5,而单个作业能申请到的最大核心数被队列的maximum-allocation-vcores限制了。
检查yarn-site.xml发现yarn.nodemanager.resource.cpu-vcores没有设置,系统默认按物理核心数的一半来算,我的虚拟机是4核,那么NodeManager能分配的总vCore数就是2个。Driver申请1个,Executor当然只能拿到1个。解决方案是在yarn-site.xml里显式设置:
<property> <name>yarn.nodemanager.resource.cpu-vcores</name> <value>4</value> </property>然后把--executor-cores调成2,重启YARN后问题解决。这个坑的核心教训是:Spark的资源配置最终要看YARN的脸色,光在Spark侧配置不管用。
6.3 小文件问题与Spark内存模型调优
ETL阶段把数据每天写成一个小分区,时间长了会产生大量的小文件,每个小文件对应一个HDFS Block,NameNode内存里元数据量膨胀,Spark读取时的任务数也会暴增。这个项目的数据量不大,但如果你要扩展到真实业务场景,建议在ETL的最后用INSERT OVERWRITE方式按天合并分区,把每天的数据合并成3到5个大文件。
Spark内存模型这块,很多人分不清executor.memory和executor.memoryOverhead的区别。executor.memory是Executor的堆内内存,主要存放RDD数据、Shuffle缓冲区和用户代码对象;memoryOverhead是堆外内存,用来跑JVM内部逻辑、存储网络缓冲和Python进程(如果用PySpark的话)。在YARN模式下,如果executor.memory设得很大但memoryOverhead不够,Executor会因为超过Container内存上限被NodeManager直接杀掉。经验法则是memoryOverhead设置成executor.memory的10%左右,最低不少于384MB。
我第一次跑全量分析时,Executor内存设了4G,memoryOverhead默认就384M,结果在数据Shuffle阶段频繁OOM。后来把executor.memory调整到6G,memoryOverhead设1024M,同时调整了spark.sql.shuffle.partitions从默认200降为48,作业就稳定下来了。合理配置Spark内存模型,比盲目堆资源更管用。
项目做完之后的一点个人体会
从数据下载到最终看板展示,整套流程前前后后花了两周时间,中间有一半时间花在环境的反复排查上。回头看最大的收获不是代码能力,而是建立了一套排查问题的思路:先确认底层服务状态,再看中间层配置,最后才动代码逻辑,顺序颠倒只会把自己绕晕。这个项目做的时候如果时间充裕,还可以继续往两个方向扩展:一是接入实时数据,把Kafka、Flink加进来做实时看板;二是引入机器学习算法,基于用户历史购买行为做商品推荐或销量预测。底层链路已经打通了,往上加东西只是工作量问题。最后再分享一个小技巧:分析结果做完后,不要急着扔给前端展示,先自己用SQL跑几个交叉验证,再看图表讲一遍“数据背后的业务故事”,你是真懂还是在念结论,几句话就能看出来。