前阵子整理团队内部代码仓库,翻到当初一个立项文档,标题栏只有三个小写字母:rea。没有正文、没有关键词、没有验收标准,连使用方都没写清楚。刚开始我以为是"read"拼错了,或者某个第三方库的缩写,但后来这个只有三个字母的项目,硬是被我们做成了一套稳定跑了半年多的资源效率分析平台。这篇文章就把当时的思考路径、技术选型、落地细节和踩坑过程完整写出来。如果你手上也接到过类似模糊到你怀疑人生的项目代号,或者正准备从零搭一套属于自己的可观测性工具,这篇应该能给你一些比"代码模板"更有用的东西。
1. 立项那天,我手里只有"REA"三个字母
1.1 这算需求,还是一个代号?
很多人拿到项目标题的时候,第一反应是:这写得不够清楚,我得找人问清楚。但"rea"这种连词都不是的缩写,问都不知道该问谁。
我们团队当时三个人,对这三个字母的理解完全不一样。一个人说可能指"Real Estate App",觉得是房产方向的项目;另一个人猜是"Reactive Analytics",想做响应式数据分析;还有一个人干脆说这是"Readiness Assessment",打算做上线前成熟度评估。你看,一个模糊的代号在三个人眼里就是三套完全不同的系统。如果直接按自己的理解开干,大概率做到一半才发现方向不对,返工成本高到离谱。
这个阶段最重要的不是选技术栈,而是先逼着所有人对齐一个问题:这个系统到底是解决谁的什么问题。项目名称本身不重要,重要的是它背后的意图。你要敢在立项会上追问:"这个缩写如果是拍脑袋起的,那拍脑袋的人想解决什么痛点?"大多数时候,对方答不上来,这恰恰说明需求本身还没有成形,需要你帮忙梳理。
1.2 三个问题把模糊代号变成清晰边界
在没有任何背景材料的情况下,我比较常用的方式是用三个问题做"需求探针",把模糊的想法压缩成可执行的范围,然后再反推这个代号到底应该长成什么样。
第一个问题:谁会被这个系统影响,谁会因为它的存在而受益?
我们把候选人列了一遍:业务研发想知道接口为什么变慢,运维想知道资源水位为什么异常,架构师想做容量规划。这三类人的诉求虽然有交集,但对数据颗粒度、实时性和准确率的要求完全不同。架构师需要横跨几十个服务的宏观视图,研发需要精确到某一次请求、某一个实例的微观数据,运维则更关心基础设施层面的指标。如果一上来就想同时满足三方,系统会变得臃肿且每个角色都用不顺手。我们最后把第一版的服务对象锁定为业务研发和运维值班同学,架构规划类需求明确挪到第二阶段。
第二个问题:这个系统是在"观察世界",还是在"改变世界"?
这一点非常关键。"观察世界"意味着输出分析结果、报表、告警,不做任何直接影响线上环境的动作;"改变世界"则意味着要做自动扩容、自动限流、自动修复这类闭环控制。后者的责任边界完全不同,一旦误判就是线上事故。对于一个从零开始、连名字都没想清楚的项目,我强烈建议第一版先做"观察系统"。REA最初就被定义为只读分析平台,不下发任何控制指令,这让我们在灰度接入时少了很多心理负担,安全风险也小一个量级。
第三个问题:第一版必须回答的"关键问题"是什么?
我们当时归纳出一句话:"某个服务某一天为什么突然变慢了?"注意这个问题的颗粒度——它不是"系统整体状态如何",而是"某一个具体服务在某个时间段内发生了什么变化"。这个颗粒度决定了后面要采集哪些数据、存储怎么设计、前端展示什么指标。如果你的关键问题是"用户注册转化率为什么下降",那数据模型就完全是另一套了。
1.3 最终定义:REA = Resource Efficiency Analyzer
把三个问题的答案钉在墙上之后,我们给"rea"补了全名:Resource Efficiency Analyzer,也就是资源效率分析器。核心职责一句话讲清楚:采集请求级别的资源消耗数据,建立资源效率基线,在指标异常偏离基线时快速定位到服务、接口、实例和错误码维度。
为了让项目不失控,我们同步写了一份"第一版不做清单":
- 不做自动扩缩容和自动优化,只做分析,不做控制;
- 不做端到端的分布式链路追踪,粒度只到请求和外部调用;
- 不做复杂的自定义报表系统,先用固定看板满足核心诉求;
- 不做细粒度的权限分级,内部工具默认全员只读。
这套清单在后面的需求评审中帮了大忙。任何新需求进门,先拿清单对照,超范围的要么砍掉,要么明确排期到下一阶段。很多项目死在"什么都想做一点",而REA能活下来并持续产出价值,很大程度上是因为我们把边界画得很死。
2. 技术选型时反复权衡的三个问题
2.1 采集端:为什么选SDK埋点,而不是旁路嗅探
确定做请求级资源分析之后,第一个硬骨头就是数据怎么采。市面上大约有三条路,我们逐一做了对比。
第一条路是旁路嗅探,在宿主机或内核层面拦截网络流量。它的最大优势是业务透明,不需要改任何应用代码,部署完后期的维护成本较低。但它的短板也极其明显:拿不到业务语义。内核层面的数据流能看到"某个进程发了多少字节到哪个端口",但完全不知道这是哪个接口、哪个用户、哪条SQL触发的。对我们想回答的"为什么某接口变慢"这个问题来说,这种数据过于原始,很难直接使用。
第二条路是日志解析。把业务日志统一采集后清洗、聚合,看起来改动很小。但我们很快就放弃了,因为日志质量完全不由我们控制。有的服务不打请求耗时,有的漏打错误码,还有的时间格式不统一。基于一份质量参差不齐的数据做分析,结论的可信度很低,而数据分析最怕的不是没数据,是数据不准。
第三条路就是最终采用的应用内SDK方案。以HTTP接口为例,在Web中间件里加一个拦截器,请求进来时记录起点时间、当前进程的CPU累计时间和内存总量,响应返回时再记录一次,两者相减就是这次请求的真实资源消耗。同时把路由、方法、状态码、用户ID等业务字段一并带上。代价是需要侵入应用进程,改造和上线成本更高。但换来的好处是数据语义完整,一次请求的"画像"从头到尾都能看得很清楚。
我们最终选了SDK方案,核心原因是数据颗粒度直接决定分析能力的上限。既然REA的定位就是请求级分析,那就不能为了接入方便牺牲掉业务上下文。这个取舍在后续所有功能落地时都被证明是对的。
2.2 存储:高基数场景下,选择合适的列式存储
这里必须说一个容易让项目翻车的地方:指标数据的存储选型。
REA面临的数据特点有两个,一是高基数,二是事后分析为主。高基数怎么理解?如果每次请求都带上用户ID、订单ID这类业务字段,那么一天下来可能的维度组合数会达到百万甚至千万级别。很多传统的时序数据库在低基数场景下表现优异,但一旦标签基数上去,索引和内存压力会急剧膨胀,查询性能断崖式下降。
我们当时需要的是支持"事后才想清楚怎么分析"的数据存储,而不是"提前把指标定义好"的方案。于是把目光转向了开源的列式存储数据库。它的数据模型更像一张宽表,每行代表一次请求,每列是一个属性,按天做分区,配合TTL过期机制。查询时按时间范围和目标字段过滤,然后做聚合,思路和跑SQL差不多,灵活度高出不少。
为了兼顾性能和可维护性,我们设计了两种表:请求明细表和元信息表。
请求明细表是核心,包含时间戳、服务名、接口名、实例ID、用户ID、状态码、耗时、CPU时间、内存增量、外部调用次数等字段。按天分区,原始数据只保留30天,聚合数据保留更长时间,避免存储成本失控。
元信息表存的是服务负责人、技术栈、部署版本、最近发版时间等静态属性。分析引擎做归因时可以JOIN这张表自动找到负责人,省掉一长串人工排查步骤。
2.3 展示层:先求快,再求深,别一上来就上重型BI
到展示层的时候,我们其实讨论过要不要直接接商业BI或开源大屏工具,这样能省不少前端工作量。后来放弃了,原因有两个。
第一,重型BI系统的核心能力是拖拽式自定义报表,但REA第一版根本不需要这种自由度。研发和分析人员的核心诉求就四个页面:总览、接口详情、实例详情、对比视图。用常规的Web框架加图表库就能搞定,开发速度反而更快。第二,自定义报表功能一旦开放,需求就会源源不断涌进来,"帮我把这两个指标放到一张图上"这类需求会占据大量开发时间,偏离了工具本身要解决的问题。
架构上的最终形态并不复杂,一条链路清清楚楚:应用内SDK负责采集,处理层负责接收、清洗、聚合和批量写入,存储层负责明细数据和聚合数据的留存,查询API服务负责向前端透出数据,展示层是一套轻量自研看板。没有任何花哨的组件,但每个环节各司其职,排查问题的时候不会"绕迷宫"。
3. 核心模块怎么落地:从采集到分诊
3.1 采集器:一个不需要用户关心的埋点
SDK接入的舒适度直接决定研发团队愿不愿意用,这是我们在设计采集器时最优先考虑的事。
以我们用的服务端框架为例,接入动作被封装成一个简单的中间件,业务方只需要加一行注册代码,剩下的全部由SDK处理。中间件的逻辑非常朴素:请求开始时记录起点快照,包括当前时间、进程累计CPU时间、内存占用;请求结束时再打一次快照,计算差值。对于外部调用,比如访问数据库或依赖的下游服务,我们在对应的客户端库上加了一层轻量计数器,记录调用次数和总耗时。这一层不要做深,够用就好。不要去造一个"函数级性能剖析器",那东西侵入性和性能损耗都会大到你难以收场。
上报环节有几个很容易踩的细节。我们一开始是同步上报,结果压测时发现SDK对接口耗时的影响在极端情况下能达到5%以上。后来改成异步批量上报:数据先在本地缓冲,攒够一定条数或间隔一定时间再一次性发送。压缩传输后网络开销可以忽略,正常业务的性能损耗基本稳定在2%以下,这是一个可以接受的数字。
采集策略上,我们默认采样率30%,而不是100%。原因是请求级数据量非常大,全量采集会让存储成本翻好几倍,而分析类场景并不需要每一笔请求都入库,统计意义上30%的样本已经能把P50、P95、P99这些分位数估算得很准。采样率做成配置项,支持动态调整,必要时可以对某个重点服务临时提高到100%,排查完再改回来。
3.2 分析引擎:先有"效率分",再做归因
数据采上来了,接下来最核心的问题就是:怎么定义"资源效率"?如果连判断标准都没有,那系统只是一个花哨的查询工具,而不是分析工具。
我们第一版没有上机器学习模型,而是定了一个可解释的线性加权打分方式。每个接口按耗时和资源消耗分别计算得分,再加权得到综合效率分:
score = (1 - p95_norm) * w_time + (1 - cpu_norm) * w_cpu + (1 - mem_norm) * w_mem其中p95_norm是把P95耗时分位映射到0-1区间,其他指标同理,权重的初始值靠历史数据倒推。这种方案的好处是任何研发看到分数都知道为什么扣分,不像黑盒模型那样"你说我效率低但我不知道哪里低"。机器学习和深度归因可以放在后续版本,第一版的底线是"结论可解释、逻辑可复查"。
归因流程也是先粗后细。效率分低于阈值后,我们按照固定路径逐层下钻:先看是哪个服务的分最低,再看这个服务里哪个接口贡献了最多异常,然后拆解这个接口的返回状态码和实例分布,找出影响面最大的那个实例或者错误码。这里要强调一个认知:归因不等于定案。系统能告诉你"这个接口的P95在过去一小时内从300ms涨到800ms,主要来自某实例",但具体是代码逻辑变了还是下游依赖挂了,还是需要研发结合日志和发布记录做最终判断。分析工具的价值在于把定位范围从几小时缩小到几分钟,而非替代人工思考。
3.3 看板与告警:让数据自己开口说话
有了打分和归因逻辑,剩下就是把结果呈现出来。我们只做了四个页面,每一个都对应一种具体的使用场景。
总览页展示所有接入服务的效率分排名、异常事件时间线和整体资源水位。研发早上到岗先看这一页,就能知道昨晚有没有服务出过状况。接口详情页是日常排查的主战场,包含时间序列曲线、P50、P95、P99分位数对比,以及CPU和内存的趋势。实例详情页会叠加部署版本标记和重启事件标记,方便把性能劣化和发版动作关联起来。对比视图支持任意两个时间段的曲线叠加,比如今天和上周同期,或者本周一和上周一,这是排查"是不是周期性波动"的利器。
告警规则我们刻意保持了克制,只分两级。P0级处理的是直接可用性问题,比如接口成功率突降、大量5xx错误、实例内存连续多轮超过阈值,这类事件立即电话通知到人。P1级处理的是效率劣化,比如某个接口的效率分环比下降超过一定比例,并且持续30分钟,这种情况发送IM消息即可。第一版我们曾经一口气配了二十多条规则,结果一周内告警轰炸,真正需要处理的事件反而被淹没在噪音里。后来把规则砍到只剩六条,配合下面要讲的动态基线,有效告警占比才提上来。
4. 上线前后踩过的四个真实深坑
4.1 凌晨三点的"幽灵流量"
REA灰度接入后的第三天,我们发现一个诡异的现象:每天凌晨3点到4点之间,数据量会异常少,但CPU和内存指标又高得离谱。乍一看像是有大批任务在夜里集中执行,但业务方反复确认,这个时段根本没有定时任务。
排查到最后,问题出在时间戳的"双标"上。采集端的某个内部实例在记录本地时间时用了服务器本地时区,而存储层统一用UTC时间,两边东拼西凑混在一起。白天的时候两者差别看不出什么问题,一到凌晨时分,时区偏移导致数据被整体挪移了一小时,看起来就是"某个时段数据稀疏、指标却飙高"。
这个坑的解决方式本身特别简单,但教训很深:所有跨系统传递的时间戳,一律使用Unix毫秒时间戳,也就是自1970年以来的绝对毫秒数,不携带任何时区信息。只有到了前端展示层,才根据浏览器的本地时区去做格式化。从那之后,再没有出现过时间错位类的问题。类似的坑在日志分析、监控系统里非常普遍,你只要看到"凌晨数据异常"这类现象,第一反应就应该是去查时间戳的时区一致性。
4.2 采样窗口吞掉了长任务
上线几周后接到反馈,某个数据分析服务经常有耗时超过三分钟的慢查询,但REA统计图上的P99最高值却只有四十几秒。数据明显失真,问题出在采样窗口的设置上。
采集SDK初始的统计窗口是10秒,设计意图是定期上报资源聚合数据。但长耗时的请求跨越了多个窗口,窗口闭合时请求还没结束,记录被作为"未完成事件"处理,最后的数据统计里这些请求的耗时被严重截短。换句话说,长任务被短窗口"吃掉"了。
修复方案是在采集端增加"完成事件"的概念:请求结束时再主动上报一条完整记录,用于修正之前所有不完整的统计;窗口只承担资源占比的聚合,不再负责终结请求记录。这件事之后我养成了一个习惯:任何采样系统都要先问一句"超过采样周期两倍以上的长任务会不会被截断"。如果你的系统也存在慢请求,测试用例里一定要覆盖"慢到超乎想象"的场景。
4.3 高基数标签的爆炸
数据量其实不大,但ClickHouse集群的写入和查询性能在某个阶段突然大幅下滑。检查之后发现,一条请求记录被我们塞进了太多"标签"字段,其中包含user_id、trace_id这类超高基数的维度。
这类字段的问题在于,它们的取值组合数量级太大,索引基本起不到加速作用,反而让压缩和扫描的开销急剧上升。从业务角度看,用户ID确实是排查单个用户问题时有用的信息,但它不适合作为全局的聚合标签。正确做法是:把高基数的业务字段放到明细数据中保留,查询时只在必要场景下使用二级索引过滤,而不是把它列入每个请求的默认标签集合。
用一个生活化的类比:在图书馆里,"楼层-房间-书架"是标签,适合用来归类和检索;但每个读者都有自己的日记,你不能把"XXX的日记"也做成一个书架标签,那书架会膨胀到无法管理。REA经过这次改造,标签数量被控制在二十个以内,所有user_id、trace_id之类的字段转移为明细字段,写入和查询性能很快就恢复了。
4.4 告警风暴之后的阈值收敛
第一版告警规则全部采用绝对阈值,比如CPU使用率超过80%就告警。这套方案的问题在于,绝对阈值不可能适配所有服务和接口。有的服务平时CPU只有5%,涨到20%已经是性能翻四倍;有的服务CPU常年跑在70%,超过80%也只是正常波动。结果就是告警规则要么过于灵敏惹人厌,要么过于迟钝形同虚设。
我们把绝对阈值改成了动态基线方案:系统自动计算过去7天同一个时间段的数据,形成基线,当实时指标超过基线的一定比例并且持续30分钟以上才触发告警。这样"和它自己比"而不是"和一个拍脑袋的数字比",误报率大幅下降。同时给告警增加了收敛机制,同一条规则同一个服务在30分钟内只发送一次通知,避免告警重复轰炸。
改造后的效果非常明显,一周内的告警总量下降了约九成,而真正需要人介入的事件,一个都没有漏掉。如果你也在维护一套告警系统,这个方向值得直接借鉴:不要问"阈值设多少",而要问"凭什么觉得这个阈值是对的"。
| 问题 | 现象 | 根因 | 修复 | 通用经验 |
|---|---|---|---|---|
| 幽灵流量 | 凌晨数据稀疏但指标飙高 | 本地时区与UTC混用 | 统一Unix毫秒时间戳 | 跨系统时间戳只传绝对时间 |
| 长任务被截断 | 慢查询P99统计失真 | 采样窗口吞掉了未完成事件 | 增加完成事件修正 | 覆盖超长场景的测试用例 |
| 标签爆炸 | 写入查询性能下滑 | 高基数字段进入标签集合 | 标签数量限制,字段降级为明细 | 标签只放低基数维度 |
| 告警风暴 | 误报过多淹没真实事件 | 绝对阈值不适配所有场景 | 动态基线与收敛机制 | 和自己比,不跟绝对值比 |
5. 从"REA"里长出来的可迁移经验
5.1 命名之外才是关键
复盘整个项目,我最大的感触是:项目叫什么都不重要,叫REA还是叫"小蓝工具",不影响它能不能解决实际问题。真正影响成败的是你有没有在早期把下面这几件事想明白。
我给所有接到模糊需求的朋友推荐一张清单,不管是做内部工具还是做产品都适用:谁会为这个问题买单或实际使用?系统是在观察世界还是在改变世界?第一版如果只能完整交付一件事,是哪一件?哪些事情明确不做?判断成功的标准是什么?这五个问题写下来之后,你会发现模糊的需求变得清晰很多,连带着技术方案也好选了。REA的一切都是从这张清单开始的。
5.2 先写出"不做清单",比写做啥更有用
在REA的演进过程中,我们每两周评审一次需求池。评审的标准不是"这个功能好不好",而是"这个功能是否在范围边界内"。不做清单的政治意义在于:给团队一个拒绝需求的依据。当有人提出一个看起来很漂亮的功能时,你可以把它放进"未来规划"而不是"本期范围",这样既不会打击积极性,又不会让系统变成一个臃肿的大杂烩。
如果你现在要启动一个新项目,建议把这个清单贴在团队交流群里,每周回顾一次。很多项目崩掉不是因为没有好的功能想法,恰恰是因为功能想法太多了。
5.3 从0到1的通用路线:灰度和复盘
REA没有选择一次性接入所有服务,而是先在两个内部服务上跑了整整两周。这两周里发现的问题,比如长任务截断、时区混用、告警误报,都是在小流量阶段暴露出来的。如果一上来就把几十个服务全部接入,这些坑会同时炸开,排查难度会大得多。
灰度接入之后的复盘也极其重要,REA上线一个多月后我们做了一次完整复盘,结论不是"哪里做错了"这种检讨,而是"哪个场景最值得继续投入"。数据揭示了一个意料之外的规律:大部分性能劣化都发生在发版后的一小时以内。这直接启发我们后来在实例详情页上叠加部署版本标记,让研发一眼就能把性能曲线和发布动作关联起来。很多时候系统告诉你的结论,和你最初的假设并不一样,你要做的是顺着数据的方向调整产品。
最后分享一点实际操作中的体会
做REA这个项目最难的其实不是写代码,而是把"什么算是效率问题"这个定义磨清楚。技术选型、存储设计、前端看板,这些都是有成熟方案可以抄的作业,但"这个系统到底要回答什么问题"没有任何模板能替你回答。如果你也在做一个从零开始的分析类工具,我建议你把前两周的时间主要花在画边界、列问题、写不做清单上,而不是急着搭建工程骨架。
另外一个特别实用的习惯是:每次迭代开始之前,都问自己一句"如果明天这个系统突然没人用了,最可能的原因是什么"。这个问题会逼着你想清楚真正的使用场景,而不是沉浸在实现细节里。REA做到今天已经稳定运行半年多,下一步的打算是逐步引入更智能的归因辅助,把研发从重复性排查中解放出来。但前提是数据质量继续打磨,在数据还不可信的时候,任何上层智能都是空中楼阁。