简介:这份资源是基于Hadoop的疾病信息统计平台完整项目源码,面向具备Java与大数据基础、希望实践分布式数据处理的学习者与开发者,可用于公共卫生数据分析、疾病预防研究等场景的二次开发与课程设计参考。压缩包共41个文件,约10.87MB,以25个Java源文件为核心业务实现,辅以6个XML配置、2个properties与1个yml完成环境与依赖管理,另含2个jar、1个arff数据集及mvnw、cmd等构建脚本,整体结构清晰,便于按模块阅读与调试。项目围绕HDFS分布式存储与MapReduce并行计算展开,涉及数据采集、存储、处理、分析及可视化等环节,并可能集成Hive、HBase、YARN等生态组件,帮助读者理解大规模疾病数据的容错存储与高效统计流程。目前已有84人学习下载,适合作为大数据入门到进阶的实战参考,快速掌握Hadoop项目搭建与Java开发要点。
1. 从一份「疾病信息统计平台」说起:Hadoop 到底在这里扛了什么活
如果你手上拿到一个叫「基于 hadoop 的疾病信息统计平台.zip」的课程设计或二次开发项目,第一反应大概率是:这玩意儿到底统计什么、Hadoop 在里面是真干活还是只挂了个名。我见过太多所谓大数据项目,Hadoop 只是被写进了标题,实际数据量连单机 MySQL 都喂不饱。但疾病信息统计这个场景不一样——它天然具备「多来源、字段杂、时间跨度长、需要按地区/病种/年龄段多维聚合」的特征,当数据从几千条涨到千万条级别,单表 group by 就会开始让你等咖啡。Hadoop 在这里的核心价值不是「存」,而是把清洗、聚合、统计这类批处理任务拆到多台机器上并行跑,HDFS 负责把原始疾病上报数据切片存储,MapReduce 或 Hive 负责把「按病种+地区+月份统计病例数」这种查询翻译成分布式作业。这篇文章面向的是拿到类似项目、想在自己机器上跑通并理解每一层在干什么的人,从伪分布式搭建一路讲到统计指标落地和踩坑排查,不堆概念,只讲能复现的路径。
2. 疾病信息统计平台的分层设计与 Hadoop 选型理由
2.1 为什么这个场景适合 HDFS + Hive 而不是直接上 MySQL
疾病信息统计平台的数据流通常是这样的:基层上报的病例记录(含患者编号、性别、年龄、病种编码、所属地区、确诊日期等字段)以 CSV 或日志形式落地,每天或每周增量追加。这类数据的查询模式有几个特点:写多读少、按时间分区扫描、聚合维度固定但组合多。MySQL 在单表超过千万行后,即使加了索引,做「按地区+病种+季度」的三维聚合也会明显变慢,而且横向扩展要靠分库分表,运维成本陡增。
HDFS 的块存储机制天然适合这种「一次写入、多次读取」的批处理场景,副本因子保证数据不丢,NameNode 管元数据、DataNode 存实际块。上层用 Hive 建外部表映射到 HDFS 目录,用类 SQL 的 HiveQL 写统计逻辑,底层自动翻译成 MapReduce 或 Tez 作业。对于疾病统计这种「T+1 出报表」的需求,延迟完全可以接受。选型时我一般会跟人说清楚:如果你的数据量在百万级以下、查询要求秒级响应,别硬上 Hadoop,PostgreSQL 加物化视图更省事;一旦到了千万级且要跑周期性全量聚合,Hadoop 生态的性价比才体现出来。
2.2 伪分布式与完全分布式:课程设计和真实落地的分界线
热词里「hadoop 伪分布式搭建」出现频率极高,这不是偶然。绝大多数人第一次接触 Hadoop 都是从伪分布式开始的——在一台机器上把 NameNode、DataNode、ResourceManager、NodeManager 全部跑起来,用不同进程模拟集群角色。它的好处是配置路径和完全分布式几乎一致,调通了伪分布式,扩展到三节点集群只是改几个 XML 里的主机名。
完全分布式才是生产形态:一台 Master 跑 NameNode 和 ResourceManager,多台 Slave 跑 DataNode 和 NodeManager,通过 SSH 免密互通。疾病信息统计平台如果只是课程设计,伪分布式足够展示完整链路;如果要模拟真实上报量,建议至少搭三节点,把数据分片和副本机制真正跑起来。下面这张表是我整理的两者在关键配置上的差异,照着改不会迷路。
| 配置项 | 伪分布式 | 完全分布式(3 节点示例) |
|---|---|---|
| fs.defaultFS | hdfs://localhost:9000 | hdfs://master:9000 |
| dfs.replication | 1 | 3 |
| yarn.resourcemanager.hostname | localhost | master |
| slaves 文件 | 不配置 | slave1、slave2 |
| SSH 免密 | 本机免密 | master 到所有节点免密 |
提示:伪分布式下 dfs.replication 必须设为 1,否则会因为副本数超过 DataNode 数量导致文件一直处于 under-replicated 状态,写入卡住。
2.3 疾病数据从上报到入库的字段映射设计
在动手写统计逻辑之前,得先把原始数据的字段结构定下来。我一般会要求上报数据至少包含以下列,缺一不可,否则后续聚合维度会残缺:
- case_id:病例唯一编号,字符串,用于去重
- gender:性别,枚举值 M/F
- age:年龄,整数,后续分年龄段用
- disease_code:病种编码,遵循 ICD 编码规范
- region_code:地区行政编码,用于按区域聚合
- confirm_date:确诊日期,格式 yyyy-MM-dd,用于按时间分区
Hive 建表时把 confirm_date 作为分区字段,这样按月份统计时只扫描对应分区目录,避免全表扫描。分区字段不能出现在表定义的数据列里,这是新手最容易犯的错——建表时把 confirm_date 同时写进列定义和 partitioned by,Hive 会直接报错。
3. 从零搭一套能跑疾病统计的 Hadoop 环境
3.1 伪分布式最小安装:JDK、SSH 与 Hadoop 解压配置
先确认基础环境。我习惯用 Ubuntu 20.04 或 CentOS 7,内存至少 4G,因为 NameNode 加 ResourceManager 一起跑起来很吃内存。第一步装 JDK,Hadoop 3.x 要求 JDK 8 或 11,别用更高的版本,否则会有反射相关的报错。
# 安装 JDK 8 sudo apt update sudo apt install openjdk-8-jdk -y java -version # 配置 SSH 本机免密,伪分布式也需要 ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys ssh localhost # 验证免密登录,能进去就说明 OK上面这段的逻辑是:Hadoop 的启动脚本需要通过 SSH 到目标节点执行命令,即使是本机也要走这个流程。ssh-keygen 生成密钥对,-P '' 表示空密码,authorized_keys 权限必须是 600,否则 SSH 会拒绝使用。验证时如果提示要输密码,说明免密没配好,后面 start-dfs.sh 会卡在输入密码上。
接下来解压 Hadoop 并配置 hadoop_home 环境变量。热词里「配置 hadoop_home 环境变量」是高频问题,很多人解压完直接跑命令,结果 hadoop 命令找不到。
# 解压到 /usr/local sudo tar -zxvf hadoop-3.3.6.tar.gz -C /usr/local/ sudo mv /usr/local/hadoop-3.3.6 /usr/local/hadoop # 编辑 ~/.bashrc,追加以下内容 export HADOOP_HOME=/usr/local/hadoop export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 source ~/.bashrc hadoop version # 能输出版本号说明环境变量生效HADOOP_HOME 指向解压目录,PATH 里加上 bin 和 sbin,前者放 hadoop、hdfs 等客户端命令,后者放 start-dfs.sh、start-yarn.sh 等启动脚本。JAVA_HOME 必须显式导出,因为 Hadoop 的启动脚本会读这个变量去找 java 可执行文件。如果 hadoop version 报「JAVA_HOME is not set」,检查路径是否写对,用which java反查真实路径。
3.2 core-site.xml、hdfs-site.xml、yarn-site.xml 三个必改文件
Hadoop 的配置文件都在$HADOOP_HOME/etc/hadoop/下,伪分布式只需要改三个核心文件。我见过有人改了 mapred-site.xml 却忘了 core-site.xml,结果 NameNode 根本起不来。
<!-- core-site.xml:指定 HDFS 的默认文件系统地址 --> <configuration> <property> <name>fs.defaultFS</name> <value>hdfs://localhost:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/usr/local/hadoop/tmp</value> </property> </configuration>fs.defaultFS 告诉客户端默认连哪个 NameNode,9000 是社区版默认端口。hadoop.tmp.dir 是 Hadoop 运行时临时目录,默认在 /tmp 下,系统重启可能被清空导致元数据丢失,所以一定要改到一个持久化路径。
<!-- hdfs-site.xml:副本数和 NameNode/DataNode 数据目录 --> <configuration> <property> <name>dfs.replication</name> <value>1</value> </property> <property> <name>dfs.namenode.name.dir</name> <value>/usr/local/hadoop/data/namenode</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>/usr/local/hadoop/data/datanode</value> </property> </configuration>dfs.replication 在伪分布式下必须为 1,原因前面说过。name.dir 和 data.dir 分开存放,方便出问题时单独清理某一个而不影响另一个。这两个目录不需要手动创建,格式化时会自动生成。
<!-- yarn-site.xml:ResourceManager 地址和 NodeManager 辅助服务 --> <configuration> <property> <name>yarn.resourcemanager.hostname</name> <value>localhost</value> </property> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> </configuration>yarn.nodemanager.aux-services 必须设为 mapreduce_shuffle,这是 MapReduce 作业在 NodeManager 上做 shuffle 阶段的前提,漏了这行作业会一直卡在 map 100% reduce 0%。
3.3 格式化与启动:一条命令验证 HDFS 和 YARN 是否都活着
配置改完后,第一次启动前必须格式化 NameNode,这个操作只能做一次,重复格式化会导致 DataNode 的 clusterID 和 NameNode 不一致,DataNode 拒绝启动。
# 格式化 NameNode,只做一次 hdfs namenode -format # 启动 HDFS 和 YARN start-dfs.sh start-yarn.sh # 验证进程 jps # 应该看到 NameNode、DataNode、SecondaryNameNode、ResourceManager、NodeManager # 验证 HDFS 可用 hdfs dfs -mkdir -p /disease/input hdfs dfs -ls /jps 是 JDK 自带的进程查看工具,五个进程缺一不可。如果 DataNode 没起来,去$HADOOP_HOME/logs/下看 datanode 的日志,最常见的原因是重复格式化或者 data 目录权限不对。hdfs dfs -mkdir 能成功建目录,说明 NameNode 和 DataNode 通信正常。到这一步,HDFS 和 YARN 都活了,可以开始灌数据。
4. 疾病数据清洗与 Hive 统计指标落地
4.1 原始 CSV 上传 HDFS 与 Hive 外部表建立
假设你手上有disease_raw.csv,字段顺序是 case_id, gender, age, disease_code, region_code, confirm_date。先上传到 HDFS 的输入目录,再建 Hive 外部表映射过去。用外部表的好处是删表不会删数据,原始文件还在 HDFS 上,方便反复调试。
# 上传原始数据到 HDFS hdfs dfs -put disease_raw.csv /disease/input/ # 进入 Hive 客户端 hive-- 建外部表,按 confirm_date 分区 CREATE EXTERNAL TABLE disease_raw ( case_id STRING, gender STRING, age INT, disease_code STRING, region_code STRING ) PARTITIONED BY (confirm_date STRING) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' STORED AS TEXTFILE LOCATION '/disease/input'; -- 加载分区数据,假设文件里已有日期列,这里手动指定分区 LOAD DATA INPATH '/disease/input/disease_raw.csv' INTO TABLE disease_raw PARTITION (confirm_date='2024-01-01');建表时注意:confirm_date 只出现在 PARTITIONED BY 里,不能同时写进列定义。ROW FORMAT 指定逗号分隔,如果原始文件有表头,加载后第一行会变成脏数据,需要在清洗阶段过滤。LOAD DATA 是移动操作,执行后 HDFS 上的源文件会被移到 Hive 的仓库目录,如果想保留源文件用LOAD DATA LOCAL INPATH从本地加载。
4.2 用 HiveQL 写「按病种+地区+月份」三维聚合
统计指标的核心是三维聚合:每个病种在每个地区每个月的病例数。这是疾病信息统计平台最基础的报表,也是最能体现 Hadoop 并行优势的查询。
-- 按病种、地区、月份统计病例数 INSERT OVERWRITE TABLE disease_stat_monthly SELECT disease_code, region_code, substr(confirm_date, 1, 7) AS stat_month, COUNT(DISTINCT case_id) AS case_count, SUM(CASE WHEN gender = 'M' THEN 1 ELSE 0 END) AS male_count, SUM(CASE WHEN gender = 'F' THEN 1 ELSE 0 END) AS female_count, AVG(age) AS avg_age FROM disease_raw WHERE confirm_date IS NOT NULL GROUP BY disease_code, region_code, substr(confirm_date, 1, 7);这段 HiveQL 的逻辑:substr 截取日期前 7 位得到月份,COUNT(DISTINCT case_id) 保证同一病例不重复计数,两个 SUM(CASE WHEN) 分别统计男女病例数,AVG(age) 算平均年龄。GROUP BY 的三个维度决定了 MapReduce 的 shuffle key,Hive 会自动根据数据量决定用多少 reducer。如果某个病种数据倾斜严重(比如流感病例远多于其他病种),会出现某个 reducer 跑得特别慢,这时候需要开hive.groupby.skewindata=true让 Hive 做两阶段聚合。
4.3 统计结果导出与报表对接方式
统计结果表建好后,导出方式取决于下游怎么用。如果是给 BI 工具做可视化,导出成 CSV 放到指定目录;如果是给接口服务查,可以同步到 MySQL 或 HBase。
# 方式一:直接导出 HDFS 文件 hdfs dfs -getmerge /user/hive/warehouse/disease_stat_monthly /tmp/stat_output.csv # 方式二:通过 Hive 导出到本地 hive -e "SELECT * FROM disease_stat_monthly" > /tmp/stat_output.csvgetmerge 会把 HDFS 目录下所有 part 文件合并成一个本地文件,适合结果集不大的场景。如果结果超过几百万行,建议用 Sqoop 导出到关系库,或者直接把 Hive 表映射到 Spark SQL 做进一步处理。我一般会在导出后做一次行数校验,用wc -l对比 Hive 里SELECT COUNT(*)的结果,防止有 part 文件遗漏。
5. 疾病统计平台跑起来后最容易翻车的几个地方
5.1 DataNode 启动失败:重复格式化留下的后遗症
现象:jps 里看不到 DataNode 进程,NameNode 正常但 HDFS 写入报「could only be replicated to 0 nodes」。
原因:多次执行hdfs namenode -format,每次格式化会生成新的 clusterID,而 DataNode 的 VERSION 文件里还是旧的 clusterID,两者不匹配,DataNode 拒绝加入。
解决:停掉所有进程,删除 NameNode 和 DataNode 的 data 目录(就是 hdfs-site.xml 里配的那两个路径),重新格式化一次,再启动。记住格式化只做一次,后面改配置重启不需要再格式化。
5.2 Hive 查询报「Vertex failed」:YARN 内存不够
现象:HiveQL 提交后卡在 map 阶段,日志里出现 Container 被 kill,提示「Container killed on request. Exit code is 137」。
原因:YARN 默认给每个 Container 分配的内存是 1024MB,疾病数据做 group by 时如果某个 key 的数据量大,map 端内存不够被 NodeManager 杀掉。137 就是 128+9,表示进程被 SIGKILL。
解决:调大yarn.scheduler.maximum-allocation-mb和mapreduce.map.memory.mb,比如都设成 2048。同时检查mapreduce.map.java.opts里的堆大小,一般是 memory.mb 的 0.8 倍。改完重启 YARN。
5.3 中文病种名称乱码:编码链路没统一
现象:Hive 查询结果里病种名称显示成问号或方块,原始 CSV 在 Windows 上打开正常。
原因:Windows 默认 GBK 编码,Linux 和 Hive 默认 UTF-8,上传时没有转码,Hive 按 UTF-8 解析 GBK 字节流就乱了。
解决:上传前用iconv -f GBK -t UTF-8 disease_raw.csv > disease_raw_utf8.csv转码,再上传。建表时确认serialization.encoding是 UTF-8。如果已经导入,只能删分区重新加载。
5.4 统计结果对不上:DISTINCT 用错位置
现象:按病种统计的总病例数比按地区统计的总和少,或者男女病例数加起来不等于总数。
原因:COUNT(DISTINCT case_id) 在多个维度 group by 时,如果同一病例在不同维度下重复出现,去重逻辑会互相干扰。另外 gender 字段如果有空值或异常值(比如 'Unknown'),CASE WHEN 只匹配 M/F,异常值两边都不计入,导致男女之和小于总数。
解决:先做数据质量检查,SELECT gender, COUNT(*) FROM disease_raw GROUP BY gender看有没有异常枚举值。统计总数用 COUNT(1) 或 COUNT(case_id),去重场景单独处理。男女计数加一个 ELSE 分支兜底,或者把异常值单独统计出来。
5.5 小文件过多拖慢查询:每个分区一堆 part 文件
现象:Hive 查询越来越慢,hdfs dfs -ls /disease/input看到每个分区下几十个小文件。
原因:每次 LOAD DATA 或 INSERT 都会生成新的 part 文件,如果按天增量导入,一个月下来就是几十个文件。HDFS 和 Hive 处理大量小文件的效率很低,每个文件对应一个 map 任务,启动开销远大于实际计算。
解决:定期做小文件合并,用ALTER TABLE disease_raw PARTITION (confirm_date='2024-01-01') CONCATENATE;或者重写一遍INSERT OVERWRITE把数据合并到少量文件。更彻底的做法是在导入前用hadoop archive打包,或者设置hive.merge.mapfiles=true让 Hive 在作业结束后自动合并。
6. 让统计平台从「能跑」到「敢用」的两个进阶技巧
6.1 用分区裁剪和桶表把查询时间压下来
疾病数据按天分区后,查询时一定要在 WHERE 里带上分区字段,否则 Hive 会全表扫描。我见过有人写SELECT * FROM disease_raw WHERE disease_code='A01',没带日期条件,结果扫了所有分区,跑了四十分钟。正确写法是WHERE confirm_date BETWEEN '2024-01-01' AND '2024-01-31' AND disease_code='A01',这样 Hive 只扫描一月份的分区目录。
如果某个病种的查询特别频繁,可以进一步建桶表。桶表按指定列的哈希值把数据分到固定数量的桶里,join 和 group by 时能减少 shuffle 数据量。
-- 建桶表,按 case_id 分 16 个桶 CREATE TABLE disease_bucketed ( case_id STRING, gender STRING, age INT, disease_code STRING, region_code STRING ) CLUSTERED BY (case_id) INTO 16 BUCKETS STORED AS ORC; -- 开桶表优化 SET hive.enforce.bucketing = true; SET hive.optimize.bucketmapjoin = true; INSERT INTO disease_bucketed SELECT case_id, gender, age, disease_code, region_code FROM disease_raw WHERE confirm_date = '2024-01-01';分桶数一般设成集群 CPU 核数的倍数,16 或 32 比较常见。ORC 格式比 TEXTFILE 压缩率高、读取快,代价是导入时多一步转换。桶表建好后,两个桶表做 join 时可以走 bucket map join,不用把数据全部 shuffle 到 reduce 端。
6.2 用 EXPLAIN 和作业计数器定位慢查询
Hive 的 EXPLAIN 命令能打印出执行计划,看它把 HiveQL 翻译成了几个 stage、每个 stage 的依赖关系。如果 stage 数量异常多,说明有多次 shuffle,可以考虑改写 SQL 减少 group by 层数。
-- 查看执行计划 EXPLAIN SELECT disease_code, COUNT(DISTINCT case_id) FROM disease_raw WHERE confirm_date = '2024-01-01' GROUP BY disease_code;执行计划里重点看 Stage 依赖图和 Map/Reduce 算子。如果看到多个 Reduce 串行,说明有嵌套聚合。作业跑起来后,去 YARN 的 Web UI(默认 8088 端口)看 Counter,重点看「Reduce input records」和「Reduce output records」的比值,如果输入远大于输出,说明聚合效果好;如果差不多,说明 group by 的 key 太分散,可以考虑加 combiner。
我自己踩过最深的一个坑是:统计脚本在测试数据上跑得飞快,一上生产就 OOM。后来发现是测试数据里病种分布均匀,生产数据里某个病种占了 60%,reduce 端严重倾斜。从那以后我养成了一个习惯——任何 group by 查询上线前,先用SELECT key, COUNT(*) FROM table GROUP BY key ORDER BY COUNT(*) DESC LIMIT 10看一眼 key 的分布,心里有数再跑全量。希望帮到你。
本文还有配套的精品资源,点击获取