ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Java的APP用户行为分析系统:Flume+Hive离线链路源码详解

基于Java的APP用户行为分析系统:Flume+Hive离线链路源码详解 简介基于Java的手机APP信息统计分析系统设计源码是一套面向移动端开发者、数据分析工程师及后端架构人员的完整项目主要解决APP用户行为数据的采集、传输、存储、离线分析与可视化展示问题帮助团队掌握功能使用情况并持续优化产品体验。项目采用多模块Maven工程结构包含日志采集上报客户端、Flume日志接入、Web收集与可视化后台、公共模块以及Hive离线分析等环节从数据入口到报表输出形成完整闭环适合作为大数据分析项目的工程范例。资源共57个文件核心为24个Java类文件、14个XML配置、2个JSP页面与2个Properties配置覆盖业务逻辑、框架配置与动态页面另含GeoIP数据库、Markdown/Word说明文档及图片素材便于理解IP归属地处理和项目部署结构。压缩包56.75MB已有338人学习下载适合需要研究APP日志分析全链路、参考源码进行二次开发或准备相关课程设计/毕业设计的开发者。1. 基于 Java 的手机 APP 信息统计分析系统一份能跑通全链路的源代码这套源码把“用户打开 App 之后发生了什么”这件事完整地拆成了六段客户端模拟打点、Web 端收集日志、Flume 采集落盘、Hive 清洗建模、可视化报表展示。我用它复现用户行为分析系统时最大的感受是它不像网上那些只有 CRUD 的管理系统而是把日志从产生到变成报表的每一环都留在工程里适合正在学大数据、准备 Java 面试或者做课程设计的人。源码里有 57 个文件24 个 Java 类、14 个 XML 配置、2 个 JSP 页面还有一份完整的项目文档和 MaxMind 的 GeoIP 库拿到的就是整套离线分析链路。接下来我从模块边界讲起一直拆到排错和批量造数的技巧。2. 先看模块边界五个 Maven 子工程的分工与数据流向2.1 从工程名反推架构谁在采集、谁在清洗、谁在展示拿到源码先别急着跑把pom.xml和src目录扫一遍架构基本就清楚了。这套源码按“模块职责”拆了五个 Maven 子工程我整理成一张表对照着看源码会顺很多。模块目录责任关键点app_logs_client模拟手机 App 端生成启动日志、页面访问日志产生日志的直接入口可独立运行app_logs_collect_web接收客户端上报的日志写入本地磁盘Java Web 项目处理 HTTP 请求app_logs_flumeFlume 采集配置与打包监控日志文件写入 HDFSapp_logs_common公共类常量、日志实体、IP 解析工具被其他模块依赖是复用核心app_logs_hiveHive ETL建表语句、清洗 SQL、指标计算离线分析的“加工车间”app_logs_visualize_web可视化 Web 端读取分析结果并渲染报表JSP/HTML 页面面向业务展示模块之间的数据流是一条直线client模拟用户行为把日志通过 HTTP 打到collect_webcollect_web把日志落成本地文件flume监控这批文件搬运到 HDFShive模块在 HDFS 数据之上建外部表、跑指标 SQL最终visualize_web把指标查出来画成报表。理解这条链路比理解任何一个模块都重要因为后面所有排错都是顺着这条线找断点的。2.2 客户端打点一条模拟日志是怎么被造出来的app_logs_client是整个系统的源头。它做的事情很纯粹模拟手机 App 启动、页面跳转、退出登录这些行为然后把行为数据组装成一条 JSON 或 key-value 日志发给收集端。常见的做法是拿 Java 的HttpURLConnection直接 POST不引太重的东西。我复现时习惯先看它生成的日志长什么样再决定 Hive 那边怎么建表。// 模拟一条启动日志并上报到 collect_web String url http://localhost:8080/app_logs_collect_web/log/start; HttpURLConnection conn (HttpURLConnection) new URL(url).openConnection(); conn.setRequestMethod(POST); conn.setRequestProperty(Content-Type, application/json;charsetUTF-8); conn.setDoOutput(true); String json { \appId\:\app_001\, \deviceId\:\864393028571234\, \appVersion\:\2.1.0\, \osType\:\android\, \network\:\wifi\, \entry\:\push\, \action\:\startup\ }; conn.getOutputStream().write(json.getBytes(UTF-8)); int code conn.getResponseCode(); System.out.println(上报结果 code code);这段代码的核心参数在 JSON 体里deviceId是设备唯一标识后续 Hive 算新增用户、活跃用户全靠它去重entry表示用户从哪个入口进来比如push是推送、icon是点击图标action是具体动作。注意deviceId必须保持稳定如果客户端每次启动都随机生成一个新 ID后面所有用户指标都会虚高。我在第一次跑通后踩过这个坑造数脚本里把deviceId写死成一个数组循环用就正常了。2.3 Flume 落 HDFSTAILDIR 监控配置与参数拆解collect_web把日志写到本地磁盘之后Flume 负责搬运。这套源码里用的是 TAILDIR Source它最大的好处是能记录读取位置Flume 重启之后不会重复消费已经读过的日志。看app_logs_flume模块里的配置文件核心是下面这段。a1.sources r1 a1.channels c1 a1.sinks k1 a1.sources.r1.type TAILDIR a1.sources.r1.positionFile /opt/flume/data/taildir_position.json a1.sources.r1.filegroups f1 a1.sources.r1.filegroups.f1 /opt/logs/app_logs/.*\.log a1.sources.r1.batchSize 500 a1.sources.r1.channels c1 a1.channels.c1.type memory a1.channels.c1.capacity 10000 a1.channels.c1.transactionCapacity 2000 a1.sinks.k1.type hdfs a1.sinks.k1.hdfs.path /user/hive/warehouse/app_logs.db/ods_start_log/dt%Y%m%d a1.sinks.k1.hdfs.filePrefix app_log a1.sinks.k1.hdfs.fileType DataStream a1.sinks.k1.hdfs.writeFormat Text a1.sinks.k1.hdfs.rollInterval 60 a1.sinks.k1.channels c1三个关键参数要调明白positionFile保存读取进度如果多套 Flume 复用同一个文件会互相抢锁filegroups.f1是正则匹配日志目录目录路径写错日志就永远进不来hdfs.path里%Y%m%d是时间变量Flume 会按当前时间自动生成日期目录这个日期要和 Hive 分区字段对齐对不齐的话 Hive 查出来全是空。rollInterval默认 60 秒滚动一次文件测试时可以调成 10 秒方便看效果生产环境不要设太短否则 HDFS 上小文件会爆炸。2.4 为什么这条链路适合新手复现整套架构没有引入 Kafka、没有 Spark Streaming纯离线流程单机就能跑通。这对学习者非常友好Flume 挂了你查 Flume 日志Hive 查不到数据你查分区路径每一步都是独立的黑盒子可以逐个击破。对于只想看报表效果的人也可以跳过 Flume直接把日志文件丢到 HDFS 对应目录然后跑 Hive SQL。源码的价值在于它把这些环节都串好了你只需要替换成自己的路径和参数。3. 把日志变成可分析的表Hive ETL 与核心指标口径3.1 启动日志与页面日志分表建模建外部表时的两个细节app_logs_hive模块里的 SQL 脚本核心是把 Flume 落到 HDFS 的日志文件映射成 Hive 外部表。注意一定要用外部表因为数据是 Flume 写的Hive 只管读如果建成内部表一不小心DROP TABLE会把 HDFS 上的原始日志一起删掉没有后悔药。-- 启动日志外部表按天分区 CREATE EXTERNAL TABLE ods_start_log ( app_id STRING, device_id STRING, app_version STRING, os_type STRING, network STRING, entry STRING, action STRING, log_time STRING ) PARTITIONED BY (dt STRING) ROW FORMAT DELIMITED FIELDS TERMINATED BY \t STORED AS TEXTFILE LOCATION /user/hive/warehouse/app_logs.db/ods_start_log;两个容易翻车的点。第一FIELDS TERMINATED BY \t要和客户端实际输出的分隔符一致客户端如果是逗号分隔这里不改成,的话整行数据会被当成一个字段塞进第一列。第二分区字段dt不要写在表结构字段列表里它通过PARTITIONED BY单独声明数据文件里也没有这一列分区值来自目录名。我见过有人把dt写进字段列表加载数据时那一列永远是 NULL还查不出原因。建完表后执行MSCK REPAIR TABLE ods_start_log;让 Hive 自动识别 HDFS 上的分区目录这一步容易漏。3.2 核心指标口径新增用户、活跃用户、启动次数怎么算有了基础表剩下的就是指标计算。这套系统里的核心指标有三个新增用户数、活跃用户数和启动次数。先说口径再看 SQL因为口径不一致同一个数字两个人能算出两个结果。-- 当日活跃用户数当天去重后的设备数 SELECT dt, COUNT(DISTINCT device_id) AS active_users FROM ods_start_log WHERE dt 2024-01-01 GROUP BY dt; -- 当日新增用户数当天首次出现的设备数 SELECT COUNT(*) AS new_users FROM ( SELECT device_id FROM ods_start_log WHERE dt 2024-01-01 GROUP BY device_id HAVING MIN(dt) 2024-01-01 ) t;活跃用户数的口径是“当天有多少设备产生过启动行为”直接用COUNT(DISTINCT device_id)。新增用户的逻辑是“这个设备之前从没出现过”所以先用MIN(dt)找每个设备首次出现日期再过滤出首次出现日等于当天的设备。这里有个隐性条件日志表里的数据必须覆盖历史全量如果只加载了当天数据那MIN(dt)算出来的全是当天新增数会等于活跃数指标直接失真。我在测试环境验证指标时会故意往表里灌前一天的数据确认新增数降下来后再看当天报表。启动次数稍微特殊每一条启动日志代表一次启动会话所以COUNT(1)就是启动次数。如果日志里还带页面停留时长字段平均使用时长就是SUM(duration) / COUNT(DISTINCT device_id)单位通常是秒。这三个指标配合起来能回答“产品改版后用户到底回没回来”这一类问题。3.3 用 mmdb 把 IP 翻译成地域GeoIP 解析的接入方式源码里那个.mmdb文件是 MaxMind 的 GeoIP 数据库用来把访问 IP 解析成省份、城市。它配合 Hive 的 UDF 或者直接写 Java 工具类使用。app_logs_common模块里应该有对应的解析封装核心调用方式如下。// 用 maxmind-db 读取 mmdb 文件按 IP 查询地域信息 Reader reader new Reader(new File(/opt/data/GeoLite2-City.mmdb)); MapString, Object result reader.get(InetAddress.getByName(218.30.118.68)); MapString, Object provinceMap (MapString, Object) result.get(subdivisions); String province provinceMap.get(names).get(zh-CN).toString(); System.out.println(解析结果: province); reader.close();这段代码里Reader.get()返回的是一个嵌套 Mapsubdivisions下的names有多语言版本取zh-CN才能拿到中文省份名。注意 mmdb 文件是只读的二进制库不需要导入数据库直接放文件系统用代码读就行。我踩过的坑是文件路径写死成src/main/resources/下的相对路径打包后 classpath 变了就找不到文件后来统一改成绝对路径或者放进配置项用${geoip.path}注入问题才消失。另外 mmdb 文件有版本更新周期IP 段一直在变代码写完后记得定期替换文件否则新地区的用户会解析成空。4. 可视化与联调从 JSP 报表到接口参数映射4.1 可视化端读的是哪张表报表字段和结果表怎么对上app_logs_visualize_web是分析结果的出口。它不做计算只把 Hive 算好的结果表查出来渲染成页面。常见的做法是用 SpringMVC 或者原生 Servlet 查 MySQL然后把 JSON 返回给前端 JSP/HTML。源码里只有 2 个 JSP 和 1 个 HTML说明报表页面是轻量级的重点是字段映射。我第一次接可视化时最晕的就是页面上叫“活跃用户”代码里查的字段可能叫active_users而且可能来自两张不同的结果表。页面指标数据来源表关键字段今日新增用户dws_new_user_daynew_users今日活跃用户dws_active_user_dayactive_users启动次数dws_start_count_daystart_count平均使用时长dws_avg_duration_dayavg_duration地域分布dws_area_distributionprovince、user_count联调时先确认后端接口返回的 JSON 字段名和页面ajax里的取值一致。我遇到最频繁的问题是后端返回province页面里写的是areaName结果图表渲染出来全是空控制台也不报错看着像数据丢了其实是字段名对不上。4.2 让报表页面动起来数据访问与渲染的最小闭环可视化端连 Hive 或 MySQL 的配置都在visualize_web模块里数据库驱动和连接串要看清楚。如果是直连 Hive连接串通常是jdbc:hive2://localhost:10000/app_logs需要先启动 HiveServer2如果结果已经同步到 MySQL那连接串就是普通的jdbc:mysql://。我用一个简单的接口代码来说明这个闭环。WebServlet(/api/summary) public class SummaryServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { String sql SELECT new_users, active_users, start_count FROM dws_summary_day WHERE dt req.getParameter(dt) ; ListMapString, Object rows JdbcUtil.query(sql); resp.setContentType(application/json;charsetUTF-8); resp.getWriter().write(JSON.toJSONString(rows)); } }dws_summary_day是预处理好的汇总表把当天三个核心指标算进一行查询时按dt参数取某一天。这里有两个细节dt参数一定要校验否则拼 SQL 会存在注入风险响应头里必须显式写charsetUTF-8不然浏览器默认按 ISO-8859-1 解码中文全部变成乱码。页面端拿到 JSON 后用 ECharts 的setOption把数据填进去。第一次联调时先不要急着看图表直接在浏览器地址栏访问/api/summary?dt2024-01-01看到 JSON 再渲染页面这样能快速区分是接口问题还是前端问题。4.3 可视化联调的验证顺序跑可视化端必须遵循一个固定的验证顺序先确认 Hive 结果表有数据再确认接口返回 JSON最后才看页面图表。如果结果表是空的接口写得再好页面也是白屏。我一般用一条简单 SQL 快速验证SELECT * FROM dws_summary_day WHERE dt 2024-01-01 LIMIT 10;有数据再往下走。页面加载出来但图表无数据时打开浏览器开发者工具看 Network 面板检查接口状态码和响应体百分之九十的问题在这一步就定位了。5. 复现与排错我把这套源码跑通前后的五件事5.1 环境版本基线JDK 8 Hadoop 2.7 Hive 2.3 Flume 1.9复现这套源码前环境版本必须先定下来。我的建议是 JDK 1.8、Hadoop 2.7.x、Hive 2.3.x、Flume 1.9.x。这个组合在源码场景下最稳原因有三JDK 8 兼容所有 Maven 依赖不会有 module-info 报错Hive 2.3 的 HiveServer2 对 JDBC 驱动要求宽松老驱动也能连上Flume 1.9 的 TAILDIR 特性稳定。不要一上来就上 Hadoop 3.x 配 Hive 3.x虽然能跑但会遇到 YARN 资源调度差异、Hive 视图查询报错等一堆跟业务无关的环境问题。第一次复现没必要追求新版本跑通链路才是目标。5.2 五个高频踩坑记录坑一collect_web 启动后日志文件不落盘现象接口返回 200但指定目录下没有.log文件生成。 原因日志输出目录不存在或者 Linux 下进程没有该目录的写权限。 解决先执行mkdir -p /opt/logs/app_logs并chmod 777目录再看日志。我在根目录创建目录时经常漏掉权限Java 进程写不进去又不会报错只是静默失败排查了很久才发现是权限问题。坑二Flume TAILDIR 不消费新产生的日志现象日志文件持续增长HDFS 上没有新文件出现。 原因filegroups.f1的正则没匹配上文件路径或者positionFile里记录了错误的文件 ID。 解决先手动往日志目录写一条数据然后看 Flume 日志里有没有Opening file的打印。没有就检查正则有但不出数删掉taildir_position.json重启 Flume 再试。坑三Hive 表能查出数据但分区时间是空的现象WHERE dt 2024-01-01查不到全表扫描有数据。 原因Flume 写入 HDFS 的路径是按服务器本地时间生成的日期目录本地时区或日期格式和 SQL 里写的不一致。比如 Flume 生成的是20240101SQL 里写的是2024-01-01永远对不上。 解决统一日期格式HDFS 目录用%Y%m%d生成Hive 分区字段就存20240101查询条件也用同格式。这个坑属于典型的“两边各写各的”。坑四IP 解析结果全部为空现象报表地域分布一栏是空其他指标都正常。 原因mmdb 文件路径在打包后失效或者拿到的字段名不对subdivisions这个键在某些 IP 段下不存在。 解决先单独写个 Java main 方法测试 IP 解析用固定的公网 IP比如218.30.118.68能出结果再封装 UDF 或者工具类。字段读取前先判空if (provinceMap ! null) {...}。坑五页面 JSON 中文乱码现象接口返回的中文变成???或乱字符图表标题没法看。 原因响应头没设置字符集Tomcat 默认用 ISO-8859-1 编码输出。 解决过滤器里统一加response.setContentType(application/json;charsetUTF-8)同时确认 JSP 头部也声明了 UTF-8。这属于老生常谈但几乎每次复现都会遇到一次。5.3 启动顺序与验证命令整个系统启动是有顺序的顺序错了链路就断。我的标准顺序是HDFS 和 YARN 先起然后 HiveServer2再 Flume Agent最后启动collect_web和visualize_web。每次启动后按下面的检查点过一遍。# 1. 检查 HDFS 上 Flume 是否写入了新目录 hdfs dfs -ls /user/hive/warehouse/app_logs.db/ods_start_log/ # 2. 检查 Hive 分区是否识别 hive -e SHOW PARTITIONS app_logs.ods_start_log; # 3. 手动触发分区修复 hive -e MSCK REPAIR TABLE app_logs.ods_start_log; # 4. 直连 HiveServer2 测试查询 beeline -u jdbc:hive2://localhost:10000/app_logs \ -e SELECT dt, COUNT(DISTINCT device_id) FROM ods_start_log GROUP BY dt;这套检查命令能帮你在 30 秒内确认链路断在哪第一步看 Flume 有没有扛起活来第二步看 Hive 认不认新分区第四步直接验证查询能否出数。把这几条命令存在一个 shell 脚本里每次启动后跑一遍比到处翻日志高效得多。6. 一个提效技巧用模拟客户端批量造数把验证周期从小时压到分钟跑通一次全链路最慢的不是写代码是造数据。手工改配置、一次次点击页面产生日志几十分钟过去 Hive 里还是那几条数据指标根本看不出趋势。我的做法是写一个批量造数的脚本直击collect_web的接口一次性灌入几百台设备的日志验证周期立刻缩短到分钟级。# 批量造数脚本模拟 100 台设备每台产生 3 次启动日志 BASE_URLhttp://localhost:8080/app_logs_collect_web/log/start for i in $(seq 1 100); do DEVICE_ID8643930000$(printf %06d $i) for j in 1 2 3; do curl -s -X POST $BASE_URL \ -H Content-Type: application/json \ -d {\appId\:\app_001\,\deviceId\:\$DEVICE_ID\,\appVersion\:\2.1.0\,\osType\:\android\,\network\:\wifi\,\entry\:\push\,\action\:\startup\} \ /dev/null sleep 1 done done echo 批量造数完成这个脚本里DEVICE_ID是核心用seq生成 100 个固定编号保证设备 ID 稳定且唯一这样 Hive 里算活跃用户、新增用户才能得到符合预期的数字。每条请求之间sleep 1是为了模拟真实节奏同时避免并发太高把 Tomcat 打挂。跑完脚本后不要立刻查 HiveFlume 的 TAILDIR 是轮询读取的给它 12 分钟把日志搬完再执行前面第 5.3 节的检查命令。用这个方式我可以在十分钟内验证日志是否落盘、Flume 是否分区写入、Hive 指标是否符合预期、可视化是否渲染正常。从那以后我每次改完 Hive 口径都强制用脚本重灌一批数据、跑一遍四步检查再去看报表。数据链路这种东西靠肉眼盯日志永远盯不干净只有批量造数才能逼出问题。希望这个习惯能帮到你少走点弯路。最后说一个最实用的边界认知这套源码是离线链路的完整参考不是在线实时系统日志从产生到报表可见有分钟级延迟。如果你想做的业务要求秒级响应那需要引入 Kafka 和 Spark Streaming那是另一个技术方向了。先把离线链路跑通透再往实时方向扩展路会顺很多。本文还有配套的精品资源点击获取
返回列表