TL;DR
- 场景:用启动日志/事件日志在离线数仓统计新增、活跃(DAU/WAU/MAU)、留存。
- 结论:采集侧用 Flume 1.8+ Taildir 监控日志落 HDFS 分区,数仓分层承接口径与聚合。
- 产出:一套可复用的 Taildir→HDFS Sink 配置模板 + 指标口径对齐要点 + 常见故障排查清单。
需求分析
会员数据是后期营销的很重要的数据,网店会专门针对会员进行一系列营销活动,电商会员一般门槛较低,注册网站即可加入,有些电商平台的高级会员具有时效性,需要购买的VIP会员卡或一年内消费达到多少才能成为高级会员。 计算指标 新增会员:每次新增会员数 活跃会员:每日、每周、每月的活跃会员数 会员留存:1、2、3日会员的留存数,1、2、3日的会员留存率
指标口径业务逻辑
会员统计指标详解
会员识别标准
- 会员定义:以移动设备为唯一识别标准,每台独立设备视为一个会员。具体识别方式:
- Android系统:通过设备IMEI号(国际移动设备识别码)进行识别,每部Android设备具有唯一的15位IMEI编码
- iOS系统:通过OpenUDID(开放统一设备标识符)进行识别,这是iOS设备的通用唯一标识符
- 特殊情况处理:同一用户的多台设备会分别计算为不同会员;设备刷机或恢复出厂设置后仍视为同一会员
活跃会员定义
- 日活跃会员(DAU):在自然日内(00:00-23:59)至少打开应用一次的独立设备,无论打开次数多少都计为1个活跃会员
- 示例:某设备在一天内打开应用5次,仍只计为1个DAU
- 周活跃会员(WAU):在自然周(周一至周日)内至少启动应用一次的独立设备
- 统计周期:以ISO标准的周计算方式为准
- 月活跃会员(MAU):在自然月(1日至月末)内至少启动应用一次的独立设备
- 跨月统计:每月1日重新计算
会员活跃率计算
- 日活跃率(DAU率):计算公式 = 日活跃会员数 ÷ 总注册会员数 × 100%
- 应用场景:衡量每日用户参与度
- 周活跃率(WAU率):计算公式 = 周活跃会员数 ÷ 总注册会员数 × 100%
- 特点:反映用户的中期粘性
- 月活跃率(MAU率):计算公式 = 月活跃会员数 ÷ 总注册会员数 × 100%
- 行业基准:通常MAU率在10-20%为健康水平
新增会员统计
- 新增会员定义:设备首次安装并启动应用的会员
- 防重复计算机制:
- 卸载后重装不计为新会员
- 换机但登录同一账号不计为新会员
- 防重复计算机制:
- 统计维度:
- 日新增:自然日内首次使用的设备数
- 周新增:自然周内首次使用的设备数
- 月新增:自然月内首次使用的设备数
- 数据应用:用于评估渠道推广效果
留存分析
- 留存会员:在初始时间段(如某日/周/月)新增的会员,在后续时间段仍保持活跃
- 示例:1月1日新增100个会员,1月8日仍有40个活跃,则这40个为7日留存会员
- 留存率计算:
- 计算公式 = 留存会员数 ÷ 初始新增会员数 × 100%
- 常见指标:
- 次日留存:第一天新增用户在第二天仍活跃的比例
- 7日留存:第一周新增用户在第二周仍活跃的比例
- 30日留存:第一个月新增用户在第二个月仍活跃的比例
- 分析意义:衡量产品粘性和用户忠诚度的重要指标
已知条件是:
- 明确了需求
- 输入:启动日志、事件日志
- 输出:新增会员、活跃会员、留存会员
- 日志文件:ODS、DWD、DWS、ADS(输出)
下一步做什么? 数据采集:日志文件 到 Flume 到 HDFS 到 ODS
日志数据采集
原始日志数据(一条启动日志)
2020-07-30 14:18:47.339 [main] INFO com.ecommerce.AppStart - {"app_active":{"name":"app_active","json":{"entry":"1","action":"1","error_code":"0"},"time":1596111888529},"attr":{"area":"泰安","uid":"2F10092A9","app_v":"1.1.13","event_type":"common","device_id":"1FB872-9A1009","os_type":"4.7.3","channel":"DK","language":"chinese","brand":"iphone-9"}}
数据采集的流程:
选择Flume作为采集日志数据的工具:
- Flume1.6 无论是 Spooling Directory Source、Exec Source均不能很好的满足动态实时收集的需求
- Flume1.8+提供了一个非常好的Taildir Source,使用该Source可以监控多个目录,对目录中写入的数据进行实时采集。
taildir source
taildir source特点
- 使用正则表达式匹配目录中的文件名
- 监控的文件中,一旦有数据写入,Flume就会将信息写入到指定的Sink
- 高可靠,不会丢失数据
- 不会对跟踪文件有任何处理,不会重命名也不会删除
- 不支持Windows,不能读二进制文件,支持按行读取文本文件
tail source配置
- positionFile:配置检查点文件的路径,检查点文件会以JSON格式保存已经读取文件的位置,解决断点续传的问题
- filegroups:指定filegroups,可以有多个,以空格分隔(taildir source可以同时监控多个目录中的文件)
- filegroups.f1:配置每个filegroup的文件绝对路径,文件名可以用正则表达式匹配
HDFS Sink配置
HDFS Sink都会采用滚动生成文件的方式,滚动生成文件的策略有:
- 基于时间,hdfs.rollInterval 30秒
- 基于文件大小,hdfs.rollSize 1024字节
- 基于Event数量,hdfs.rollCount 10个event
- 基于文件空闲时间,hdfs.idleTimeout 0
- 0 禁用
- minBlockReplicase,默认值与HDFS副本数一致。设为1是为了让Flume感知不到HDFS的块复制,此时其他的滚动方式配置(时间间隔、文件大小、events数量)才不会收到影响
Agent配置
我把配置放在了这里:
cd /opt/wzk
cd flume-conf
vim flume-log2hdfs1.conf
配置的内容如下所示:
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# taildir source
a1.sources.r1.type = TAILDIR
a1.sources.r1.positionFile = /opt/wzk/conf/startlog_position.json
a1.sources.r1.filegroups = f1
a1.sources.r1.filegroups.f1 = /opt/wzk/logs/start/.*log
# memorychannel
a1.channels.c1.type = memory
a1.channels.c1.capacity = 100000
a1.channels.c1.transactionCapacity = 2000
# hdfs sink
a1.sinks.k1.type = hdfs
a1.sinks.k1.hdfs.path = /user/data/logs/start/%Y-%m-%d/
a1.sinks.k1.hdfs.filePrefix = startlog.
a1.sinks.k1.hdfs.fileType = DataStream
# 配置文件滚动方式(文件大小32M)
a1.sinks.k1.hdfs.rollSize = 33554432
a1.sinks.k1.hdfs.rollCount = 0
a1.sinks.k1.hdfs.rollInterval = 0
a1.sinks.k1.hdfs.idleTimeout = 0
a1.sinks.k1.hdfs.minBlockReplicas = 1
# 向hdfs上刷新的event的个数
a1.sinks.k1.hdfs.batchSize = 1000
# 使用本地时间
a1.sinks.k1.hdfs.useLocalTimeStamp = true
# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
对应的截图如下所示:
Flume的优化配置
使用如下的指令,启动Agent进行测试:
flume-ng agent --conf-file /opt/wzk/flume-conf/flume-log2hdfs1.conf -name a1 -Dflum
e.roog.logger=INFO,console
启动后的截图如下所示:
查看刚才的Flume窗口:
查看HDFS的内容:
错误速查
| 症状 | 根因定位 | 修复 |
|---|---|---|
| Taildir 不采集、HDFS 无新文件 | filegroups 正则没匹配到文件/路径写错 | Flume 日志搜 TaildirSource、看监控到的文件列表;核对 /opt/wzk/logs/start/.*log 是否命中改正绝对路径与正则;先用更宽正则验证(如 .*)再收紧 |
| positionFile 不更新/采集断续 | positionFile 无权限或目录不存在 | 看 positionFile 是否生成 JSON、是否增长;检查 Flume 运行用户权限 给 /opt/wzk/conf/ 写权限;确保 positionFile 路径可创建 |
| HDFS 写入失败、反复重试 | HDFS 权限/NameNode 连接/路径不存在 | Flume 日志搜 hdfs / Permission denied / Failed;HDFS 侧看目标目录权限 创建目录并授权;修正 hdfs.path;确保 HDFS 可达与认证配置一致 |
| 文件迟迟不滚动、HDFS 目录空 | rollSize/rollInterval/rollCount 都被禁用或条件不触发 | 你配置了 rollSize=32M 且 interval/count=0;低流量时难触发 低流量测试加 rollInterval(如 60/300 秒)或调小 rollSize |
| 内存飙升/Channel OOM/吞吐抖动 | memory channel 容量过大、下游 HDFS 写慢导致堆积 | 观察 channel size、GC、Sink 写入耗时;看是否持续 backlog 降低 capacity;加大 Sink 并发/优化 HDFS;必要时换 file channel 提升可靠性 |
| 数据重复/缺失(断点后异常) | positionFile 被清空/回滚;日志轮转策略与 Taildir 追踪不一致 | 对比源日志行数与 HDFS 落地行数;检查 positionFile 历史 保持 positionFile 稳定持久;避免频繁删改;日志轮转用追加模式更稳 |
| 分区日期不对(按 UTC/时间错位) | 时间戳来源与 useLocalTimeStamp 不一致 | 对比写入目录日期与本地时间;检查事件时间字段 开启 hdfs.useLocalTimeStamp=true(你已开);若用事件时间分区需改用拦截器/自定义分区策略 |
| HDFS 写入滚动行为异常(看起来不按配置) | minBlockReplicas 未设导致受副本/块策略影响 | Flume 日志搜 minBlockReplicas 相关;检查 HDFS 副本数 设 hdfs.minBlockReplicas=1(你已设),并确认集群副本/安全模式状态 |
| Agent 启动参数报错/找不到组件 | -name 应为 -n 或命令被换行截断(示例里 -Dflum 断行) | 直接看启动报错;检查命令是否被 shell 分行 使用标准启动: flume-ng agent -n a1 -c /opt/wzk/flume-conf -f /opt/wzk/flume-conf/flume-log2hdfs1.conf -Dflume.root.logger=INFO,console 并确保一行执行 |
其他系列
🚀 AI篇持续更新中(长期更新)
AI炼丹日志-29 - 字节跳动 DeerFlow 深度研究框斜体样式架 私有部署 测试上手 架构研究,持续打造实用AI工具指南! AI研究-132 Java 生态前沿 2025:Spring、Quarkus、GraalVM、CRaC 与云原生落地
💻 Java篇持续更新中(长期更新)
Java-218 RocketMQ Java API 实战:同步/异步 Producer 与 Pull/Push Consumer MyBatis 已完结,Spring 已完结,Nginx已完结,Tomcat已完结,分布式服务已完结,Dubbo已完结,MySQL已完结,MongoDB已完结,Neo4j已完结,FastDFS 已完结,OSS已完结,GuavaCache已完结,EVCache已完结,RabbitMQ已完结,RocketMQ正在更新... 深入浅出助你打牢基础!
📊 大数据板块已完成多项干货更新(300篇):
包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈! 大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT案例 详解