基于Hadoop的游戏数据分析系统:完整工程实践与MapReduce实现
简介该压缩包提供了一套基于Hadoop的游戏数据分析系统完整源码与配套资源适合正在学习大数据处理、Java MapReduce编程及Web可视化的开发者可用于课程设计、毕业设计或企业项目原型参考。项目中包含6个JSP页面、3个JS文件、2个CSS样式文件和2个JAR包覆盖玩家活跃度、付费行为、游戏习惯、新用户分析等核心模块并附有SQL脚本用于初始化数据库同时提供classpath与project文件便于导入开发环境。全部20个文件约2.1MB目录结构清晰。目前已有220人学习下载。通过研究项目的数据采集、预处理、指标计算与结果展示流程读者可以掌握HDFS存储、MapReduce作业编写、Hive/HBase结果存储以及JSP页面调用的完整实现方式理解大数据技术在游戏运营场景中的落地路径具有较强的实践参考价值。1. 基于 Hadoop 的游戏数据分析系统跑通四类指标分析的完整工程做游戏数据分析的同学应该都有这种体会单机 Excel 处理几百万行登录日志已经卡到想砸电脑而一提到 Hadoop又被“分布式”“MapReduce”这些词吓得以为要搭五台服务器。这份“基于 Hadoop 的游戏数据分析系统”就是一个让你避开这些恐惧的完整工程——一个 Eclipse 动态 Web 项目Java 源码、WebContent 页面、SQL 脚本一应俱全在伪分布式环境下就能把玩家活跃度、付费行为、游戏习惯、新用户分析四类指标跑出来。适合正在做课程设计、毕业设计或者想看看 Hadoop 分析代码长什么样的同学。下面按看懂工程、部署环境、跑起来、踩坑、进阶的顺序拆开讲。2. 先看懂工程再动手目录、数据表与四个分析模块的对应关系2.1 工程目录拆解src、WebContent、dbgame 各管什么解压 zip 之后你看到的是一个名为 Hadoop-based-game-user-analysis-system-master 的文件夹这是标准的 Eclipse Dynamic Web Project 布局。我第一次打开的时候也被一堆目录唬住了实际上只需要盯住几个关键位置。.classpath 和 .project 是 Eclipse 的工程描述文件决定导入后能否被识别src 是 Java 源码MapReduce 作业、工具类和 Servlet 都放在这里WebContent 下面则是 JSP 页面、JS、CSS 和 WEB-INF 目录web.xml 里的路由映射决定了每个页面请求交给哪个 Servlet 处理dbgame 是数据库相关目录sql.sql 是建表语句和模拟数据脚本。另外还有 build、classes 这类目录是 Eclipse 自动生成的编译输出删掉也能重新编译不用管。这个工程没有 pom.xml也就是说它不是 Maven 工程依赖管理完全走 Eclipse 的 Build Path。老一点的课程设计基本都是这种组织方式好处是导入即用坏处是换一台电脑就要手动补一遍 jar 包这一步放在第 4 章专门讲。2.2 数据库脚本 sql.sql表结构与模拟数据的长相sql.sql 是整套系统的地基负责建库、建表和灌模拟数据。拆开看的话这类课程设计最常见的是围绕玩家、登录、付费三张表建库我按最常见的结构还原如下字段含义都写在注释里-- 玩家基础信息表 CREATE TABLE t_user ( uid INT PRIMARY KEY, username VARCHAR(50), register_time DATETIME, channel VARCHAR(20) -- 注册渠道android / ios / web ); -- 登录日志表 CREATE TABLE t_login_log ( log_id BIGINT PRIMARY KEY, uid INT, login_time DATETIME, -- 登录时间 logout_time DATETIME, -- 登出时间 online_minutes INT -- 本次在线时长分钟 ); -- 付费记录表 CREATE TABLE t_pay_log ( pay_id BIGINT PRIMARY KEY, uid INT, pay_time DATETIME, amount DECIMAL(10,2), -- 付费金额 item_name VARCHAR(50) -- 购买的道具或礼包 );注意你解压后以 sql.sql 实际内容为准但角色基本就是这三张表用户表存注册信息登录日志表存每一次上下线记录付费表存每一笔充值。日活、在线时长、付费金额这些指标全部由这三张表推导出来。导入数据库我用的是 MySQL 命令行sql.sql 里如果没有建库语句就先执行 create database dbgame再 use dbgame最后 source sql.sql。模拟数据一般会灌几百到几千行足够让 MapReduce 作业跑出肉眼可见的统计差异。2.3 四个分析 JSP 对应四类运营指标WebContent 下的几个 JSP 页面名字起得相当直白一个页面就是一个分析主题这也是这套系统最适合拿来交作业的原因每个模块的展示端、分析端、数据库端能一一对上。下面这张表可以直接作为你阅读源码的索引JSP 文件名分析主题典型输出指标Player activity analysis.jsp玩家活跃度分析日活 DAU、周活 WAU、平均在线时长Player payment behavior analysis.jsp玩家付费行为分析付费总金额、付费率、ARPUPlayer game habit analysis.jsp玩家游戏习惯分析时段分布、关卡偏好、在线时长分布New user analysis.jsp新用户分析新增用户数、次日留存率、渠道分布这四类指标基本覆盖了游戏运营日报的骨架。你可能会问这些分析到底是 JSP 里直接查 MySQL还是走 MapReduce答案是两类都有。简单聚合如按渠道统计用户数直接 SQL 更快而涉及全量日志扫描的比如按天去重算 DAU在工程里往往会抽成 MapReduce 作业先把结果落到 HDFS 或临时表JSP 只负责读最终结果。想清楚这一层你就知道这套系统的边界没有 Flume 采集也没有 Ambari 监控它重在教学链路完整而不是工业级平台。3. 部署环境准备JDK 8 与 Hadoop 2.x 伪分布式的版本匹配和核心配置3.1 版本匹配是第一道坎为什么我推荐 JDK 8 配 Hadoop 2.x很多人在环境这步就放弃了多半是版本乱配。这套工程诞生在 Hadoop 2.x 年代代码里如果用到了 org.apache.hadoop.mapreduce 包在 Hadoop 3.x 下跑通常问题不大但配置文件和端口行为变化会影响伪分布式联调。比如 Hadoop 3 的 YARN 默认端口、DataNode 通信端口都和 2.x 不同老工程里的 JSP 如果写死了 hdfs://localhost:90003.x 上就要跟着改一堆配置。我一般推荐直接用 Hadoop 2.7 到 2.10 之间的版本配上 JDK 1.8。原因很简单网上能搜到的 hadoop-eclipse-plugin 插件和课程设计教程绝大多数都跑在这套组合上遇到问题搜索时命中率最高这也是一种“生态匹配”。如果机器上已经装了新版本 JDK建议再装一个 JDK 8把 Eclipse 的编译级别切到 1.8避免用太高版本去兼容老依赖。组件推荐版本说明JDK1.8老 Hadoop 生态兼容性最好Hadoop2.7.x ~ 2.10.x资料最多踩坑好搜Eclipse2021 之前对老插件支持好新版也能用hadoop-eclipse-plugin2.7.x用于在 Eclipse 里直接连 HDFS3.2 核心配置三件套core-site.xml、hdfs-site.xml、mapred-site.xml伪分布式不需要你理解多少分布式理论但三个配置文件必须写对任何一个地址写错后面 JSP 联调时都会报连接失败。下面这份配置可以直接抄路径按你自己的安装位置改!-- core-site.xml决定 HDFS 访问地址 -- configuration property namefs.defaultFS/name valuehdfs://localhost:9000/value /property property namehadoop.tmp.dir/name value/home/hadoop/hadoopdata/value /property /configuration!-- hdfs-site.xml决定副本数和元数据路径 -- configuration property namedfs.replication/name value1/value /property property namedfs.namenode.name.dir/name valuefile:///home/hadoop/hadoopdata/name/value /property property namedfs.datanode.data.dir/name valuefile:///home/hadoop/hadoopdata/data/value /property /configuration!-- mapred-site.xml决定 MapReduce 跑在哪个框架上 -- configuration property namemapreduce.framework.name/name valueyarn/value /property /configuration参数解释一下fs.defaultFS 是 HDFS 的访问入口工程里所有 hdfs:// 开头的路径都以它为准dfs.replication 设成 1 是因为单机集群配 3 反而会一直报块副本不足hadoop.tmp.dir 别偷懒放 /tmp系统重启就清空NameNode 元数据也会跟着丢这是个可以提前规避的惨痛教训。mapreduce.framework.name 设为 yarn 表示作业交给 YARN 调度如果设成 local作业会在提交端本地跑不再走分布式流程。3.3 启动 HDFS 并验证进程格式化只做一次配置写完后按下面顺序执行这是 Hadoop 伪分布式搭建的标准流程# 首次使用必须格式化 NameNode这个命令会清空元数据绝不能重复执行 hdfs namenode -format # 分别启动 HDFS 和 YARN start-dfs.sh start-yarn.sh # 查看 Java 进程判断各服务是否起来 jps启动之后 jps 应该能看到 NameNode、DataNode、ResourceManager、NodeManager 四个进程少一个都不行。少 DataNode 通常就是前面说的 clusterID 不一致少 ResourceManager 就去 logs 目录看 yarn-hadoop-resourcemanager-*.log。我一般还会顺手建好输入目录免得 JSP 第一次提交作业时因为目录不存在而报错hdfs dfs -mkdir -p /game/input。另外 start-dfs.sh 会要求配置过 SSH localhost 免密登录没配的话会让你反复输密码配一次以后就顺了。环境部分最花时间的往往不是安装而是端口、hosts、权限这些小问题所以下一章直接进 Eclipse 联调。4. 把工程跑起来Eclipse 导入、依赖补齐与 JSP 调用 MapReduce 的完整链路4.1 导入 Eclipse让工程被识别成 Web 项目拿到解压后的目录打开 Eclipse 用 File - Import - Existing Projects into Workspace选中 Hadoop-based-game-user-analysis-system-master 文件夹。因为有 .project 文件存在Eclipse 一般能直接识别项目结构但导入后要马上确认两件事src 有没有变成源码目录WebContent 有没有被识别成 Web 根目录。如果都没有在项目上右键选择 Configure - Convert to Faceted Form勾选 Dynamic Web Module再把 WebContent 指认为 Web Content Directory。这一步很多人会漏漏了之后 JSP 能打开但 Servlet 映射全失效页面 404。转换完记得重新 Run on Server让 Tomcat 重新发布。4.2 编译路径与 Hadoop 依赖 jar本地仓库在哪、加哪几个由于没有 Maven项目里所有依赖都需要手动挂到 Build Path 上。Hadoop 安装目录下其实已经带了全套 jar按下面路径把对应 jar 全选加进去# Hadoop 2.7 本地 jar 目录按实际安装路径替换 $HADOOP_HOME $HADOOP_HOME/share/hadoop/common/*.jar $HADOOP_HOME/share/hadoop/common/lib/*.jar $HADOOP_HOME/share/hadoop/hdfs/*.jar $HADOOP_HOME/share/hadoop/mapreduce/*.jar操作路径是右键项目 - Properties - Java Build Path - Libraries - Add External JARs。我一般先把 common 和 mapreduce 两个目录加全再加 hdfs最后把 yarn 相关的也加上这样省得缺一个类报一次 ClassNotFoundException。加完之后如果项目红叉优先看有没有重复的 commons-* 包Tomcat 自带的 servlet-api 和 Hadoop 的某些依赖版本冲突是常见雷区把 Build Path 里低版本的那个移除就好。4.3 JSP 调 MapReduce 的完整链路参数传递与结果回显这个工程的核心链路是JSP 页面传参数给 ServletServlet 调 MapReduce 作业作业跑完把结果写回 HDFSServlet 再读出来转发给 JSP 渲染。下面是一个统计日活的作业骨架模式非常典型改改就能用到付费、留存分析上public class ActiveAnalysisJob extends Configured implements Tool { public static class ActiveMapper extends MapperObject, Text, Text, Text { protected void map(Object key, Text value, Context context) throws IOException, InterruptedException { // 假设日志行格式uid,login_time,logout_time String[] fields value.toString().split(,); String date fields[1].substring(0, 10); // 取2024-01-01 context.write(new Text(date), new Text(fields[0])); } } public static class ActiveReducer extends ReducerText, Text, Text, IntWritable { protected void reduce(Text key, IterableText values, Context context) throws IOException, InterruptedException { SetString uidSet new HashSetString(); for (Text v : values) uidSet.add(v.toString()); context.write(key, new IntWritable(uidSet.size())); } } public int run(String[] args) throws Exception { Job job Job.getInstance(getConf(), active analysis); job.setJarByClass(ActiveAnalysisJob.class); job.setMapperClass(ActiveMapper.class); job.setReducerClass(ActiveReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); return job.waitForCompletion(true) ? 0 : 1; } }这里几个参数要重点讲。map 阶段输出的 key 是登录日期value 是 uidreduce 里用 Set 做全局去重再计数这就是 DAU 的精确算法不能省掉 Set 直接用累加器否则同一个用户当天登录三次会被重复统计。substring(0, 10) 依赖日志里 login_time 是固定的 yyyy-MM-dd HH:mm:ss 格式如果你的线上日志是时间戳或别的分隔符这里必须同步改。代码里的 import 我省略了实际补上 org.apache.hadoop.io.Text、IntWritable 和 java.util.Set 即可。输出的 part-r-00000 文件每行是“日期\t去重人数”这个文件就是 JSP 页面的数据源。Servlet 里调用这段作业并回显结果的写法也很固定WebServlet(/analysis/active) public class ActiveServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String date req.getParameter(date); if (date null || .equals(date)) { date LocalDate.now().toString(); } String input /game/input/login.log; String output /game/output/active_ date; String[] args { input, output }; try { ToolRunner.run(new ActiveAnalysisJob(), args); String result HdfsUtil.readAll(output /part-r-00000); req.setAttribute(dauResult, result); } catch (Exception e) { req.setAttribute(error, e.getMessage()); } req.getRequestDispatcher(/activity_analysis.jsp).forward(req, resp); } }注意一个前提跑作业之前先把业务库里的登录日志导出成 HDFS 上的文本文件常见做法是写一个导出脚本把 t_login_log 查出来按逗号拼接写进 /game/input/login.log。这一步不做作业会直接报 Input path does not exist。HdfsUtil 是工程里的 HDFS 读取工具类没有的话就自己用 FileSystem.open 读流代码量很小。提示MR 输出目录不能提前存在否则作业在初始化阶段会直接报 FileAlreadyExistsException所以输出目录要带日期或时间戳每次生成新路径。5. 避坑指南从 zip 解压到 HDFS 端口不通的五个典型问题课程设计项目翻车的高发区往往不在代码逻辑而在环境联通。下面五条是我在这个项目上反复见过的真实问题每一条都按“现象-原因-解决”拆开你可以直接当排查手册用。5.1 解压后 Eclipse 里项目带红叉src 无法展开现象导入 zip 解压出来的工程后项目图标有红叉src 目录展开后 .java 文件显示成普通文本运行按钮是灰的。原因.classpath 文件里记录的 JRE 版本或者依赖 jar 路径和当前机器不一致最常见的是别人用 JDK 1.8你装了 JDK 11另一种是缺 Hadoop jar编译通不过导致 Eclipse 放弃了整个源码目录。解决右键项目选 Properties - Java Build Path先把 JRE System Library 切成本机安装的 JDK 1.8再把带感叹号的 jar 全部 Remove然后按 4.2 的路径重新 Add External JARs。项目刷新后红叉会消失。如果还不行检查 Project Facets 里 Java 版本是否和编译级别一致。5.2 页面报 hdfs://localhost:9000 连接失败现象第一次点分析按钮页面直接抛 java.net.ConnectException: Connection refused或 Call From localhost/127.0.0.1 to localhost:9000 failed。原因90% 的情况是 HDFS 根本没启动或者配置里的 fs.defaultFS 端口和工程代码里写的不一致。还有一种隐蔽情况Linux 的 /etc/hosts 把 localhost 解析成了 IPv6 地址 ::1而 Hadoop 监听的是 IPv4导致同一台机器都连不上自己。解决先执行 jps 看有没有 NameNode没有就重新 start-dfs.sh。有的话再测 telnet localhost 9000通不通一目了然。最后检查 core-site.xml 的端口和 JSP、代码里 hdfs:// 地址是否一致。IPv6 的问题改 /etc/hosts 把 localhost 指到 127.0.0.1 即可。5.3 作业提交到 YARN 后一直 ACCEPTED进度条不动现象通过 JSP 或命令行提交作业后控制台停在 Submitting applicationResourceManager 界面里任务状态一直是 ACCEPTEDmap 进度 0%。原因这是内存配置问题。伪分布式机器的物理内存本来就紧张YARN 给每个容器分配的默认内存超出了 NodeManager 的可用额度容器起不来作业只能排队等着。解决在 mapred-site.xml 里把容器内存调低然后重启 YARNproperty nameyarn.app.mapreduce.am.resource.mb/name value512/value /property property namemapreduce.map.memory.mb/name value512/value /property property namemapreduce.reduce.memory.mb/name value512/value /property调完注意 yarn.nodemanager.resource.memory-mb 要大于所有容器之和一般设 2048 就够。这个配置改完不重启不生效记得 stop-yarn.sh 再 start-yarn.sh。5.4 JSP 页面中文乱码HDFS 读出的结果也乱现象页面标题、渠道名称显示成问号或者读取 part-r-00000 后 JSP 表格里中文全乱。原因三处编码不一致。JSP 页面请求没走 UTF-8 过滤器HDFS 文件读取时用了系统默认编码Windows 下是 GBK或者 sql.sql 导入 MySQL 时字符集就是 latin1。解决JSP 顶部统一加 % page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8%web.xml 里配一个 CharacterEncodingFilter 强制 UTF-8。读取 HDFS 文件时用 new BufferedReader(new InputStreamReader(in, UTF-8))不要用默认编码。数据库连接串上追加 characterEncodingutf8并把 sql.sql 先用 UTF-8 编码保存再 source。这一条是免费的经验做一次能省一整晚。5.5 重复执行 format 导致 DataNode 起不来现象第一次作业跑到一半想重来于是又执行了 hdfs namenode -format再启动时 NameNode 正常DataNode 进程起来又立刻退出日志里报 java.io.IOException: Inconsistent clusterID。原因format 会重新生成 NameNode 的 clusterID但 DataNode 的 data 目录里还留着旧 clusterID两侧对不上DataNode 拒绝启动。这是伪分布式里误操作频率最高的一步很多人不知道格式化会清空所有元数据和数据块。解决停掉集群后删除 hadoop.tmp.dir 目录下的 name 和 data 两个子目录然后再执行一次格式化并重启。从那以后我每次想重来的时候都会先确认三遍是不是只重置元数据、DataNode 上的块能不能丢、target 输出目录有没有被误伤。格式化这件事做过一次就应该长记性。6. 进阶用 Hive 外部表接管分析结果并强制做指标对账6.1 把 MR 输出直接建成 Hive 外部表跑通之后你会觉得每次都在 JSP 和 HDFS 文件之间来回复制很别扭结果没法用 SQL 继续聚合。把这套工程的分析输出接到 Hive 是最顺手的升级路径而且不用改 Java 代码只需要建一张外部表-- MR 输出按 tab 分隔正好匹配字段终止符 CREATE EXTERNAL TABLE game_dau ( stat_date STRING, dau INT ) ROW FORMAT DELIMITED FIELDS TERMINATED BY \t LOCATION /game/output/active;MR 的 reduce 输出是“日期\t人数”正好能被这张表按 tab 解析。外部表的好处是 Hive 只是挂载在 HDFS 路径上的壳MR 重跑覆盖数据后表里查到的就是最新结果不需要重建表或导数据非常适合反复调参的场景。建完后可以直接在 Hive 里验证当天结果也可以 join 上 t_user 表做渠道维度下钻。6.2 用 SQL 对账MR 算出的 DAU 必须和数据库直查一致接上 Hive 之后建议你做一道必做验证题用 MySQL 直查同一份登录日志算 DAU和 MR 结果对比SELECT DATE(login_time) AS stat_date, COUNT(DISTINCT uid) AS dau FROM t_login_log GROUP BY DATE(login_time);两边结果如果一致说明整个清洗、切分、去重链路是可信的不一致就按日期逐个往下查多数时候是日志里有脏数据uid 为空、时间字段格式不对在 MR 里被截断成了别的值。早先我做课程设计时demo 能出图就觉得完事了结果答辩被老师一句“你 MR 算的和数据库查的怎么对不上”问得当场翻车。从那以后我每次出分析结果都强制用 SQL 原路对一遍账再往页面上挂数这已经成了我做所有数据项目的固定动作。这套体系从 Hadoop 跑到 Hive 一直在变但对账这个习惯我始终没丢过。这份 zip 如果正躺在你的下载列表里照着上面的顺序装上跑完两小时左右应该能看到第一张分析页面亮起来希望帮到你。本文还有配套的精品资源点击获取