ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

Hadoop+SpringBoot电力数据分析系统搭建实战

Hadoop+SpringBoot电力数据分析系统搭建实战

简介:基于Hadoop与Spring Boot的电力生产数据分析系统完整项目包,面向计算机相关专业学生、毕业设计/课程设计使用者及大数据入门开发者。项目以HDFS+Yarn为存储与调度底座,结合PySpark完成数据预处理和分析,后端采用Spring Boot、MyBatis、Druid,前端使用Vue,适合在电力生产数据场景中学习大数据分析与Web应用整合。压缩包共369个文件,约9.6MB,主要包含Java后端源码、Vue页面、XML配置、Python脚本、SQL/CSV数据文件、项目截图与说明文档,结构清晰便于按模块查阅。包内含项目截图、搭建说明及文档,代码经过运行测试,下载后可根据README和文档快速还原环境;已有166人学习下载,适合用于毕设、课设或初期项目演示。

1. 从“能跑通的 Demo”到“能答辩的毕设”:一套 Hadoop+SpringBoot 电力生产数据分析系统到底在做什么

做电力、能源方向的毕业设计或课程设计时,最常见的尴尬是:Hadoop 课上学过、Spring Boot 也写过 CRUD,但真要做一个“基于 Hadoop 大数据 + Spring Boot 实现的电力生产数据分析系统”,就卡在两层框架怎么拼起来。这套系统的本质是两条线:一条是数据线,把电力生产现场产生的测点数据(电压、电流、有功功率、设备温度)落到 HDFS 上,用 MapReduce 或 Spark 之类的批处理任务算出日发电量、负载率、告警统计;另一条是业务线,Spring Boot 提供 Web 服务和权限控制,把分析结果展示给不同角色的用户。它解决的痛点是数据量大、格式杂、多人要看不同范围的数据,单靠数据库存和查是撑不住的。适合谁?适合想在毕设里真正把 Hadoop 跑起来、而不是只写 Word 的人,也适合想进大数据方向但缺一个完整项目经验的在职读者。

2. Hadoop 伪分布式环境搭建:让电力生产数据先落到 HDFS

2.1 版本选型:JDK 1.8 + Hadoop 3.3.x 与 Spring Boot 2.7.x 的搭配理由

大数据集群部署策略里最忌讳的一件事,就是为了做毕设直接买三台云主机搭集群。单机伪分布式在配置上和生产集群几乎一致,只是所有进程跑在同一台机器上,用来理解 HDFS 和 YARN 的工作机制、跑通数据链路完全够用。我一般建议用一台 Linux 虚拟机或云主机,2 核 4G 起步,装 CentOS 7 或 Ubuntu 20.04。

版本搭配是第一个要死记的参数组合:JDK 用 1.8,Hadoop 用 3.3.x,Spring Boot 用 2.7.x。为什么 Spring Boot 不用最新的 3.x?因为 Hadoop 生态里有大量依赖还停留在 javax 命名空间,Spring Boot 3.x 强制切到 jakarta,接 Hadoop 客户端会撞出一片 ClassNotFoundException。这类“Spring Boot 版本太高”引发的问题在真实项目里特别常见,后面坑的章节我会专门讲。JDK 1.8 则是 Hadoop 官方支持最稳的版本,用 17 跑会有反射警告和权限问题。

2.2 三个核心 XML 与启动命令:伪分布式其实就这几行配置

先把 Hadoop 解压到 /opt/hadoop,配好环境变量 JAVA_HOME、HADOOP_HOME,然后改四个配置文件。第一个是 core-site.xml:

<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://localhost:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/opt/hadoop_tmp</value> </property> </configuration>

fs.defaultFS 指定了 NameNode 的地址和端口,后面 Spring Boot 连 HDFS 也要用这个地址。hadoop.tmp.dir 非常关键,NameNode 的元数据、DataNode 的数据块都默认放在这个目录下,后续“二次格式化 NameNode 失败”的坑就是因为它。

接下来是 hdfs-site.xml。生产环境通常会配三副本,但伪分布式只有一个 DataNode,副本数必须显式写成 1:

<configuration> <property> <name>dfs.replication</name> <value>1</value> </property> <property> <name>dfs.namenode.name.dir</name> <value>/opt/hadoop_tmp/name</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>/opt/hadoop_tmp/data</value> </property> </configuration>

再配 yarn-site.xml 和 mapred-site.xml。YARN 负责资源调度,MapReduce 任务跑在它上面,端口和资源参数可以参考下面这张表:

参数推荐值说明
yarn.nodemanager.aux-servicesmapreduce_shuffleNodeManager 的辅助服务,MapReduce 依赖它
yarn.nodemanager.resource.memory-mb2048单机可用的总内存,4G 内存的机器别给太多
yarn.scheduler.minimum-allocation-mb256单个容器最小内存,设太大浪费资源
mapreduce.framework.nameyarn让 MapReduce 任务提交到 YARN 执行

yarn-site.xml 里把上面前三个参数写进去,mapred-site.xml 里写 mapreduce.framework.name。配置完成后,如果你的机器内存只有 4G,建议把 yarn.nodemanager.resource.memory-mb 降到 1536,否则后面跑分析任务时容器会因为内存不足被直接 kill 掉。这一步很多人忽略,等 YARN 日志里出现 Container killed by the ResourceManager 才回头调参。

2.3 上传一份样例 CSV 验证 HDFS 读写

配置写得再完整,不启动验证就等于零。第一次启动 HDFS 需要先格式化 NameNode,生成元数据目录。注意格式化命令只需要在第一次运行,后续每次启动都不需要再执行:

cd /opt/hadoop bin/hdfs namenode -format sbin/start-dfs.sh sbin/start-yarn.sh jps

格式化时如果终端输出 “Successfully formatted” 就是正常。start-dfs.sh 会拉起 NameNode、DataNode 和 SecondaryNameNode,start-yarn.sh 拉起 ResourceManager 和 NodeManager。jps 是 Java 进程查看命令,能看到 5 个进程才说明伪分布式起来了。每次开机后只需要跑 start-dfs.sh 和 start-yarn.sh,不要重复格式化,除非你想把整个 Hadoop 的数据清掉重来。

启动后赶紧做一次完整的 HDFS 读写验证,避免后面 Spring Boot 连不上才发现问题。把一份电力测点 CSV 放上去再读出来:

hdfs dfs -mkdir -p /data/raw hdfs dfs -put /opt/samples/station_20250601.csv /data/raw/ hdfs dfs -ls /data/raw hdfs dfs -cat /data/raw/station_20250601.csv | head -20

能正常列出文件并打印出 CSV 内容,说明 HDFS 这一层已经通了。到这一步,电力生产数据的落盘问题就解决了,下一步要解决的是 Spring Boot 怎么读写这些数据。

3. Spring Boot 接入 HDFS:把文件读写与分析任务封装成 REST 服务

3.1 引入 hadoop-client 依赖,避开 Spring Boot 版本太高带来的命名空间冲突

Spring Boot 接入 HDFS 最常见的做法是引入 hadoop-client 依赖,通过官方 FileSystem API 操作 HDFS,而不需要自己写 Socket 通信。Maven 依赖写法如下:

<dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>3.3.6</version> </dependency>

这个依赖会带进来 Guava、commons-logging 等一堆传递依赖,而 Spring Boot 的 spring-boot-starter-parent 也会管理 Guava 版本,两者容易出现冲突,典型报错是 NoSuchMethodError: com.google.common.base.Preconditions.checkArgument。解决方式是在 hadoop-client 的 dependency 里把 Guava 排除掉,再显式引入一个固定版本:

<dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>3.3.6</version> <exclusions> <exclusion> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>27.0.1-jre</version> </dependency>

这里把 Guava 固定在 27.0.1-jre,是 Hadoop 3.3.x 官方测试过的兼容版本。需要强调一句:Spring Boot 版本太高是这里最大的坑,如果你用 3.x,还要面对 javax 转 jakarta 的问题,hadoop-client 里很多类直接 import javax.*,编译期就过不去。所以项目起步阶段直接把 Spring Boot 控制在 2.7.x,能省掉一整类莫名其妙的问题。

3.2 用 FileSystem API 封装 HDFS 操作:上传、列表、下载可以直连

拿到依赖之后,需要一个配置类把 Hadoop 的 Configuration 和 FileSystem 准备好。伪分布式部署时,客户端和 Hadoop 在同一台机器上,直接用 fs.defaultFS 就能连接:

@Configuration public class HadoopConfig { @Bean public Configuration hadoopConfiguration() { Configuration conf = new Configuration(); conf.set("fs.defaultFS", "hdfs://localhost:9000"); conf.setBoolean("fs.hdfs.impl.disable.cache", true); return conf; } @Bean public FileSystem fileSystem() { try { return FileSystem.get(hadoopConfiguration()); } catch (IOException e) { throw new RuntimeException("HDFS FileSystem 初始化失败", e); } } }

fs.hdfs.impl.disable.cache 这个参数很多人不知道,它的作用是禁用 HDFS 客户端的进程级缓存。如果不设,同一个 JVM 里多个用户共用同一个 FileSystem 实例,会出现 A 用户写文件、B 用户读到 A 的临时状态的诡异问题。在毕设系统里,后续要做行列权限,多用户同时访问是常态,所以这个开关建议显式设上。

封装 FileSystem 之后,写一个 HdfsService,把常用的上传、列表、读取操作做薄。这是整个系统里复用率最高的一个服务类:

@Service public class HdfsService { @Resource private FileSystem fileSystem; public void upload(String path, InputStream input) throws IOException { Path hdfsPath = new Path(path); try (FSDataOutputStream out = fileSystem.create(hdfsPath, true)) { IOUtils.copyBytes(input, out, 4096, true); } } public List<String> listFiles(String dir) throws IOException { Path hdfsDir = new Path(dir); FileStatus[] statuses = fileSystem.listStatus(hdfsDir); List<String> paths = new ArrayList<>(); for (FileStatus status : statuses) { paths.add(status.getPath().toString()); } return paths; } public String readText(String path) throws IOException { Path hdfsPath = new Path(path); try (FSDataInputStream in = fileSystem.open(hdfsPath)) { return IOUtils.toString(in, StandardCharsets.UTF_8); } } }

create(hdfsPath, true) 里的第二个参数是覆盖标记,为 true 表示同名文件直接覆盖。这在重新上传同一天的电力数据时很有用,免得手工先删旧文件。IOUtils.copyBytes 的第三个参数是缓冲区大小,伪分布式环境给 4096 字节足够;如果你在跨机房读大文件,可以调到 65536,但本机跑没差别。readText 里用 UTF-8 读,对应后面“中文乱码”那个坑,源文件如果不是 UTF-8 编码,这里会看到一串问号。

3.3 对外接口与原理解释:REST Controller 与前端打包联调

Spring Boot 的价值是给 HDFS 操作套上一层 HTTP 接口,前端页面只跟 Controller 打交道,不直接碰 Hadoop API。这里的 Controller 要做得薄,只做参数校验和调用转发:

@RestController @RequestMapping("/api/hdfs") public class HdfsController { @Resource private HdfsService hdfsService; @PostMapping("/upload") public String upload(@RequestParam("file") MultipartFile file, @RequestParam("dir") String dir) throws IOException { String path = dir + "/" + file.getOriginalFilename(); hdfsService.upload(path, file.getInputStream()); return path; } @GetMapping("/list") public List<String> list(@RequestParam("dir") String dir) throws IOException { return hdfsService.listFiles(dir); } }

上传接口的逻辑是先拼 HDFS 路径,再把前端传上来的文件流写入 HDFS。dir 参数由前端指定,注意做好校验,不能接受 .. 之类的上跳路径,否则用户能往任意目录写文件。这是权限设计的一部分,后面会把数据权限下沉到用户维度,这里先留一个口子。

联调时有个很实用的做法:把前端 Vue 项目 build 出来的 dist 目录整个拷贝到 src/main/resources/static 下,再打包 Spring Boot。这样外网只需要开放一个 8080 端口,静态页面和 REST 接口由同一个 jar 服务,不用额外部署 Nginx。做法是执行 npm run build,然后把 dist 里的内容复制到 static 目录,重新 mvn package。前后端分离开发、合并部署的模式,在企业内部系统里很常见,毕设答辩时也能少开一个端口、少一台服务器。

4. 电力生产数据分析模块:从原始测点到日发电量与设备告警

4.1 原始数据建模:时间序列字段、文件切分与存储策略

电力生产数据是典型的时间序列数据,一个火电厂或者风电场每天会产生几十万条测点记录。建模时不要想着直接存进 MySQL,而是按照“原始层 → 汇总层 → 应用层”的思路落到 HDFS。原始层按天分目录,路径形如 /data/raw/2025/06/01/,每天一个或多个 CSV 文件。字段设计参考下表:

字段名类型说明
record_timestring采集时间,格式 yyyy-MM-dd HH:mm:ss
station_idstring电厂或电站编号
unit_idstring机组编号
active_powerdouble有功功率,单位 MW
reactive_powerdouble无功功率,单位 MVar
voltagedouble母线电压,单位 kV
currentdouble电流,单位 A
equipment_tempdouble设备温度,单位 ℃
fault_flagint告警标记,0 正常,1 告警

文件切分有个经验:按 station_id 分目录,不要把所有电站堆在一个大文件里。这样后续做行权限拦截时,可以直接按目录范围控制用户可见数据,扫描成本和权限逻辑都更简单。每 50 万行左右切一个文件,HDFS 的块大小默认 128M,CSV 能压缩到接近块大小,既不浪费空间也不产生大量小文件。

4.2 用 MapReduce 跑日发电量统计,再把结果写回 HDFS

分析模块的核心指标是日发电量。因为数据已经按天分目录,只要把当天 CSV 里有功功率累加起来,再乘以采集时间间隔,就是该站当天的发电量。这里 MapReduce 的 Mapper 负责解析每行记录,Reducer 负责分组求和:

public class DailyPowerMapper extends Mapper<LongWritable, Text, Text, DoubleWritable> { private Text outKey = new Text(); private DoubleWritable outValue = new DoubleWritable(); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line = value.toString(); String[] fields = line.split(","); if (fields.length < 4) { return; } String date = fields[0].substring(0, 10); String stationId = fields[1]; outKey.set(date + "_" + stationId); outValue.set(Double.parseDouble(fields[3])); context.write(outKey, outValue); } }

Mapper 的输入是 HDFS 文件的一行文本,输出 key 是“日期_电站编号”,value 是有功功率。这里有个容易翻车的点:CSV 字段顺序如果和建模表不一致,取 fields[3] 就不是有功功率。我一般会先在代码里加一个字段映射的配置,而不是硬编码下标,否则上游调度系统哪天调了列顺序,分析结果就悄悄错了。Reducer 侧只需要把同一 key 的数据累加,再乘以采集间隔(假设 5 分钟一条,那一天的采集次数是 288):

public class DailyPowerReducer extends Reducer<Text, DoubleWritable, Text, Text> { @Override protected void reduce(Text key, Iterable<DoubleWritable> values, Context context) throws IOException, InterruptedException { double sum = 0; for (DoubleWritable val : values) { sum += val.get(); } double powerMwh = sum / 12.0; context.write(key, new Text(String.format("%.2f", powerMwh))); } }

这里 sum 的单位是 MW × 条数,除以 12 是因为 5 分钟一条、一小时 12 条,结果就是 MWh。如果采集频率变了,这个分母要跟着变,否则发电量会偏大或偏小。把这段逻辑做成配置文件里的一个参数,比写死在 Reducer 里好得多。MapReduce 计算结果会写回 HDFS 的 /data/result 目录,Spring Boot 端只需要用上一章的 readText 方法把结果文件读出来,转成 JSON 给前端画曲线图就行。

4.3 定时调度与行列权限:Quartz 触发分析,注解拦截数据权限

分析任务不能每天靠人手工点按钮,要定时调度。常见做法是在 Spring Boot 里集成 Quartz,每天早上 0 点半触发当天的汇总计算。调度任务类的核心逻辑就是提交 MapReduce,然后睡上一段时间轮询结果文件是否生成:

@Component public class AnalysisJob extends QuartzJobBean { @Resource private HdfsService hdfsService; @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { String today = LocalDate.now().toString(); String resultPath = "/data/result/" + today + "/summary.csv"; try { Process process = Runtime.getRuntime().exec( "/opt/hadoop/bin/hadoop jar /opt/jars/power-analysis.jar DailyPowerJob " + today); process.waitFor(); String result = hdfsService.readText(resultPath); // 把 result 写入 MySQL 供前端快速查询 } catch (Exception e) { throw new JobExecutionException("分析任务失败", e); } } }

调度任务的写法不复杂,但要考虑两点:一是任务执行时间和数据就绪时间的关系,如果电厂的数据凌晨 1 点才传完,调度时间要往后挪;二是任务失败的重试机制,Quartz 里配置 misfire 策略时,我给单机项目推荐 withMisfireInstructionDoNothing,避免补跑堆积导致 YARN 资源耗尽。

权限设计是这个系统的门面。参考大数据行列权限设计开源的常见做法,行权限管“看得到哪些电站”,列权限管“每列能不能看”。实现方式是自定义一个 @DataPermission 注解,打在查询接口上,由 AOP 拦截器根据当前登录用户的 station_id 自动拼接过滤条件:

@Override public List<Map<String, Object>> queryDailySummary(String userId) { User user = userMapper.selectById(userId); String sql = "SELECT * FROM daily_summary WHERE 1=1"; if (StringUtils.hasText(user.getStationScope())) { sql += " AND station_id IN (" + user.getStationScope() + ")"; } // 列权限:普通用户隐藏设备温度字段 if ("operator".equals(user.getRole())) { sql = sql.replace("*", "record_time, station_id, active_power"); } return jdbcTemplate.queryForList(sql); }

行权限用 IN 条件限制电站范围,列权限用投影字段控制可见列。这个设计的好处是底层 SQL 完全可控,不引入额外框架,毕设答辩时也容易讲清楚。注意这里的 SQL 是动态拼接的,参数必须来自后端解析出的用户信息,绝不能直接用前端传来的字符串,否则就是 SQL 注入。

5. 项目搭建避坑:从代码能编译到系统能跑的 5 个高频问题

5.1 二次格式化 NameNode 失败:先清空 name/data 目录再格式化

现象是第一天跑得好好的,第二天想清空数据重新来,执行 hdfs namenode -format 后控制台报错,说 NameNode 已经在运行或者 layout version 不一致,起服务时 DataNode 一直连不上。原因是格式化只清空了元数据,可 DataNode 的 data 目录里还残留着旧的块信息,两者一比对就对不上。解决是先停掉所有服务,删除 hadoop.tmp.dir 下的整个目录,再重新格式化。命令顺序是:

sbin/stop-all.sh rm -rf /opt/hadoop_tmp bin/hdfs namenode -format sbin/start-dfs.sh sbin/start-yarn.sh

后期如果你只想清某个业务数据,千万别用重新格式化这招,直接用 hdfs dfs -rm -r /data/raw 删除对应目录即可。把格式化当成一个危险操作,能不动就不动。

5.2 Spring Boot 版本太高导致 Guava 和 javax 命名空间报错

现象是 mvn package 时编译通过,但启动 Spring Boot 后一调用 HDFS 接口就抛 NoSuchMethodError 或 ClassNotFoundException,堆栈里能看到 com.google.common 或 javax.servlet 的字样。原因有两层:一是 Spring Boot 的依赖管理和 hadoop-client 传递依赖的 Guava 版本不一致;二是 Spring Boot 3.x 全面迁移 jakarta 命名空间,而 Hadoop 3.3.x 的客户端组件仍依赖 javax。解决就是踩住两点:Spring Boot 用 2.7.x 而不是 3.x;hadoop-client 里排除 Guava 并显式引入兼容版本。这两个操作落实了,九成以上的依赖冲突会消失。剩下那一成,用 mvn dependency:tree 查具体是哪个依赖带来的冲突,再单独排除。

5.3 伪分布式下数据副本不足:把 dfs.replication 设为 1

现象是 HDFS 管理页面显示块副本数不完整,上传文件后 DataNode 日志里反复出现 “There are 1 datanode(s) running and 1 node(s) are excluded in this operation”,文件状态一直处于 under-replicated。原因是 Hadoop 默认副本数是 3,但伪分布式只有一个 DataNode,无法满足副本策略,于是 NameNode 一直尝试复制却永远无法成功。解决是在 hdfs-site.xml 里把 dfs.replication 显式设为 1 并重启 HDFS 服务。这个参数属于“不配置就永远在报警”的类型,做完这一步,HDFS 页面上的健康状态才会变成绿色。

5.4 YARN 容器内存不够:调小 minimum-allocation 与 map 内存

现象是提交 MapReduce 任务后,任务跑几秒就失败,YARN 日志显示 Container killed by the ResourceManager,或者物理内存超出限制。原因是 2 核 4G 的机器上,YARN 默认给 NodeManager 分配的资源偏大,而 MapReduce 默认的 map 容器内存是 1G,多个 map 同时跑直接把机器内存挤爆。解决是调低 yarn-site.xml 里的三个参数,给一份我常用的配置:

<property> <name>yarn.nodemanager.resource.memory-mb</name> <value>2048</value> </property> <property> <name>yarn.scheduler.minimum-allocation-mb</name> <value>256</value> </property> <property> <name>yarn.scheduler.maximum-allocation-mb</name> <value>1024</value> </property>

同时把 mapreduce.map.memory.mb 和 mapreduce.reduce.memory.mb 设为 512。这个调参组合能够让单机同时跑 3 到 4 个容器而不触发 OOM。日志里如果还有 “Physical memory usage is very high”,继续往下调容器内存,别硬扛。

5.5 中文乱码:统一编码比事后转码省事

现象是前端页面上设备名称、告警信息全部显示成问号,Spring Boot 日志里读出 HDFS 文件时中文乱成一团。原因是现场导出的 CSV 常常是 GBK 编码,而 Hadoop 文件系统读写默认按 UTF-8 处理,两端对不上。解决分两步:一是在数据导入环节用脚本统一转成 UTF-8 再上传 HDFS,二是在 Mapper 里如果实在要直接读 GBK 文件,用下面的方式指定编码:

import java.io.InputStreamReader; InputStreamReader reader = new InputStreamReader( ((FileSplit) context.getInputSplit()).getPath().getFileSystem(context.getConfiguration()) .open(((FileSplit) context.getInputSplit()).getPath()), "GBK");

但我的经验是转码环节越靠前越好,最好数据从电力系统导出时就用 Python 脚本清洗成 UTF-8 标准格式。编码的坑属于“事后查半天、事前一分钟”的典型,不要在映射和解析阶段硬扛编码问题。

6. 跑起来以后:验证指标、调 YARN 参数、再往实时分析走一步

6.1 先验证闭环,再谈功能扩展

系统能启动、页面能打开,只说明没崩,不代表数据链路是对的。我每次做完这类系统,都会按下面这个顺序做一次闭环验证:第一,用 curl 调用上传接口,传一个构造的 5 分钟间隔 CSV;第二,触发分析任务,等结果文件生成;第三,调查询接口,核对日发电量是否等于手工计算的期望值;第四,用两个不同 station_id 的用户登录,确认行权限确实过滤了数据。验证命令可以用这一条串起来:

curl -F "file=@station_test.csv" -F "dir=/data/raw/2025/06/01" http://localhost:8080/api/hdfs/upload curl http://localhost:8080/api/daily/summary?date=2025-06-01

如果第二个接口返回的发电量和你手工算的对不上,优先怀疑 Mapper 里的列下标和 Reducer 里的采样间隔分母,而不是怀疑 Hadoop 算错。大数据系统的调试逻辑是“先怀疑自己的业务代码,再怀疑框架”,这条血泪经验帮我少走了很多弯路。

6.2 调优与下一步:批处理往 Spark、Flink 迁移时哪些思路不变

调优不用一次做满,优先把第 5 章的内存参数稳定住,然后根据任务耗时决定要不要动并行度。任务慢的时候先看 YARN 界面上的 map 数量,如果文件块多而 map 少,可以在提交任务时加 -D mapreduce.job.maps=4 提高并行度,但单机 CPU 核数不够时,这个参数反而有害。调优的原则是:先内存,再并行度,最后才考虑换框架。

这个系统的批处理路径如果将来要升级,把 MapReduce 换成 Spark 或 Spring Boot 整合 Flink 时,思路是不变的:数据仍然在 HDFS,分析逻辑仍然是从原始层算到汇总层,Web 端仍然只读结果。变的只是计算引擎的 API 和提交方式。真正要提前做好的设计是原始数据的目录结构和字段规范,只要这一层稳定,后面的计算引擎换起来都很快。

我做第一个电力数据分析项目时,最吃亏的地方是把大部分时间花在代码编译上,忽略了对数据的理解。等到答辩时被问“为什么用 HDFS 而不用 MySQL”才发现,自己根本讲不清数据量和查询模式之间的取舍。后来我习惯先把数据想象成一条河流,先让水流动起来、验证每个环节,再回头优化细节。希望帮到你。

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

RELATED READING

延伸阅读

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