实时数仓/实时数据流处理平台(Kafka + Kafka Streams + K8s):从 0 到 1 搭建 App 实时数仓,以「单镜像 + Spring Profile」模式在 K8s 上裂变 40+ 个独立流处理应用,消费埋点与 Canal CDC 两类 Kafka 数据源,产出 10 分钟粒度实时经营看板(InfluxDB)、秒级用户画像(ES 宽表)、实时风控反引流(Redis 频控 + 内容/图片识别 + 自动封禁)、热门内容 TopN 榜单、广告归因回流与监管合规上报;解决窗口最终值结算、at-least-once 幂等、大基数 UV 去重、黑产字符混淆对抗等核心问题,稳定服务百万级用户 5 年。
1. 项目概述
项目名称:App 数仓实时计算平台
项目背景:App 是一款面向中老年群体的短视频图文创作与社交社区的产品(视频/直播/IM 私信/充值打赏),业务上对数据的实时性有四类核心诉求:
- 实时经营看板——活跃用户/设备、注册、直播、推送、支付、资产等核心指标以 10 分钟粒度产出,供运营与管理层实时决策;
- 实时用户画像——把行为日志与数据库变更秒级汇聚成用户宽表画像,支撑推荐、风控与运营查询;
- 实时风控反作弊——对引流导流、批量注册、刷量等黑产行为做秒级发现与自动处置(封禁/验证码);
- 合规与外部数据回流——按监管规范上报明细行为数据(BCP 文件 + FTP),并向广告投放平台回传激活/留存/付费事件用于投放优化。
项目定位:一套以 Kafka 为数据总线、以 Kafka Streams 为计算引擎的实时数仓/实时处理平台,覆盖了实时 ETL、窗口聚合、CDC 增量同步、实时画像、流式风控、榜单计算、合规上报等典型实时场景。
规模量级:
- 单一代码库裂变为 40+ 个独立部署的流处理应用,覆盖 20+ 个 Kafka 数据源 Topic(埋点类 + CDC 类);
- 服务百万级注册用户的实时指标计算与画像更新;
- 代码约 150 个 Java/Scala 类,持续迭代 5 年以上(2018–2023),日均处理全量埋点与数据库变更流。
2. 整体架构
2.1 架构总览图
flowchart TB
subgraph SRC["数据源层"]
A1["App 客户端服务端埋点
report-video / report-im / report-push 等"]
A2["网关 API 访问日志
access / access-api"]
A3["业务 MySQL 集群
用户库 / 资金库 / 审核库 / 话题库"]
end
subgraph MQ["采集与消息层"]
C1["Fluentd -> filebeat"]
C2["Canal
MySQL Binlog 转 JSON FlatMessage"]
B[("Kafka 集群
埋点 Topic 与 binlog Topic
及 Streams 状态 Changelog")]
end
subgraph RT["实时计算层 - Kafka Streams 共 40+ 流应用"]
D1["实时统计看板
10 分钟窗口聚合"]
D2["用户画像
多源 upsert"]
D3["实时风控
频次 / UV / 内容 / 图片"]
D4["实时榜单 TopN
滑动窗口"]
D5["广告归因回流
多平台回调"]
D6["合规上报 WA
BCP 文件"]
D7["DataFinder 回流
攒批上报"]
end
subgraph STORE["存储与服务层"]
E1[("InfluxDB
十分钟看板")]
E2[("Elasticsearch
user_profile 画像")]
E3[("PostgreSQL
风控与统计数据")]
E4[("Redis
去重 / 锁 / 榜单 / 计数")]
E5[("MySQL
业务库与离线数仓")]
E6["推荐服务 / 审核服务
反垃圾服务 / 封禁服务"]
E7["广告平台回调
头条 / 快手 / 腾讯"]
E8["监管 FTP 上报
BCP 与 ZIP"]
E9["火山 DataFinder"]
E10["钉钉告警机器人"]
end
A1 --> C1
A2 --> C1
C1 --> B
A3 --> C2 --> B
B --> D1
B --> D2
B --> D3
B --> D4
B --> D5
B --> D6
B --> D7
D1 --> E1
D2 --> E2
D3 --> E3
D3 --> E4
D3 --> E6
D3 --> E10
D4 --> E4
D5 --> E7
D6 --> E8
D7 --> E9
D2 --> E5
D1 -. 批量写失败或流异常 .-> E10
2.2 部署架构图
flowchart LR
subgraph CICD["CI 与 CD 流水线"]
S1["Maven 构建
单一 Fat Jar"] --> S2["Docker 镜像
streams-appName:tag"] --> S3["Helm 与 K8s"]
end
subgraph K8S["Kubernetes 集群 - namespace colorv"]
subgraph POOL["大数据专用节点池 - nodeSelector big.data=stream"]
P1["Pod: mau-stream
profile=mau"]
P2["Pod: suspectim-stream
profile=suspectim"]
P3["Pod: hotaudio-stream
profile=hotaudio"]
PN["…… 共 40+ 个独立 Deployment"]
end
CM["ConfigMap
生产配置热刷新"] -.-> P1
CM -.-> P2
CM -.-> P3
NAS[("NAS PVC
合规文件共享盘")] -.-> PN
end
S3 --> P1
S3 --> P2
S3 --> P3
S3 --> PN
部署模式核心思想:一个代码库、一个 Fat Jar、一个通用镜像,借 Spring Profile 在 K8s 上裂变为几十个独立的流处理应用。
- 每条流 = 一个
@Configuration + @EnableKafkaStreams + @Profile("xxx")的拓扑声明类; - 容器启动命令固定为
java -jar -Dspring.profiles.active=prod,<appName> app.jar,一个 Deployment 只激活一个拓扑; - 每个应用以
STREAM_NAME=<appName>-stream作为 Kafka Streams 的application.id→ 独立消费组、独立状态存储(RocksDB State Store + Changelog)、独立扩缩容与滚动升级,单条流的故障/重启完全不影响其他流; - Helm Chart 用一个模板遍历
values.yaml中的应用列表批量生成 Deployment,构建脚本支持按应用名单独构建、发版、灰度滚动; - 资源规格:单 Pod 限制 1C/2G,调度到专用大数据节点,关闭 Service Mesh sidecar 注入以降低流处理时延。
3. 技术栈
| 分类 | 技术选型 | 用途说明 |
|---|---|---|
| 流计算引擎 | Kafka Streams(RocksDB 5.18 状态存储) | 40+ 条实时拓扑:窗口聚合、TopN、流表 join、CDC 处理 |
| 消息总线 | Kafka | 埋点/日志/CDC 统一数据总线 + Streams 状态 changelog |
| CDC 采集 | Alibaba Canal(FlatMessage JSON 协议) | MySQL binlog → Kafka,数据库变更实时捕获 |
| 埋点与其他数据采集 | Fluentd -> filebeat | 服务端将埋点与接口等日志数据上报 Fluentd |
| 应用框架 | Spring Boot 2.3 + Spring Kafka(@EnableKafkaStreams) | 拓扑声明为 Spring Bean,Profile 隔离多应用 |
| 设计语言 | Java、Shell | Java 负责主逻辑代码编写,Shell 负责特定逻辑脚本实现 |
| 时序存储 | InfluxDB(批量写 + 2 周保留策略) | 「十分钟看板」全部实时指标 |
| 搜索/画像 | Elasticsearch 7.6(RestHighLevelClient) | user_profile 用户画像宽表,upsert + scroll |
| 缓存/共享状态 | Redis(Jedis 多连接池) | 去重 Set、频控计数、分布式锁、榜单、key 过期事件 |
| 关系库 | MySQL(业务库×多套)、PostgreSQL(统计库) | 风控落库、幂等 upsert(ON CONFLICT) |
| 对象存储/内容安全 | 七牛云(图片 hash、OCR、鉴黄 API) | 嫌疑图片识别、图中文字抽取 |
| 配置中心 | Spring Cloud Kubernetes(ConfigMap) | 生产配置 event 模式监听热刷新 |
| 容器化 | Docker + Helm + Kubernetes | 40+ Deployment 批量编排,NAS PVC 共享盘 |
| 可观测性 | 钉钉机器人(HMAC 加签)、Kafka Streams 状态机联动探针 | 异常告警 + Pod 自愈 |
4. 核心原理与逻辑(按数据链路展开)
4.1 数据接入层:两类数据源
| 类型 | Topic 举例 | 生产方 | 消息格式 |
|---|---|---|---|
| 埋点/日志流 | report-video、report-im、report-push、report-login、access-api、report-robot 等 | App 客户端 / 业务服务端 / 网关 | JSON(Snake_case) |
| CDC 流 | binlog-master、binlog-money、binlog-topic、binlog-video-texts 等 | Canal 监听各 MySQL 业务库 | Canal FlatMessage JSON |
Canal 消息解析模型(CDC 链路的统一入口):每条消息携带 database/table(库表路由)、type(INSERT/UPDATE/DELETE)、data(变更后行数据数组)、old(变更前列值)、es(MySQL 执行时间)与 ts(Canal 处理时间)。计算层统一模式为:JsonSerde(FlatMessage) 反序列化 → 按 库.表 过滤路由(branch 分流)→ flatMapValues(getData()) 摊平多行 → 按事件类型处理。其中 data 与 old 的差分是资产增量对账、订单状态迁移判断(如「支付回调字段由 0 变非 0」)的关键。
4.2 实时统计看板(十分钟看板)
模式:stream → filter/map → groupByKey → windowedBy(10 分钟滚动窗口) → count/reduce/aggregate(Materialized 命名状态库) → foreach 写 InfluxDB。
典型指标与去重手段:
| 指标 | 数据源 | 计算方式 |
|---|---|---|
| 活跃用户/设备(按 OS) | access-api | 时间戳对齐 10 分钟桶,Redis Set SADD/SCARD 精确去重,仅在集合新增成员时打点 |
| 直播大盘(房间数/主播 UV/时长/送礼钻石/观看 PV/UV) | report-live + report-im 双源合流 | 窗口 aggregate 到自定义累加器,窗口状态内 HashSet 做 UV 去重 |
| 推送全链路漏斗(触发→厂商受理→到达→点击) | report-push | 一个窗口聚合出四级漏斗计数,仅信任可信厂商回执防刷量 |
| 全站虚拟资产余额 | binlog-money(Canal) | 见 4.6「增量对账」 |
| 实验分桶流量(A/B 实验) | report-experiment | 1 分钟窗口内 HashSet 精确去重统计各实验分桶活跃数 |
| 注册/设备激活/支付订单/审核时效/机器人任务量 等 | binlog / 各 report topic | 同模式窗口聚合 |
4.3 实时用户画像
flowchart LR
subgraph 多源数据
B1[access-api 访问日志] -->|ip| M
B2[report-video 地理事件] -->|省市/经纬度| M
B3[binlog 手机账号表] -->|phone| M
B4[binlog 实名认证表] -->|姓名/证件| M
B5[binlog 资金库资产表] -->|钻石/金币/金豆| M
B6[access-api 老用户回访] -->|last_active/app_version/渠道| M
end
M[ES user_profile 索引
doc_id = user_id
doc 局部更新 + 冲突重试]
M --> R1[风控:用户价值/风险判定]
M --> R2[合规上报:IP/经纬度/行政区划补全]
M --> R3[运营查询]
- 宽表式画像:不建大宽表 Schema,而是各维度独立事件流按 user_id 持续
upsert到同一 ES 文档(UpdateRequest.doc局部合并 +retryOnConflict),单维度更新互不覆盖、天然幂等可重放; - 「最后活跃/版本/渠道」类弱实时字段用 按天窗口 + reduce 取最新值 的方式降频更新(每用户每天至多一条);
- 读取侧按规模自适应:<1000 用户走 terms 查询,≥1000 走 scroll 滚动查询,多用户多字段用 msearch 批量。
4.4 实时风控(反引流/反作弊)
风控是本项目逻辑最复杂、对抗性最强的部分。总体架构为「5 路信号采集 → 统一嫌疑服务 → 分级处置」:
flowchart TB
subgraph SIG["五路信号采集 - 各自独立流应用"]
S1["接口频次
access-api 高危接口"]
S2["IM 私信
腾讯云回调"]
S3["评论频次
report-video"]
S4["文本内容
评论 / 昵称 / 签名 / 私信"]
S5["图片内容
头像 / 相册 / 私信图 / 话题封面"]
end
subgraph CORE["统一判定 SuspectContentService"]
J1["字符反混淆归一化
花体数字 / 全角 / 形近字 / 中文数字"]
J2["正则抽取嫌疑关键词
微信号 / QQ / 手机号 / URL"]
J3["图片 hash 黑白名单
与 OCR 图中文字"]
J4["PG 幂等落库
key 加 user 加 md5 唯一,count 累加"]
J5["阈值分级
全站与单用户两级阈值"]
end
subgraph ACT["分级处置"]
O1["验证码挑战
弱信号 - 评论频次"]
O2["系统级全站封禁
强信号 - 高危渠道包 / 黑词 / 黑图"]
O3["钉钉告警与黑词库回灌
人工在回路定性"]
end
S1 --> J4
S2 --> J4
S3 --> J4
S4 --> J1
J1 --> J2
J2 --> J4
S5 --> J3
J3 --> J4
J4 --> J5
J5 --> O1
J5 --> O2
J5 --> O3
关键设计点:
- Redis Key + TTL 即风控窗口:分钟级(Set 统计私信触达的不同收件人 UV,阈值 60)、日级(UV 500 / 接口 PV 2000/1000/500 按接口分级)——风控计数不依赖 Kafka Streams 窗口,跨实例共享、天然滚动过期;
- 新老用户水位线:用自增 user_id 水位(如「3 个月前最小 user_id」)区分新老账号,老用户豁免频控,聚焦新注册黑产小号,同时结合设备/渠道画像(特定老安卓版本 + 应用商店渠道包)直接封禁;
- 「先观测、后定性、再自动封禁」的人在回路机制:新出现的联系方式只累计不封禁;被运营/告警定性为黑产的词/图进入黑名单后,再传播者自动封禁(图片用七牛 hash 跨用户复用已定性违规图);普通内容要求单用户历史累计超阈值才封,避免误伤;
- 文本反混淆对抗:黑产用花体 Unicode 数字、全角字符、中文数字、形近字拼联系方式,系统先做字符归一化再正则抽取(微信号样式 / 5–20 位连续数字 / URL),长文本按 100 字切片送检防截断漏匹配;
- 行为宽表:另有一路把「每用户每小时对多少不同对象点赞/评论/关注/私信」的去重 UV 写入 PG 小时表,供离线策略分析。
4.5 实时榜单 TopN(热门音乐/搜索热词/场景词)
两段式窗口聚合 + 自研 Top 容器 + 原子发布:
第一层:按内容 key 分组 → 6 小时窗口 / 1 小时滑动 hopping window
→ aggregate 出 (曝光数, 使用数) [WindowStore]
第二层:以「窗口结束时间」为 key → 聚合进自研 Top<T> 容器
(TreeSet 定长有序、支持窗口滑出时撤回 remove) [KeyValueStore]
输出: 仅发布「窗口结束距今 1 小时内」的榜单元数据
→ Redis 临时 list 写满后 rename 原子替换正式 key
- 自研
Top<T>容器解决「TopN 状态需要可序列化恢复 + 窗口过期撤回」两个问题,配套自定义 Deserializer 在状态恢复时重建容器; - 榜单排序以真实使用量为主权重、曝光量为次序,防刷量;
- 临时 key +
rename保证读侧永远看不到半成品榜单。
4.6 CDC 增量对账(全站资产总额统计)——最典型的窗口工程难点
问题:Kafka Streams 的 windowedBy(...) 后的输出不是窗口关闭时触发一次,而是以秒级节奏持续增量触发;且把「新增」和「减少」拆成两条支流后,并发回读时序库上一窗口值存在竞态。
解法(当前窗口只累加、下一窗口做结算,Redis 单线程做仲裁):
sequenceDiagram
participant K as Kafka Streams 窗口聚合
participant R as Redis(单线程仲裁)
participant I as InfluxDB
Note over K: 窗口 N(10 分钟)
K->>R: add 流:data 新值窗口求和 → key add_N
K->>R: sub 流:old 旧值窗口求和 → key sub_N
Note over K: 进入窗口 N+1(start == N.end 对齐)
K->>R: setnx 占位(保证只结算一次)
K->>R: 读 final_N-1 + add_N − sub_N
K->>I: 写入全站资产总额(10 分钟粒度)
K->>R: 更新 final_N,清理 N 周期中间 key
配合「实时先行、离线 T+1 校正差值」的口径对账思路,解决了实时值与离线基准的衔接。同一模式(窗口对齐 + Redis 缓存 + 下一窗口结算)也用于「视频发布→审核入库平均时长」等均值类指标。
4.7 广告投放归因与事件回流
归因链路:广告点击日志(含设备标识 only_key / idfa / udid + 计划/创意/广告主/投放端维度)落业务库 → 实时流以设备标识反查点击记录富化投放维度(内置投放端/广告主/代理商字典翻译)→ 按维度窗口计数产出「激活归因」「新客当天创作转化」实时指标。
事件回流链路(激活/次留/付费回传给投放平台,用于平台侧 oCPX 优化):
- 次留回流:识别「昨日首次访问、今日仍活跃」的用户 → 查 Redis 中缓存的该设备回调 URL → 按回调 URL 域名路由到不同平台(头条/快手/腾讯/友盟),以各平台约定的
event_type组装参数回传; - 付费回流:监听订单表 binlog 的支付成功状态迁移(
old/data差分判定)→ 回传付费事件; - 幂等保障:回调成功即删除 Redis 回调缓存 key,确保每台设备每类事件只回传一次;失败钉钉告警。
4.8 监管合规数据上报(WA 链路)
按监管规范上报 15 类明细行为数据(注册/登录/好友/播放/评论/点赞/关注/分享/私信/支付/充值/下单/打赏/搜索),每条记录需补全行政区划(GB/T 2260 六位码,本地字典 + 画像省市区转换)、IP(转长整型)、经纬度等溯源字段。
链路设计(典型「流式落盘 + 定时打包上传」架构):
Canal binlog / 埋点 topic
→ 流内过滤富化(CSV 配置驱动的「监管字段 ← 业务库.表.字段」映射,非硬编码)
→ 追加写 BCP 数据文件(NAS 共享盘,多 Pod 文件锁防并发;单文件 5000 行滚动切分)
→ 凑够 N 个文件 或 Redis 触发 key 过期(keyspace notification 监听,保证至少每 5 分钟一包)
→ setnx 抢锁(多节点只有一个执行)
→ shell 清洗 + 从 ES 画像批量补列 + 横向合并
→ 生成索引 XML(GAB_ZIP_INDEX,XStream 序列化)→ 打 ZIP
→ 宿主机 crontab 每分钟经 VPN + FTP 上传监管侧(上传前自动检测 VPN 健康、失败自愈重启)
4.9 服务端埋点回流(DataFinder)
把服务端日志与订单 binlog 二次加工成标准埋点事件(user/header/events 三段式协议),回流到增长分析平台,补齐客户端埋点覆盖不到的服务端行为(VIP/钻石商品下单与支付成功等商业化事件)。
- 10 秒短窗口攒批:按 userId 分组、窗口内字符串拼接聚合成批后批量上报,显著降低外呼请求数(批量接口有条数上限);
- 10 线程异步 +
@Retryable指数退避重试,失败钉钉告警; - 客户端事件按「事件 ID 白名单(Redis Set 动态配置)」过滤后回流。
4.10 实时推荐协同
推荐算法本体在外部 Python 推荐服务,本平台承担实时喂料与门槛判定:
- 话题/图文内容 binlog → 过滤(审核通过/未删/白名单作者/正文长度)→ HTTP 调推荐服务加入随机池;运营标记上榜 → 加入精选池;删除/下架 → 移出;每个动作成败打点监控;
- 用户登录 → 清空推荐会话(避免重复曝光);
- 对外暴露「视频是否可推荐」判定 HTTP 接口:视频存在性 + 非模板默认标题 + 作者白名单 + 封面图像质量判定(调用图像服务检测黑边/画质)四重门槛。
5. 关键技术难点与解决方案
| # | 难点 | 解决方案 |
|---|---|---|
| 1 | 几十条实时任务需要独立部署、独立扩缩容、故障隔离 | 单镜像 + Spring Profile 裂变:一份代码/镜像按 Profile 部署为 40+ 个 K8s Deployment,各自独立消费组与状态存储,Helm 单模板批量编排 |
| 2 | Kafka Streams 窗口输出为持续增量触发而非窗口关闭触发,无法直接取窗口最终值 | 「当前窗口只累加写 Redis,下一窗口用窗口边界对齐后结算上一窗口」模式 + Redis 单线程特性仲裁并发竞态 |
| 3 | at-least-once 语义下流处理副作用可能重复 | 全链路幂等:ES doc 局部更新 + 冲突重试、MySQL/PG ON CONFLICT upsert、榜单临时 key + rename 原子替换、广告回调「成功即删 key」、多节点动作 setnx 抢锁 |
| 4 | 流任务「假活」——进程在但拓扑已停 | Kafka Streams 状态机联动 K8s 探针:拓扑进入 RUNNING 才创建健康标记文件,liveness 探针检查该文件,异常自动重启 + 钉钉告警(含业务包堆栈裁剪) |
| 5 | 黑产用花体/全角/形近字符混淆联系方式 | 字符反混淆归一化(数学 Unicode/圈码/中文数字等统一映射)+ 正则抽取 + 图片 OCR + hash 黑名单的文本/图片双重识别,人在回路黑名单自动封禁 |
| 6 | 大基数 UV 去重 vs 2G 内存 Pod 限制 | 去重分流:跨实例大基数用 Redis Set 精确去重,窗口内中小基数用状态存储内 HashSet(随 changelog 持久化可回放) |
| 7 | 毒消息导致整条流崩溃 | 全局 LogAndContinueExceptionHandler 跳过脏数据;关键链路反向为之——主动不捕获系统异常让流阻塞以暴露问题 |
| 8 | 多节点定时/事件动作重复执行 | 注解式分布式锁(AOP + setnx + Lua 脚本校验 requestId 释放)+ Redis keyspace 过期事件触发 + setnx 抢占 |
| 9 | 高吞吐下的外部依赖调用 | 消费参数调优(单次 poll 上限/提交间隔/最大处理时长约束)、外呼全部设短超时、慢操作(图片审核延迟 60s 等待上传完成)异步化、InfluxDB/上报接口批量写 |
6. 项目成果与价值
- 建成覆盖「埋点 + CDC」双数据源的统一实时数仓,支撑活跃/直播/推送/支付/资产/审核时效/投放等全业务线 10 分钟粒度实时看板,替代 T+1 离线报表成为运营决策主入口;
- 秒级用户画像(ES 宽表)成为推荐、风控、运营的统一用户数据底座;
- 实时风控体系(五路信号 + 分级处置 + 人在回路黑名单)有效压制中老年社区高发的引流导流黑产,支持验证码/系统封禁自动处置;
- 打通广告投放归因—回流闭环(激活/次留/付费回传多平台),直接服务买量优化;
- 完成监管合规上报工程建设(15 类数据集、BCP/ZIP/FTP/VPN 全链路自动打包上传),满足监管审计要求;
- 平台稳定运行 5 年,后期主导按业务优先级分批下线与迁移(埋点链路迁移至日志服务方案),完成技术栈的新老交替收尾。

发表评论