资讯动态

从零构建实时事件分析系统:需求拆解、架构选型与性能调优

发布时间:2026/10/14 9:20:18 来源:尧图企业网站定制
接手这个任务的时候我手里只有一个需求文档。上面既没有产品说明也没有原型链接就孤零零三个字母rea。我盯着它看了十分钟脑子里转过了各种可能——realresearchreact后来才发现这种极简项目代号在内部系统里太常见了它背后往往藏着一整条业务线。今天就把这个代号从模糊到落地的全过程完完整整拆给你看包括需求拆解、架构选型、核心代码、性能调优和线上踩坑希望对正在做实时数据类项目、或者说刚接手一个说不清需求的任务的朋友有点帮助。这个项目最终被定义为一个实时事件分析系统Real-time Event Analytics也就是把客户端埋点上报的行为事件实时采集、实时计算、实时展示到运营看板上。它适合的读者不只是写代码的人还包括产品、运营和数据同学——你会知道一个看起来简单的数据看板背后到底经历了什么。1. 先别急着写代码把rea这三个字母拆清楚1.1 代号背后藏着一张需求地图内部项目喜欢用缩写代号核心原因是沟通成本低。两个人在走廊碰面说rea 那个项目怎么样了比说就是那个实时事件分析系统要省事得多。但这玩意儿有个致命问题代号传递过程会丢失大量上下文。等需求文档到了开发手里通常只剩一个光秃秃的名字所有背景全要靠人去补。我拿到rea的第一反应不是去猜而是去找给了这个代号的人。找到需求方之后一段十分钟的对话把模糊的东西慢慢凿开了。对方说他们的运营同学每天要看用户行为数据来调整策略但报表系统是 T1 的第二天早上才能看到昨天的数据太慢了。他们想要一个看板能看到当前时刻的 PV、UV、点击率、转化漏斗延迟在分钟级就满足。这段对话非常关键。它让我知道了四件事数据来源是客户端埋点实时程度是分钟级而不是秒级指标类型是统计类而不是明细查询服务对象是运营而非外部客户。这四个限定直接影响了后面的所有技术选型——比如分钟级意味着窗口计算可以选 1 分钟滚动窗口秒级的话方案就会完全不一样。还有一件事要提前确认数据规模。需求方说预估每天 1 亿条事件峰值每秒大概 5000 到 1 万条。虽然这个数字听着唬人但对于成熟的实时链路来说其实是小场面这也让我敢在方案里直接用比较轻量级的组件组合而不是一上来就上重型的计算集群。拿到这些信息之后我心里已经对rea有了一个初步判断它就是一套标准的行为事件采集 实时聚合 可视化展示的系统。和市面上几乎所有流量分析工具的底层逻辑一致区别只是规模和场景。1.2 用一份清单把模糊需求变成可交付定义需求沟通完之后我习惯把结论输出成一份可交付定义清单用最小篇幅把项目的边界锁死。这样做的原因是防止后续扯皮——开发最怕的不是需求多而是需求在开发过程中反复横跳。清单长这样数据源客户端埋点事件通过 HTTP 上报JSON 格式实时性从事件发生到看板可见延迟目标 90% 在 1 分钟内核心指标PV、UV、点击率、转化漏斗下单成功率按分钟/小时/天三个粒度分析维度页面路径、用户来源渠道、设备类型、省份地域写入场景数据只需要追加不需要对历史事件做删改查询场景95% 是大屏和报表的聚合查询没有明细查询需求非目标不做用户画像、不做推荐、不做规则引擎其中非目标这一栏特别重要。模糊的需求往往会被人不断加戏把无关的东西往里面塞。提前写明什么不做能挡住不少后续的需求蔓延。口径确认也是一开始就要做的事情。同样一个UV是自然日去重还是滚动 24 小时去重点击率的分子分母各是什么事件活跃用户按什么标准判定这些口径如果不事先敲定后面每次对数据都会吵一遍。我们当时的做法是直接在清单上写死UV 按自然日 用户 ID 去重点击率 点击事件数 / 曝光事件数活跃用户 当日产生过任意事件且日志中 session 时长超过 10 秒的用户。这些定义不一定是最优的但一定是最明确的。实时数仓项目里最贵的从来不是服务器而是对口径的时间。2. 架构设计一条实时事件链路是怎么长出来的2.1 端到端数据流向对实时事件分析系统来说数据流向通常是一个固定的管道模式客户端埋点 SDK → 上报网关 → 消息队列Kafka→ 流处理引擎Flink→ 存储层ClickHouse Redis→ 查询 API → 大屏/报表这个链路里每一层都有自己的职责少一个或者合并错了都会出问题。比如有些团队图省事把 Kafka 都省了埋点数据直接打到 Flink结果上游一抖动整个计算就断了。消息队列在这个场景里起的不只是缓冲作用它本质上是在给系统装一个蓄水池——流量洪峰来了水池帮你扛住下游处理不过来的时候可以慢慢消化。上报网关是数据进入系统的第一道门。它的职责很纯粹接收、校验、转发。校验包括格式对不对、必填字段有没有、时间戳是不是合理值。网关本身不写任何业务逻辑不负责去重、不负责补全维度只做最基础的格式检查。这一步越简单越好因为它要扛的是全部并发流量。Flink 是整个链路的计算大脑。它从 Kafka 里消费事件做窗口聚合、去重、清洗然后把结果写到 ClickHouse 和 Redis。这里有个容易犯的错把清洗逻辑也放到网关做觉得能少传输一点数据。实际上网关的算力非常宝贵让它做复杂计算会导致整体的吞吐上不去算力应该集中在 Flink 这一层。存储层采用了双写。ClickHouse 存所有聚合好的明细结果用于随时多维查询和报表刷新Redis 存最近一小时的热点指标供大屏接口直接读取这样大屏轮询不会把 ClickHouse 打垮。两层各司其职一个管深度查询一个管高频查询。2.2 事件模型设计一切分析的基础事件模型是整个系统里最不该被低估的部分。很多人上来就写代码结构设计得乱七八糟后面每一个指标都要写奇怪的 SQL 去适配。数据建模的功夫花在前面后面能省一个月的调优时间。一个行为事件最基础的四个要素是谁who、什么时候when、在什么地方where、做了什么what。落到 JSON 里长这样{ event_id: 8f14e45f-ea2a-4f2a-8f2a-1f9a0b2c3d4e, event_type: click, user_id: u_100023, device_id: d_9f8a, ts: 1699999999999, event_time: 2023-11-15 10:30:00, page: home, props: { button: confirm_order, product_id: sku_233 }, context: { app_version: 3.2.1, channel: xiaohongshu, device_type: android, province: zhejiang, network: wifi } }有几个设计要点值得单独拿出来说。event_id 必须全局唯一它是后续去重的唯一依据生成方式建议在客户端直接生成 UUID 而不是到服务端再生成——否则网络重试会产生重复数据。ts 是事件发生时的毫秒级时间戳注意一定要用毫秒而不是秒否则后面按秒做窗口的时候就尴尬了。props 是可变属性不同事件类型会有不同的字段context 是公共属性所有事件都会携带包括版本、渠道、设备等这些公共属性在分析时经常用来做维度下钻。时间字段我故意放了两个一个纯数字 ts一个人可读的 event_time。你说冗余也行但这能帮运营同学排查问题时省很多事——他们看不懂毫秒时间戳但直接看 event_time 就知道是不是凌晨的数据出了问题。2.3 技术选型背后的取舍选定 Kafka Flink ClickHouse 这套组合我有一堆踩过坑的理由。Kafka 作为消息队列是没什么悬念的选择它的吞吐量极高、生态成熟而且对乱序和分区有非常清晰的支持。日志型数据是它的主场每秒几十万条轻轻松松。Flink 的选择我犹豫过。另一个选项是 Spark Streaming但有 3 个关键场景让我最终选了 Flink第一是事件时间处理Flink 对乱序事件的处理非常成熟内置水位线机制这是 Spark Streaming 的弱项第二是精确一次语义实时计算最怕重复消费Flink 可以通过 Checkpoint 机制保证端到端精确一次第三是窗口类型丰富滚动、滑动、会话窗口都有现成的 API不用自己造轮子。ClickHouse 的选择也很明确。实时分析场景需要的是列式存储ClickHouse 对聚合查询的响应速度是秒级的而且支持 AggregatingMergeTree 这类专门为预聚合设计的数据引擎。相比之下直接用 MySQL 或者 HBase 做这个事要么查询太慢要么开发量太大。现在说这个有点事后诸葛但实践证明分钟级延迟这个需求本身帮我省了一大笔成本。如果当时对方说我要毫秒级实时那这套架构可能要引入更复杂的方案成本会翻好几倍。存储引擎的最终选型参数如下表组件版本用途关键配置Kafka3.4事件缓冲3 节点副本因子 2Flink1.17实时计算并行度 8Checkpoint 60sClickHouse23.8聚合存储2 节点分片MergeTree 家族Redis6.2热点缓存1 主 1 从AOF 开启3. 核心环节的工程实现3.1 埋点 SDK数据的源头决定了天花板埋点 SDK 是整个系统里最小的模块但也是最不能出错的模块。数据源出了问题后面所有层级的计算都是垃圾。这个道理就好比盖房子打地基表面上看不到但一旦塌了就是整体塌。我们接了差不多 3 个平台的埋点Web 端、iOS、Android。为了保证上报行为一致三个端共用一个协议规范只在实现上各自单独写。SDK 的核心逻辑是批量上报——不能每次点击都发一个 HTTP 请求那会把服务器打垮。攒一批事件达到数量阈值或者时间阈值再统一上报。下面是一个简化版的 Web SDK 核心逻辑用 TypeScript 写的class Tracker { private queue: Event[] []; private readonly MAX_SIZE 20; private readonly FLUSH_INTERVAL 5000; private timer: ReturnTypetypeof setTimeout; private storageKey rea_tracker_events; constructor(private endpoint: string) { this.loadFromStorage(); this.timer setInterval(() this.flush(), this.FLUSH_INTERVAL); document.addEventListener(visibilitychange, () { if (document.visibilityState hidden) this.flush(); }); } track(eventType: string, props: Recordstring, any) { const event: Event { event_id: crypto.randomUUID(), event_type: eventType, user_id: this.getUserId(), device_id: this.getDeviceId(), ts: Date.now(), props, context: this.getContext() }; this.queue.push(event); if (this.queue.length this.MAX_SIZE) { this.flush(); } } private flush() { if (this.queue.length 0) return; const batch this.queue; this.queue []; navigator.sendBeacon(this.endpoint, JSON.stringify(batch)); } private loadFromStorage() { const cached localStorage.getItem(this.storageKey); if (cached) { this.queue JSON.parse(cached); localStorage.removeItem(this.storageKey); } } }几个容易被忽略的细节用 navigate.sendBeacon 而不是 fetch 发请求因为 Beacon 在页面关闭时也能保证把数据发出去不会因为页面卸载就丢数据。visibilitychange 事件里做最后兜底用户切后台或关页面前的最后一次数据必须发送。localStorage 缓存做了个简单补偿——如果上次有数据没发出去这次启动先把缓存的数据带上。这三个小细节合在一起能显著降低数据丢失率实测大概从 3% 丢数据降到 0.2% 左右。SDK 还有一个必须注意的点不能影响业务主流程。埋点代码里不能有同步阻塞、不能改动页面 DOM、不能报错影响页面渲染最好所有逻辑都放在异步队列里。业界有个埋点代码坏一锅粥的教训——因为埋点 SDK 抛了个异常整个页面白屏了这在线上是重大事故。3.2 消息队列与数据管道配置上报网关把数据打到 Kafka 之后主题topic的划分策略直接影响下游消费复杂度。我们有三种主题划分方式可以考虑按事件类型分click_topic、pageview_topic、purchase_topic按业务域分user_behavior_topic、order_topic、payment_topic一个大 topic 全装all_events_topic最后选了按业务域分只建了一个 user_behavior_topic。原因是事件类型太多上线时有 20 多种按事件类型分会导致 topic 数量爆炸而按业务域分可以保证同一个用户的行为事件在同一个分区内后续做漏斗分析的时候排序很容易。如果搞多个 topic 又要不同业务方消费不同数据维护成本会高很多。选择分区键也是一门学问。我们用 user_id 做分区键保证同一个用户的全部事件都进入同一个分区这样窗口聚合时比如漏斗能在单分区内完成排序不产生跨分区数据乱序的问题。如果用没有业务含义的随机 key数据会均匀分布但单用户事件会被打散到不同分区窗口计算时要 shuffle 一把性能损耗很明显。生产端的参数配置也很有讲究。下面是一份经过调优的普通配置# Kafka Producer 关键参数 bootstrap.serverskafka-1:9092,kafka-2:9092,kafka-3:9092 acks1 batch.size131072 linger.ms20 compression.typelz4 buffer.memory33554432 max.in.flight.requests.per.connection5 retries3这里的取舍逻辑是acks1 表示 leader 写入成功即可返回不需要等所有副本确认吞吐最高极端情况可能丢少量数据但埋点场景可以接受batch.size 调到 128KB 并配合 linger.ms20允许生产者攒 20 毫秒或 128KB 数据再发出去吞吐能提升好几倍compression.typelz4 对 JSON 这种文本数据的压缩比很可观能省 70% 以上的带宽retries3 是保险但注意 max.in.flight.requests.per.connection5 意味着最多 5 个请求同时乱序发送重试不会导致严重的乱序问题。这个环节我踩过一个坑最初 retries 没设上限默认是无限重试。消息在 Kafka 端积压时重试队列越堆越长最终把内存打满了。之后我把 retries 改成 3配合 max.in.flight.requests.per.connection 的约束丢数据概率几乎为零且不会再拖垮进程。3.3 Flink 流处理作业窗口计算的正确打开方式Flink 作业是整个系统的核心计算单元。我们用的是标准的 Flink SQL消费 Kafka topic做窗口聚合结果写入 ClickHouse。整套逻辑一句话总结定义数据源表声明事件时间和水位线然后写 Group By 窗口查询。事件时间的声明是整个作业的命门。因为网络传输、SDK 延迟等原因Kafka 里的数据不一定是按时间顺序到达的。我们必须在 SQL 里告诉 Flink请以事件内置的 ts 字段作为时间依据而不是以数据到达 Flink 的时间。同时给一个最大乱序容忍时间确保迟到的数据能进入正确的窗口。下面这段 SQL 是关键参考CREATE TABLE source_table ( event_id STRING, event_type STRING, user_id STRING, ts BIGINT, page STRING, props STRING, event_time AS TO_TIMESTAMP(FROM_UNIXTIME(ts / 1000, yyyy-MM-dd HH:mm:ss)), WATERMARK FOR event_time AS event_time - INTERVAL 5 SECOND ) WITH ( connector kafka, topic user_behavior_topic, properties.bootstrap.servers kafka-1:9092, properties.group.id rea-flink-group, format json, scan.startup.mode group-offsets );WATERMARK FOR event_time AS event_time - INTERVAL 5 SECOND 这行代码的意思是允许事件最多迟到 5 秒超过 5 秒的迟到数据会被丢弃或进入迟到侧输出。这个 5 秒不是拍脑袋定的我参考了线上延迟分布数据——99% 的数据在 3 秒内到达留出 2 秒的余量足够了。如果设太大窗口会迟迟不触发实时性受损设太小大量迟到数据被丢统计值会偏低。PV/UV 的实时计算用滚动窗口来实现。UV 去重是个经典问题Flink SQL 里可以用 COUNT(DISTINCT) 但代价较高。我们采用的是基于 BloomFilter 的近似去重同时在 Flink 的 Keyed State 中维护精确去重集合CREATE VIEW uv_1min AS SELECT TUMBLE_START(event_time, INTERVAL 1 MINUTE) AS window_start, TUMBLE_END(event_time, INTERVAL 1 MINUTE) AS window_end, page, COUNT(DISTINCT user_id) AS uv FROM source_table WHERE event_type view GROUP BY page, TUMBLE(event_time, INTERVAL 1 MINUTE);这里有个比写代码更重要的事Flink 的 Checkpoint 必须开它是精确一次语义的保证。我的配置是 Checkpoint 间隔 60 秒存储到 HDFS。为什么是 60 秒太小会导致 Checkpoint 过于频繁性能下降太大会导致恢复时间长。对分钟级延迟的需求60 秒恰到好处。生产上我把这个写进启动参数不允许任何人临时改掉。最后是写入 ClickHouse 的 Sink。这里我踩过一个很深的坑后面在第 5 节详细讲。4. 查询、报表与大屏让数据真正被用起来4.1 大屏和报表 API 的设计思路很多实时系统的开发把精力全花在计算链路上结果到了查询层直接拉胯接口响应三五秒大屏刷新一下就转圈。查询层的设计同样是核心。我们给大屏和报表规划了一套统一的查询 API结构非常简单GET /api/v1/metrics ?metricuv gran1m range1h dimensionspage,channel filtersevent_typeviewmetric 是指标名uv / pv / click_rate / funnelgran 是时间粒度1m / 1h / 1drange 是查询范围最近 1 小时 / 24 小时 / 7 天dimensions 是维度组合filters 是过滤条件。这样一个接口能覆盖 90% 的大屏指标需求也能支撑运营日常的报表查看。接口的响应体统一是{ code: 0, data: { total: 15234, series: [ {ts: 2023-11-15 10:30:00, value: 102}, {ts: 2023-11-15 10:31:00, value: 98} ], dimensions: [ {name: home, value: 203}, {name: cart, value: 87} ] } }设计这个接口最大的教训是不要为大屏单独拉一套后端。一开始我们想给大屏的接口做特殊逻辑后来发现大屏和报表的指标 90% 重合拆了两套后要维护两处口径逻辑对数据时非常痛苦。最后统一的接口逻辑配不同的参数完美解决。4.2 聚合表与缓存策略查询快的前提是数据长得对查询层最大的性能问题来自计算型指标。如果每次查询 UV 都要去扫几亿条明细再快也扛不住。我们的做法是在 ClickHouse 里用聚合树表存好每 1 分钟的预聚合结果查询时直接查聚合表按时间范围累加即可。下面是实时的 Rollup 表结构示例CREATE TABLE agg_page_1m ( window_start DateTime, window_end DateTime, page String, uv AggregateFunction(uniq, String), pv UInt64, channel String ) ENGINE AggregatingMergeTree PARTITION BY toYYYYMMDD(window_start) ORDER BY (window_start, page, channel);AggregatingMergeTree 是 ClickHouse 专门为实时聚合设计的引擎它支持把多次写入的同一主键数据自动合并。UNIQ 聚合函数的参数是 String 类型的 user_id插入时用 uniqState 函数查询时用 uniqMerge 函数。这套机制配合 TTL 策略可以实现明细数据存 30 天预聚合数据存 1 年的完整分层。缓存策略上坚持一个原则大屏读缓存报表读聚合表。Redis 只保存最近 1 小时的分钟级指标和最近 7 天的小时级指标。key 的格式设计成 metric:gran:date:hour:dimensions比如 uv:1m:20231115:10:pagehome。设置 2 分钟过期时间过期后由查询接口自动回源拉取聚合表并回填缓存。这里提一个经典问题缓存穿透。如果大屏上线前没有数据或者某段时间业务量很小某些 key 会不存在导致每次查询都穿过缓存去打 ClickHouse。我们做了两重防御第一不存在的数据也写一个空值缓存过期时间缩短到 30 秒第二ClickHouse 侧设置 readonly1超时 3 秒自动降级保证大屏永远有数据展示哪怕都是 0 也可以。5. 性能调优实录把链路压到稳定范围5.1 最容易翻车的三个瓶颈刚上线一周链路在低并发下看起来一切正常结果流量一上来直接现原形。总结下来实时链路最容易翻车的是三个地方。第一个是入口压力。线上峰值 8000 events/s 时上报网关的 CPU 直接飙到 90%批量 JSON 解析占了大头。你可能会问不是有 Kafka 撑着吗但进 Kafka 之前网关要先收 HTTP 请求、解析 JSON、再序列化写入这些都是 CPU 密集操作。后来我们把网关扩容到 4 个节点并且在 Nginx 层做了直接转发网关只保留校验和格式标准化逻辑CPU 降到了 60%。第二个是 Flink 反压背压。反压的本质是下游处理速度跟不上上游导致数据在 Flink 内部积压。我们遇到的典型症状是 Flink UI 上看到 Source 算子发送速率很高但 Sink 算子的发送速率一直上不去。最开始怀疑 Flink 状态太大后来查下来发现是 ClickHouse Sink 批量参数没调好。第三个是查询慢。最惨的时候大屏打开要 8 秒才能刷出数据。后来定位到 ClickHouse 查询计划里出现了全表扫描——原来我把时间过滤条件放到了 where 子句的末端但 ClickHouse 的优化器并没有聪明到自动调整过滤顺序。加上 PREWHERE 之后同样的查询降到 200 毫秒以内。5.2 参数调优的实测数据把优化前后的实测数据放出来大家可以直观看到到底优化了什么。指标优化前优化后关键调整网关 CPU90%60%精简处理逻辑 扩容Kafka 生产吞吐8000 events/s25000 events/sbatch.size linger.msFlink 吞吐5000 events/s50000 events/s并行度 8 优化 SinkClickHouse 查询8s200msPREWHERE 分区裁剪端到端延迟35s4.5s全局参数协同调整Flink 的调整细说下。并行度从 4 改到 8这是根据 Kafka 分区数定的规则——并行度尽量不要超过分区数否则会有空闲任务。Sink 端我们给 ClickHouse JDBC 设置了 batch_size10000 和 flush_interval3000ms这样数据是攒一批再批量写入。绝对不能一条条写那是灾难。同时把 Sink 的写入方式从单条插入改成了 batch insert。单条插入的瓶颈在网络往返Flink 每处理一条数据就要和 ClickHouse 建立一次网络交互批量插入把 10000 条合并为一次写入吞吐直接翻了几倍。还有状态 TTL 的设置。UV 去重需要维护一段时间内的去重集合如果不设 TTL状态会无限增长最终拖垮作业。我们把 uv 去重的状态 TTL 设置为 7 天因为业务上只关心最近 7 天的去重人数。状态小了备份和恢复都轻快很多。6. 线上问题排查与避坑速查6.1 四个典型故障的排查过程任何一个实时系统上线后一定会遇到问题区别只是你能不能快速定位。分享四个我们在rea项目里真实遇到的典型案例思路比结论更值钱。第一个是乱序。现象某天运营反馈凌晨 12 点的 UV 数据特别高比前后时段高了 30%。排查发现是因为客户端上报时存在网络排队一部分 23:59 的事件实际在 00:01 才到达 Kafka。如果 Flink 的水位线设置不够容忍这些事件会进入 00:00 的窗口导致数据虚高。最终我们把水位线允许乱序时间从 5 秒放宽到 15 秒并设置了 side output 侧输出流来监控丢弃的迟到数据既保证数据真实又不影响实时性。第二个是倾斜。现象Flink UI 上看到某个并行子任务的输入记录数明显高于其他任务处理延迟持续增加。排查后发现是一个特定的 user_id 每秒上报几十条事件把整个分区拖住了。解决方案是两阶段聚合第一阶段按 user_id 做局部预聚合第二阶段再把预聚合结果按窗口做全局合并。这个方案有效绕开了数据倾斜同时保证了结果的准确性。需要注意两阶段聚合只适合 COUNT、SUM 这类可叠加的指标AVG 要改造为 SUM/COUNT 再合并。第三个是重复消费。现象数据看板和业务数据库的统计对不上我们的数字总是略高。排查发现Flink 作业一旦重启会从上次 Checkpoint 的 offset 继续消费而 Checkpoint 中的 offset 可能领先于实际写入 ClickHouse 的进度导致一部分数据被消费了两次。解决方式是在 ClickHouse 建表时设置 event_id 作为去重键写入时对重复记录做幂等处理。这个坑解释了为什么 event_id 的设计如此重要——没有它重复消费的问题几乎无解。第四个是时区。现象业务方反馈每天下午 14 点的数据和 15 点的数据对不上差了 1 小时。排查后发现Kafka 里的时间戳用的是中国时区UTC8但 Flink 默认用 UTC 解析时间导致 14:00 的事件被解析成了 14:00 UTC 22:00 北京时间第二天合并时数据错位。处理方式是环境变量里显式设置 user.timezoneAsia/Shanghai并在表定义时用 TIMESTAMP 类型配合时区参数。这个坑是新手最容易忽略的因为它不报错但数据一直错。6.2 常见问题速查表最后整理一张通用速查表实时链路上遇到类似症状可以直接照着排查顺序走。症状可能原因处理方式数据看板数字比业务库偏高重复消费窗口重复计算检查 Checkpoint offset为表加 event_id 去重某用户数据占比异常大数据倾斜恶意脚本刷量两阶段聚合限制单用户上报频率数据统计波动剧烈乱序事件未处理时间字段口径不统一调整水位线容忍时间统一为毫秒时间戳大屏刷新极慢缓存穿透ClickHouse 查询扫描太大空值缓存PREWHERE分区裁剪Flink 作业处理越来越慢状态无限增长下游 Sink 写入慢设置状态 TTL批量写入下游早上 8 点的数据总是缺失客户端在凌晨没有活跃SDK 缓存清理失败检查客户端缓存策略确认业务空闲时段数据能查到但比预期少很多数据被过滤事件类型名称不一致检查清洗逻辑核对 SDK 事件类型枚举Kafka 消费积压暴涨下游作业未跟上Sink 故障观察 Flink 反压指标检查 Sink 日志这张表不是万能的但覆盖了实时链路里 80% 的常见问题。真正上线之后建议把这些排查路径写进团队的故障应急手册里新人接手时能少走很多弯路。7. 项目上线后我的一些真实体会现在回看整个rea项目的推进过程对我帮助最大的不是某个高深的技术点而是怎么把一个只有三个字母的需求变成一个可交付的系统这件事。很多人觉得技术选型是最难的但对我来说和需求方把UV 口径是什么这个问题对齐比部署一个 Flink 集群难多了。如果让我重来一次有两个地方会调整。第一会先做最小闭环再去补性能。先手动造一批测试数据把端到端全部打通证明链路能跑通再上真实流量。而不是像这次一样把全部组件搭好才开始测数据结果出了问题都不知道是计算逻辑还是数据格式导致的。第二会更早引入模拟压测。真实的流量曲线从来不是均匀的早晚高峰和偶发热点会造成巨大脉冲在压测环境里把这个问题暴露出来比线上半夜被报警叫醒要舒服得多。最后分享一个小经验给这种极简代号的项目写文档时不要只写实现细节要在文档第一页写清楚三个东西——需求方到底想要什么、验收标准是什么、哪些事明确不做。这份文档是你和产品经理之间最好的屏障。等下一次你再接到一个只有三个字母的任务至少你的后任不会跟你一样对着rea发呆十分钟。这个项目后续还打算扩展用户留存分析和多维归因分析不过这是另外的故事了。先把当前的实时链路维护稳定比什么新功能都重要。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑