Java手机APP信息统计分析系统源码解析:Flume+Hive离线链路实战 简介这是一套面向Java后端与大数据方向学习者的手机APP信息统计分析系统源码围绕用户行为数据的采集、存储、分析与可视化展开适合作为课程设计、毕业设计或大数据入门项目的参考实现。资源包共57个文件约56.75MB以24个Java类文件与14个XML配置为主构成核心业务与工程配置另有2个JSP页面、2个Properties配置、1个HTML与1个JS文件承担前端展示与参数管理并附带MaxMind GeoIP数据库用于地域解析整体结构按采集、公共模块、Hive处理与可视化等模块划分便于按需拆解学习。目前已有338人学习下载。通过该源码可了解Flume日志采集、Hive离线统计与Web可视化之间的衔接方式掌握用户行为指标从原始日志到统计结果的完整链路同时借鉴多模块Maven工程的目录组织与配置管理思路为后续二次开发或性能优化提供可复用的基础框架。1. 从一份 Java 源码包说起手机 APP 信息统计分析系统能跑出什么前阵子有个做 Android 客户端的朋友找我说老板要一份「用户行为漏斗」报表运营那边催得紧但后端只给了一堆原始日志谁也没空从零搭一套采集链路。我翻出硬盘里存了很久的一个 Java 项目包丢给他——一个基于 Java 的手机 APP 信息统计分析系统源码解压后 57 个文件24 个 Java 类、14 个 XML 配置、2 个 JSP 页面还带一个 MaxMind 的 GeoIP 数据库文件。他照着跑了两天把日志采集、清洗、入库、可视化这条链路走通了漏斗报表也出来了。这套东西解决的核心问题很明确把散落在客户端和服务端的用户行为日志用 Flume 采集、Hive 做离线统计、Web 端做可视化展示形成一条可复现的离线分析流水线。它适合两类人——一是想学大数据离线分析完整链路的学生或转行者二是手上有一批 APP 日志、需要快速搭个统计原型验证思路的工程师。下面我按「这套系统怎么搭起来、每个模块干什么、哪里容易翻车」的顺序拆一遍。2. 模块拆解与运行环境五个子工程各管什么2.1 从目录结构看数据流向解压后的目录里除了.DS_Store这类 macOS 残留文件真正干活的是五个 Maven 子工程加上一个文档和一个 GeoIP 库。我先把它们的关系理清楚不然后面配依赖会一头雾水。子工程职责关键依赖app_logs_common公共实体类、常量、工具方法无特殊依赖被其他模块引用app_logs_flumeFlume 采集配置与自定义拦截器flume-ng-coreapp_logs_hiveHive UDF 与统计 SQL 脚本hive-execapp_logs_collect_web日志接收 Web 服务JSP Servletservlet-api、jsp-apiapp_logs_visualize_web可视化展示 Web 端servlet-api、jsp-apiapp_logs_client模拟客户端上报的测试程序httpclient数据流向是这样的app_logs_client模拟 APP 端产生行为日志通过 HTTP 上报到app_logs_collect_web采集 Web 服务把日志落到本地文件Flume 的app_logs_flume模块负责把文件搬运到 HDFSapp_logs_hive里的 SQL 和 UDF 对 HDFS 上的日志做清洗和聚合最后app_logs_visualize_web从 Hive 结果表读数据渲染成 JSP 页面。提示app_logs_common是被其他四个模块共同依赖的改这个模块的实体类字段时记得四个下游模块都要重新mvn install否则会出现字段对不上的编译错误。2.2 环境准备与依赖安装这套系统对版本比较敏感我踩过的坑是 JDK 和 Hadoop 版本不匹配导致 Flume 启动直接报NoClassDefFoundError。下面是我验证过能跑通的一套组合JDK 1.8不要用 11 及以上Hive 老版本对高版本 JDK 支持差Maven 3.6Hadoop 2.7.x伪分布式即可Hive 2.3.xFlume 1.9.xTomcat 8.5跑两个 Web 模块先确认基础环境java -version mvn -version hadoop version hive --version flume-ng version这几个命令都能正常输出版本号再往下走。如果hadoop version报错先解决 Hadoop 环境变量别急着编译项目。2.3 编译打包五个子工程项目根目录下没有聚合 pom需要逐个进入子工程编译。顺序很重要app_logs_common必须第一个装到本地仓库# 1. 先编译公共模块安装到本地 Maven 仓库 cd app_logs_common mvn clean install -DskipTests # 2. 编译 Flume 采集模块 cd ../app_logs_flume mvn clean package -DskipTests # 3. 编译 Hive 统计模块 cd ../app_logs_hive mvn clean package -DskipTests # 4. 编译两个 Web 模块产出 war 包 cd ../app_logs_collect_web mvn clean package -DskipTests cd ../app_logs_visualize_web mvn clean package -DskipTests # 5. 编译客户端模拟程序 cd ../app_logs_client mvn clean package -DskipTests-DskipTests是为了跳过测试用例这套源码里的测试用例依赖外部服务本地跑会失败。编译完成后两个 Web 模块的target目录下会有.war文件Flume 模块的target下会有带依赖的 jar 包。参数说明install和package的区别在于install会把产物装进本地仓库供其他模块引用package只打包到 target。所以app_logs_common必须用install。2.4 部署采集端与 Web 端把app_logs_collect_web的 war 包丢进 Tomcat 的webapps目录启动 Tomcat。这个服务的作用是接收客户端上报的日志默认监听一个 HTTP 接口把请求体追加写入本地日志文件。Flume 的配置在app_logs_flume模块的src/main/resources下通常是一个.conf文件。核心配置项是 source 类型一般是 spooldir 或 taildir、channel 类型memory 或 file、sink 类型hdfs。我一般会把 channel 改成 file虽然慢一点但机器重启不丢数据。# flume 配置示例根据实际 conf 文件调整 a1.sources r1 a1.channels c1 a1.sinks k1 a1.sources.r1.type TAILDIR a1.sources.r1.filegroups f1 a1.sources.r1.filegroups.f1 /data/applogs/.*.log a1.channels.c1.type FILE a1.channels.c1.checkpointDir /data/flume/checkpoint a1.channels.c1.dataDirs /data/flume/data a1.sinks.k1.type HDFS a1.sinks.k1.hdfs.path hdfs://localhost:9000/applogs/%Y%m%d a1.sinks.k1.hdfs.fileType DataStream a1.sinks.k1.hdfs.rollInterval 3600TAILDIR适合持续追加的日志文件rollInterval3600表示每小时滚动一个 HDFS 文件避免小文件过多。FILEchannel 的checkpointDir和dataDirs要指向磁盘空间充足的路径。启动 Flumeflume-ng agent \ --conf conf \ --conf-file /path/to/app_logs_flume.conf \ --name a1 \ -Dflume.root.loggerINFO,console看到Component started字样说明采集链路起来了。这时候跑app_logs_client模拟上报几条数据去 HDFS 上hdfs dfs -ls /applogs/看有没有文件生成。3. Hive 统计与 GeoIP 解析把原始日志变成可用指标3.1 建外部表映射 HDFS 日志日志进 HDFS 后还是原始文本需要用 Hive 外部表映射。在app_logs_hive模块的 SQL 脚本里一般会先建一张外部表指向 HDFS 路径CREATE EXTERNAL TABLE IF NOT EXISTS ods_app_logs ( event_time STRING COMMENT 事件时间, user_id STRING COMMENT 用户ID, app_id STRING COMMENT 应用ID, event_type STRING COMMENT 事件类型, page_id STRING COMMENT 页面ID, ip STRING COMMENT 客户端IP, device_model STRING COMMENT 设备型号 ) ROW FORMAT DELIMITED FIELDS TERMINATED BY \t STORED AS TEXTFILE LOCATION /applogs/;字段分隔符要和 Flume 写入时保持一致如果 Flume 那边用的是逗号这里就改成FIELDS TERMINATED BY ,。建完表先SELECT * LIMIT 10验证一下字段错位是最常见的问题。3.2 用 GeoIP 库补全省份城市维度摘要里提到那个.mmdb文件是 MaxMind 的 GeoIP 数据库用来把 IP 解析成地理位置。Hive 里可以通过 UDF 调用它。app_logs_hive模块里应该有一个继承UDF的 Java 类核心逻辑是加载 mmdb 文件、传入 IP 返回省市信息。// GeoIP UDF 核心逻辑示意 public class GeoIpUDF extends UDF { private static DatabaseReader reader; static { try { // mmdb 文件路径部署时改成实际路径 File dbFile new File(/data/geoip/GeoLite2-City.mmdb); reader new DatabaseReader.Builder(dbFile).build(); } catch (IOException e) { throw new RuntimeException(GeoIP 库加载失败, e); } } public String evaluate(String ip) { if (ip null || ip.isEmpty()) return unknown; try { InetAddress addr InetAddress.getByName(ip); CityResponse response reader.city(addr); String country response.getCountry().getNames().get(zh-CN); String city response.getCity().getNames().get(zh-CN); return country - city; } catch (Exception e) { return unknown; } } }打包后需要在 Hive 里注册ADD JAR /path/to/app_logs_hive-1.0.jar; CREATE TEMPORARY FUNCTION geo_ip AS com.example.hive.GeoIpUDF; -- 使用 UDF 补全地理维度 SELECT geo_ip(ip) AS region, COUNT(DISTINCT user_id) AS uv FROM ods_app_logs GROUP BY geo_ip(ip);ADD JAR是会话级的重连 Hive 后要重新注册。如果要做成永久函数需要把 jar 放到 HDFS 并用CREATE FUNCTION语句。3.3 核心统计指标与 SQL 写法这套系统要出的统计指标常见的有日活、事件分布、页面停留、地域分布。下面几个 SQL 是实际跑过的-- 日活 DAU INSERT OVERWRITE TABLE dws_dau PARTITION (dt2024-01-01) SELECT COUNT(DISTINCT user_id) FROM ods_app_logs WHERE event_time LIKE 2024-01-01%; -- 事件类型分布 SELECT event_type, COUNT(*) AS cnt FROM ods_app_logs WHERE dt2024-01-01 GROUP BY event_type ORDER BY cnt DESC; -- 地域 UV 排名 SELECT geo_ip(ip) AS region, COUNT(DISTINCT user_id) AS uv FROM ods_app_logs WHERE dt2024-01-01 GROUP BY geo_ip(ip) ORDER BY uv DESC LIMIT 20;INSERT OVERWRITE配合分区每次跑批覆盖当天分区避免重复数据。分区字段dt在建表时要声明为PARTITIONED BY (dt STRING)。3.4 可视化 Web 端对接 Hive 结果app_logs_visualize_web模块通过 JDBC 连 HiveServer2 读结果表在 JSP 页面里渲染。连接配置一般在src/main/resources的 properties 文件里hive.urljdbc:hive2://localhost:10000/default hive.userroot hive.passwordJSP 页面里用 JDBC 查询结果集转成 JSON 传给前端图表库。这里要注意 HiveServer2 的并发连接数多个用户同时打开页面容易把连接池打满建议在 Web 端加一层缓存统计结果几分钟更新一次就够了。4. 避坑与排查这套源码跑不起来时先看这几条4.1 编译报错找不到 common 模块现象mvn package时报Could not resolve dependencies for project ... app_logs_common:jar:1.0。原因app_logs_common没有先install到本地仓库或者版本号和其他模块 pom 里声明的不一致。解决先cd app_logs_common mvn clean install然后检查其他模块 pom 里dependency的groupId、artifactId、version是否和 common 模块的 pom 完全一致。版本号差一个字符都会找不到。4.2 Flume 启动报 NoClassDefFoundError现象flume-ng agent启动后立刻退出日志里出现NoClassDefFoundError: org/apache/hadoop/...。原因Flume 的 classpath 里没有 Hadoop 的依赖 jar或者 Hadoop 版本和 Flume 编译时用的版本不匹配。解决把 Hadoop 的share/hadoop/common/*.jar和share/hadoop/hdfs/*.jar软链或复制到 Flume 的lib目录下。更稳妥的做法是在flume-env.sh里设置FLUME_CLASSPATH指向 Hadoop 的 jar 目录。4.3 Hive 查询返回 NULL 或字段错位现象SELECT * FROM ods_app_logs LIMIT 10出来全是 NULL或者字段值串位。原因建表时声明的分隔符和实际日志文件的分隔符不一致。Flume 写入时可能用了默认的换行分隔而建表用了\t。解决先用hdfs dfs -cat /applogs/xxx.log | head -5看原始日志长什么样确认分隔符。如果是逗号就改FIELDS TERMINATED BY ,如果是多个空格考虑用正则 SerDe。4.4 GeoIP UDF 返回 unknown现象所有 IP 解析结果都是unknown。原因mmdb 文件路径不对或者 UDF 里加载文件时抛了异常被 catch 吞掉了。解决先确认 mmdb 文件在服务器上真实存在且有读权限然后在 UDF 的 catch 块里把异常打印出来别静默返回 unknown。另外 MaxMind 的免费库精度有限内网 IP 解析不出来是正常的。4.5 Tomcat 部署 war 包后 404现象war 包丢进 webapps 后访问对应路径报 404。原因war 包名和访问路径不匹配或者 Tomcat 启动时解压失败。解决看 Tomcat 的logs/catalina.out确认 war 是否成功解压成同名目录。访问路径就是 war 包名去掉.war后缀。如果解压失败多半是 war 包本身不完整重新mvn package一次。5. 进阶技巧把离线链路改成准实时以及验证数据对不对5.1 用 Flume 拦截器做字段清洗原始日志里经常混着脏数据比如 user_id 为空、event_time 格式不对。与其在 Hive SQL 里到处写WHERE过滤不如在 Flume 采集阶段就用拦截器丢掉。app_logs_flume模块里可以加一个自定义 Interceptorpublic class LogCleanInterceptor implements Interceptor { Override public Event intercept(Event event) { String body new String(event.getBody(), StandardCharsets.UTF_8); String[] fields body.split(\t); // 字段数不足或 user_id 为空直接丢弃 if (fields.length 7 || fields[1].trim().isEmpty()) { return null; } return event; } Override public ListEvent intercept(ListEvent events) { ListEvent result new ArrayList(); for (Event e : events) { Event out intercept(e); if (out ! null) result.add(out); } return result; } }在 Flume 配置里挂上a1.sources.r1.interceptors i1 a1.sources.r1.interceptors.i1.type com.example.flume.LogCleanInterceptor$Builder拦截器返回null的事件会被 Flume 丢弃这样进 HDFS 的就是干净数据Hive 侧不用再反复过滤。5.2 数据对账三招验证统计结果没跑偏跑完批任务怎么知道数字是对的我一般做三层校验第一层原始日志行数和 Hive 外部表行数对比。hdfs dfs -cat /applogs/20240101/*.log | wc -l和SELECT COUNT(*) FROM ods_app_logs WHERE dt20240101应该一致差太多说明有文件没被映射到。第二层关键指标交叉验证。比如 DAU 用COUNT(DISTINCT user_id)算出来是 1 万那事件总数除以人均事件数应该在合理范围。如果 DAU 比总事件数还大肯定是字段错位了。第三层抽样人工核对。从结果表里随机抽 10 个 user_id去原始日志里 grep 这个用户的完整行为序列看统计口径是否符合预期。5.3 从离线到准实时的最小改动这套系统原生是离线批处理如果业务方要求「小时级」更新不用大改架构。把 Flume 的rollInterval从 3600 改成 600Hive 侧加一个每小时调度的脚本用INSERT OVERWRITE覆盖当天分区。查询端加一层判断如果当天分区还没跑完就展示前一天的数据避免读到半截结果。# 小时级调度脚本示意 #!/bin/bash DT$(date %Y%m%d) HH$(date %H) hive -e INSERT OVERWRITE TABLE dws_dau_hour PARTITION (dt${DT}, hh${HH}) SELECT COUNT(DISTINCT user_id) FROM ods_app_logs WHERE dt${DT} AND hour(event_time)${HH}; 这个改动成本很低但能让报表从「T1」变成「T1 小时」对运营侧体验提升明显。从那以后我每次拿到一套陌生的日志分析源码都强制先跑一遍「原始日志行数 vs 外部表行数」的对账再去看业务指标。这个习惯帮我省了很多次被老板追问「数字怎么对不上」的尴尬。希望这套拆解能帮到你少走点弯路。本文还有配套的精品资源点击获取