三十岁以前的往事(十五):2025 年终总结

时间过得可真快 …

工作

对于相关建库业务的支持和中间件运维这里就不提了,包括但不限于新业务对接、存量业务日常问题排查、中间件故障修复等,事情比较碎。

Q1

离线建库架构维护迭代

负责的五个库种 19 个业务的计算模块迁移(架构代码适配 + 业务代码迁移 + diff 测试)。业务很多很杂,但是由于之前也负责了这五个库种整体离线建库架构的迁移,所以对于其中计算模块迁移做起来轻车熟路,相对来说比较顺利。对于迁移后的计算模块,也为其接入了配置自动更新的能力,重启服务后即可更新相关配置。

对数据重算任务进行了改造,在重算期间导出计算的中间结果。相关配置的定义符合新模式下的配置风格。也即注册需要导出字段,定义这些字段的来源为重算任务,这些字段的生效目标是内部的存储服务。在生成重算任务配置时,会根据字段的定义生成具体的配置。

对于 Kafka 模块,新增了匿名 consumer group 的堆积监控。搜索这边的倒排索引模块会从 kafka 中通过 assign 的方式读取数据,同时设置了 offset 提交。如果使用 kafka 的接口是没法获取到通过 assign 创建的 consumer group 的信息的,但是倒排索引模块的负责同学想看到消费的进度,也即数据堆积的情况。凑巧的是,Kafka manager 上可以查看到这些 consumer group 的信息,所以就研究了一下 Kafka manager 这块的代码实现,发现它是直接读的 __consumer_offsets 这个内部 topic 的数据。这个 topic 的作用之一就是保存 consumer group 提交的 offset 信息。仿照 Kafka manager 的实现思路,搞了一个 exporter 读取这个 topic 的数据,然后进行解析,从中提取出 offset 信息暴露给 prometheus ,这样监控数据就有了。

对于 MongoDB 模块,目前线上集群的表默认都是开始分片的,但是发现存在部分表的索引在一些分片上丢失的问题。这会导致缺失索引的分片上相关表的查询由于是全表扫描所以执行的特别慢,甚至拖垮整个分片。针对这个问题,搞了一个定时任务脚本定期筛出索引缺失的表对应的分片,然后在这些分片上执行索引创建的命令。大致的执行流程也很简单,首先查询 mongos ,找出开启分片的表以及对应的索引信息,然后查询各个分片,如果这个表存在于这个分片上但是索引和 mongos 返回的信息不一致,则在这个分片上执行索引创建的命令。任务部署后,相关的问题就没再出现过了。

LLM 开发

开始接触 LLM 应用的开发。在 Q1 刚开始的时候,组长就跟我说过会安排我做一些和 LLM 应用的相关的工作,所以在极客时间上买了一个 LLM 入门的课程《程序员的AI开发第一课》,春节假期期间学了一遍,从相关概念的介绍,包括大模型、提示词、Agent,到动手裸调模型接口开发聊天应用,再到使用 Langchain 框架进行改造,加入提示词模板、Memory、RAG、function call 等机制,最后到开发一个垂直应用场景的 Agent 。整个课程学习完,对于 LLM 的应用算是有了一个比较全面的了解。

在 Q1 有里程碑的工作是做了一个 RAG 文档问答助手。当时接到这个工作后,正好在极客时间上有相关的课程《RAG快速开发实战》,所以就边学这个课程边进行开发,其中还花了好几个周末的时间,最后的效果还不错,唯一一点缺憾是,公司内部的知识库文档没有 markdown 导出的接口,所以每次文档更新都需要人工将知识库文档转为 markdown 的格式。后面公司的知识库本身就支持了文档问答的能力,所以后续这个助手就没有迭代了。整体开发的过程还是挺有趣的,首先是需要对 markdown 文档进行向量化,其中包括数据清洗(删除链接等无用文本)和文档切块,最后将其存到向量数据库中,原始文档在切块后也需要存储一份。然后是在用户提问时,根据用户提问从向量数据库中检索 top3 的结果,然后再找到对应的原始文档段落后,发给模型进行选择和总结,最后将模型的结果返回给用户。

和 LLM 相关的工作,除了 RAG 文档问答助手外,在 3 月的时候,字节跳动开源了 LLM Agent 框架 eino,这个框架是用 golang 编写的,比较符合我的技术栈。在业余时间基于这个框架的 ReAct 模式,搞了一个查询 Kafka 集群信息的工具,还是挺有意思的。

Q2

离线建库架构维护迭代

对于新离线建库架构下的模块,基于 23 年做的 trace 机制,实现了模块日志 trace 的自动接入。在新架构下,所有的信息都在配置中心里维护,包括模块服务的信息。在 23 年做的 trace 机制中,有一个 API Server 支持提供服务 id 来实现 trace 的接入,所以实现思路就非常明确了,在配置中心流水线中新增一个环节,执行一个脚本用于从配置中提取出模块的信息,然后请求 trace API Server 的接口来接入 trace 。这样每次配置发布时,跑完流水线后新增模块就自动接入 trace 了。

在新的离线建库架构下,整个建库链接为同步 rpc 调用,所以作为数据入口的引入模块可以获取到一条数据完整的建库状态信息,当前这个模块已经将建库失败的数据信息 dump 至 HDFS 上,而我这边需要建设一种机制对原始数据进行处理,对错误的类型进行归类,并展示在一个页面上。实现思路其实很简单,就是部署一个小时级别例行执行的 MR 任务,基于 MR 的机制,对前一个小时的原始数据进行预处理和归类,最后将归类的结果存储至 MongoDB 中。在页面上展示时,直接读取 MongoDB 中的数据即可。为了便于问题排查,在页面上还新增了抽样数据的 trace 展示功能,也即对于每一个错误,都会抽样 3 条数据,使用它们的 traceid 查询到对应的 trace 日志内容。trace 查询接口也是 23 年建设 trace 机制时 API Server 支持的功能。页面代码是纯手工用 Angular.js 撸的。就我个人而言,使用 Angular.js 开发简单的单页面应用还是最顺手的。这个页面后续在开周会的时候都会过一遍来确认当前建库是否有异常,感觉还是挺有用的。

对于上面提到的数据引入模块,其中对于一条数据的处理分了很多的阶段,可以将每一个阶段都看出是一个算子,其中一个阶段执行耗时高就会导致整体的耗时上涨,当前仅有 RPC 请求下游阶段的耗时监控,缺失其它阶段的,我这边就对其做了改造,将其它的阶段都补全了。实现思路非常简单,对于一条数据,其定义是一个 C++ 结构体,在初始化时包裹在一个智能指针中。那么我在这个对象初始化的时候,启动计时器,每完成一个阶段,就调用函数重置计时器,并记录之前计时器的耗时以及对应的阶段名称,最后在对象销毁时,在析构函数中将各个阶段及其耗时作为监控指标暴露出去。利用这个监控,发现了在一个场景下,某个阶段耗时异常的问题,模块负责人对其进行了修复,耗时占比从 40% 优化到 1% 以下。不过在另一个场景下,这个监控会导致服务偶发 coredump ,排查发现是这个场景下有单个数据存在并发访问的问题,之前从模块负责人那儿得到的信息是单个数据在模块内不会被并发访问,所以重置计时器和记录耗时的操作都没加锁,但是后续为了支持一个场景才加入了并发访问的逻辑,最后还是给相关操作加了锁。

对于重要的中间件 Kafka 和 MongoDB 服务,出现过几次实例所在机器异常导致实例异常的问题,所以为 Kafka 和 MongoDB 实例所在的机器新增了 CPU、Memory 和 Disk 使用率监控的采集和展现,这样便于在服务异常时快速定位可疑的实例。另外对于离线建库主通路的 Kafka 服务,由于在 Q2 接了一个大业务导致数据量暴增,所以将服务下的所有实例使用的磁盘从 3.6 TB 全部切换为 7 TB,以应对数据处理及生效异常时,对于建库中间数据的存储与回放。

在对 MongoDB 的索引信息进行修复的任务部署后,无意间发现存在一些没有开分片的表,系统梳理了一下发现居然有将近 100 张表。排查后发现很多表都是业务私连 MongoDB 建的,其中一些表的数据量还不少。如果一个表没有开分片,那么它的数据都会落在一个特定的分片上,这样这个分片的存储比其它分片增长的就快。联系了相关的业务问了一下背景,并且为数据量较多的表开了分片。

之前日常运维 MongoDB 的时候发现,MongoDB 集群对外表现异常时,常常是由单个分片引起的,之前定位的方式是登录目标机器查看各个分片主节点的日志,如果出现大量的慢查询,那么就执行切主操作。后面我为方便扫日志,自己写了一个脚本并发 ssh 登录对应的机器自动读取日志文件。但是近期发现有些线上机器无法通过开发机登录,需要走 relay ,但是 relay 环境只有 ssh,脚本无法运行。研究了一下 MongoDB exporter 导出的监控数据,发现是有分片粒度的延时监控的,但是监控看板没有配置。在补全了这个监控项后,后续集群异常时,观察到确实存在单个分片延时监控数据上涨的问题,对这个分片操作切主后,集群就正常了。

离线建库监控报警建设

在新的离线建库架构下,各个模块的监控和报警都是由模块负责人配置,且各个业务对应都使用独立的模块,所以监控和报警都需要人工进行配置,缺乏一个自动化的机制。我的工作就是要实现这一机制。实际上当时要求做的是 “智能监控”,也就是说,除了自动化添加监控报警外,还需要体现出 “智能” 才行。在做项目评审的时候,T7 和 T8 就质疑这个项目缺乏 ”智能“ 的要素,但是 T9 最后拍板说,先把自动化做好就可以了。不过在 Q2 末 T9 离职后,T7 和 T8 就在推建设 ”智能“ 的机制了。

我在对这个监控系统进行拆分时,将系统的构成拆分为:1)监控数据的采集。2)监控指标的展现。3)异常指标的发现。4)异常指标的处理。

首先,对于监控数据的采集,准备编写 prometheus exporter 来从各个模块的实例获取监控数据,那么这就涉及到三个问题:1)模块信息从哪儿获取。2)应该采集哪些监控数据。3)如果处理获取到的监控数据并暴露给 prometheus。首先,对于第一个问题,之前提到,在新的离线建库架构下,配置中心存储有完整的信息,所以我仿照 trace 自动化接入的方式,在流水线上挂了一个脚本,将业务机制对应模块的信息存储至 MongoDB 中,exporter 直接读 MongoDB 就可以获取到模块的信息,并且由于发布配置必须跑流水线,所以 exporter 拿到的信息一定是实时更新的。而对于问题二和问题三,我学习了新的离线建库架构对于数据处理的建模,也即将配置定义分为:1)数据来源。2)数据处理方式。3)数据输出。下面是一个配置样例,通过将监控数据处理的流程进行拆分,后续对于新需求的支持就比较灵活了,例如新增一种 fetcher 类型,或者新增一个聚合函数,只需要修改少量的代码即可实现。这个 prometheus exporter 算是 2025 年,甚至从 2022 年入职后到现在,我最满意的一个模块设计和实现了,这个满意主要体现为在开发代码和填写配置时,内心非常的舒畅。其它做的一些模块,因为设计与实现的不够合理,或是过于复杂,或者有很多定制逻辑,导致在使用或者迭代的时候内心总感觉堵得慌。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
{
"source": {
"importer_app_source": { // 定义数据来源
"fetcher": { // 定义数据应该如何获取,具体获取方式需要编码实现
"type": "bvar",
"config": {
"parallel": 30,
"metrics_path": "/rpc_metrics",
"timeout": 3
}
},
"config": { // 数据来源使用的动态生成的元信息
"type": "standard_mongodb",
// ....
}
}
},
"metrics": {
"importer_send_downstream_filter_count": { // 定义暴露给 prometheus 的 metrics 名称
"desc": "引入发送下游时被过滤的数量",
"from": { // 定义这个数据是从哪儿来源产生的,以及如何匹配
"type": "source",
"source_name": "importer_app_source",
"match_regex": "^send_downstream_filter_metric_([a-zA-Z_]+)$"
},
"tags": { // 定义需要为监控项打上哪个 tag,可以是由数据源产生的,也可以是正则匹配的内容
"from_source": {
"app": "id",
"inst": "inst"
},
"from_regex": {
"source": "1"
}
}
},
"process_msg_latency_metric_by_stage_percentiles_avg": {
"desc": "分阶段的处理延迟的原始分位值的平均值聚合",
"from": { // 支持进行聚合计算,那么具体的数据源就是暴露给 prometheus 的 metrics 名称
"type": "metrics",
"metrics": {
"process_msg_latency_metric_by_stage_percentiles": {}
}, // 定义聚合的 tag
"by_tags": [
"app",
"source",
"stage",
"tp"
]
}, // 定义聚合的函数
"aggr_func": {
"avg": {}
}
}
}
}

其次,对于监控指标的展现和报警的配置的问题,由于当前是每一个业务都部署了一套模块,所以在进行监控指标展现时,利用 Grafana template 的能力即可,但是无法为 template 配置报警。也即当前的场景是这样的,一个模块下有一批 app ,模块负责人制定了一系列的监控报警规则以及报警触发时的回调动作,需要按照每个 app 进行具体的配置,以 app 的粒度进行创建与更新。类似于是一个类和其实例化的一系列对象的关系。需要建设一套自动化机制,自动生成 app 粒度完整模块的监控指标看板及配置对应的报警。在新版的 Grafana 中,Dashbaord 和 alert rule 均被定义为 json 的格式,且 Grafana 提供相关的 api 对这个 json 进行 CRUD 操作,所以实现的思路就很明确了,由模块负责人定义它们自己的模块的 Dashboard template 和 alert rule ,然后有一个自动化模块读取各个模块的 Dashboard 和 alert rule ,基于配置中心中各个业务的配置信息,将其拼成一个个完整的 app (业务)粒度 Dashboard 及生成对应的 alert rule,每一个业务的 alert rule 中的阈值可以独立配置。基于这个思路实现了一版自动化生成看板及配置报警的机制,虽然存在一些问题,但是也达到了可用的地步,为 30+ 的业务生成了模块完整的监控看板及配置了对应的报警规则。在实现这个机制时,我比较满意的一点是,业务的 Dashboard 存在一些状态,比如说 Dashboard 对应的业务 id、Dashboard 中各个 Panel 对应 alert rule 映射等,没有使用外部存储来维护,而是存储在 Dashboard 的 tag 中,也即由 Grafana 自身来存储这些信息。

最后是异常指标处理的部分,目前做的比较简单,就是实现了 Grafana 的 Webhook ,将 Grafana 推送的报警信息存储在 MongoDB 中,并转发至对应模块的报警群组中。

LLM 开发

基于多 Agent 协作的模式,搞了一个用于查询离线建库信息的 Agent,使用之前提到的 eino 框架实现。Agent 使用的 LLM 是部署在百度云千帆平台上的 Deepseek 和文心。在那个时候,Deepseek 还不支持 function call ,但是推理的效果比较好,而文心支持 function call,但是推理效果远差于 Deepseek ,所以最后设置了如下的模式:

  1. Deepseek V3 在收到问题后,分析需要获取哪些信息来对用户的问题进行回答,然后对任务进行拆分,列出一个执行计划,接着按照执行计划依次执行收集必要的信息
  2. 当执行计划的某一个步骤需要执行 function call 的时候(例如请求 API 查询某个信息),调用文心 3.5 来执行 function call。Deepseek 传给文心的上下文是经过裁剪的,仅包含执行 function call 所需的上下文。而文心 3.5 返回给 Deepseek V3 的上下文也经过了裁剪,仅包含 function call 的原始响应。也即对于 Deepseek V3 来说,文心 3.5 就是执行 function call 的一个工具人。
  3. 当 Deepseek V3 认为收集到了所有比较的信息后,会将收集到的信息传给 Deepseek R1 进行总结,最后返回给用户。这里 V3 传给 R1 的上下文也进行了裁剪,仅包含用户的原始问题以及它收集到的信息,其它的信息,例如 V3 调用文心的上下文,都被移除掉了。

借助 eino 框架的能力,可以进行便捷的拓扑编排、业务逻辑注入等操作,且整个处理过程都可以通过 hook 接口暴露出来,完全可观察,用起来还是挺爽的。在开发 Agent 的过程中深刻体会到了一个干净的上下文对 LLM 输出效果的重要性。通过仅给 LLM 传入必要的上下文,在复杂任务的场景下可以极大地提升 LLM 执行的精度。

Q3

离线建库监控报警建设

在上个 Q 完成了监控监控报警自动添加后,这个 Q 围绕着监控报警进行了进一步的建设。原先使用的是部署在物理机上的 Prometheus 来存储监控数据的,但是出过几次 Prometheus oom 的问题后,准备使用公司集团云的 Prometheus 。由于集团云的 Prometheus 对于 exporter 单次返回的数据有大小限制,所以我这边对 exporter 进行了改造,将其拆分为了多个 exporter ,每个 exporter 获取一部分数据。由于之前在设计 exporter 的时候对监控数据获取的流程进行了拆分,所以这次改造仅涉及到最开始监控数据获取的极少部分的代码,改造起来非常快速且顺利。另外,访问集团云 Prometheus 的鉴权流程比较复杂,当前希望继续使用部署在物理机上的 Grafana 通过 Basic auth 的方法访问 Prometheus ,所以额外开发了一个 Prometheus 的代理,在代理请求的时候执行鉴权逻辑,生成相关的 token 并注入到发给 Prometheus 的请求中,而代理通过 Basic auth 的方式对收到的请求进行鉴权。

对于 Prometheus exporter,除了之前采集各个模块监控数据的功能,还额外新增了如下的能力:1)检测建库监控中间件,例如 Grafana、Prometheus 代理和 Grafana WebHook 服务,的可用性。2)检测存储在 MongoDB 中的报警数据中是否有未恢复的高优报警。3)检测 MongoDB 服务的状态。只需要编写监控数据获取阶段的 fetcher 即可完成适配。如果存在异常则会给 Prometheus 提供对应的监控指标,在 Prometheus 侧可以基于这个指标配置电话报警。

其中对于第二个能力,有一个比较难忘的经历这里必须记录一下。离线建库架构存量的监控报警也是基于 Grafana 搞的,流程也是 Grafana WebHook 将报警信息存入 MongoDB 中,但是高优报警触发电话报警是基于日志报警实现的,也即有一个定时任务扫 MongoDB 表,如果存在未恢复的高优报警,那么就往本地日志中写一个特殊标记,在公司集团云上可以基于日志规则配置电话报警。某一天晚上,我发现这个定时任务所在的机器挂了,但是在重启后发现基于日志的电话报警不生效,拉了集团云的值周也没查出原因。电话报警失效就意味着如果半夜出现故障那么值周无法感知。搞到晚上快十二点,实在没办法了,我就基于新的 Prometheus exporter 中的第二个能力,在集团云的 Prometheus 上配了电话报警。由于原报警关联的值班表较多,配置起来比较麻烦,所以我先让电话报警都报给我一个人,也就是至少今晚有人可以感知到故障的出现。结果呢,当晚连接 3 个报警电话,2 点一个、5点一个、7点一个,导致实际睡眠时间可能少于 3 h。到了第二天上午集团云的值周给结论了,确实是他们的一个服务有问题,目前已经修复了。唉,其实我在想,如果前一天晚上我发现监控报警没恢复,但是装没看见,可能也是没关系的,因为我不是当周的值周,不需要关注报警的问题,但是这样的话职业素养就太差了。I will not knowingly allow code that is defective either in behavior or structure to accumulate。

为了增加监控系统的 ”智能“ 程度,想尝试新增报警预测的能力,例如 qps 的场景,一般的报警阈值为跌 0,但是假如常态的 qps 为 1000,但是跌成了 30 ,在这种情况下无法触发报警,但是却是有问题的,如果可以基于历史的 qps 趋势生成对于当前 qps 的预期值,那么就可以和当前实际的 qps 进行比较,来检测是否正常了。我这边调研了一圈,发现有一个开源的时序预测库 Prophet 可以开箱即用,就基于它封装了一个 Prometheus exporter ,定时基于过去 28 天的数据训练 Prophet ,然后在 Prometheus 拉取监控数据时生成各个业务数据引入模块 qps 和延时的预测上界和预测下界。最后在 Grafana 上配置报警,也即如果当前的监控值小于预测下界或者大于预测上界,则发出报警。在部署 Prometheus exporter 发现直接 pip 安装的 Prophet 库在公司内部物理机上运行存在问题,研究了一个下午,在物理机环境中从源码编译了 Prophet 库解决了这个问题。实际运行下来效果只能说一般,存在一些误报,但是确实是发现了个别的问题,也就是上面提到的 qps 的值小于常态的值的问题,不过这类问题查下来都是上游存在故障导致的流量降低,不是建库系统本身的问题。

另外还基于 eino 框架搞了一个分析建库监控报警的 Agent 。由于之前提到整个离线建库链路为同步 RPC 调用,所以如果上游模块出现故障而触发报警,可能是因为下游模块导致的。这个 Agent 可以通过封装的建库监控查询 MCP 服务拿到整个链路各个模块的重要监控指标的数据,然后对这个数据进行分析,最后指出真实存在问题的模块以及具体的问题。Agent 侧直接复用的 eino 框架提供的 ReAct 模式,LLM 使用的是支持 function call 的新版 Deepseek V3.1。

LLM 开发

上半年比较火的一个协议是 MCP ,也即给 LLM 调用工具的协议,这个协议不但可以让 LLM 执行本地的功能,而且可以让它调用远程的服务。处于好奇,我对这个协议了解了一下,然后也搞了几个 MCP Server,例如将运维 kafka 用的几个工具以及将操作线上容器的接口都封成了 MCP 服务,接入了 Cherry Studio 中来让 LLM 调用,感觉还是挺有意思的,就是比较费 token。

KRaft Kafka

因为业务的扩展,有大规模部署新 Kafka 集群的需求。存量的 Kafka 使用的都是 2.1.1 版本,且使用 ZooKeeper 。新部署的集群希望直接使用 KRaft 模式,也即移除掉 ZooKeeper 。我最终选用了大版本为 3 中的最新版本 3.9.1 来作为部署版本。由于移除了 ZooKeeper ,Kafka 的云原生改造需要重新进行,因为 2.1.1 版本下的云原生改造是基于 ZooKeeper 进行的。在 KRaft 模式下,需要在 Kafka 的配置文件中指定 Kafka 的类型,也即是 controller 还是 broker ,亦或者两者都是,并配置 controller 列表(对应老版的 ZooKeeper 地址)。我专门编写了一个代理程序,在 Kafka 实例启动前更新 Kafka 的配置文件,生成最新的 controller 列表,并根据存储在 MongoDB 中的元信息,来判断是否需要指定当前 Kafka 实例为 controller 。在 Kafka 服务成功启动后,如果是 controller,则基于 KIP-853 的能力,执行相关脚本将其加入到 raft 的 voter 列表中。同时也对集群做了一些压测,基于吞吐数据评估实例的性能,以及基于 CPU 和内存的使用量评估单实例预期需要申请的资源。

由于引入了 KRaft 模式,所以在网上找了其对应的监控配置,补全了诸如当前 controller id、raft 选主耗时、raft io 空闲率等和 raft 相关的监控指标。另外老版的 Kafka manager 不支持非 ZooKeeper 模式的 Kafka,所以在网上找了另一个 ui (provectus/kafka-ui)进行了部署。

由于组内之前跟我共同运维 Kafka 的 T5 同事离职,目前组里负责 Kafka 的只剩下我还有 T7 了。我内心非常怕出了线上故障但是因为对于 Kafka 的不了解而导致故障无法修复,而且目前又准备用新版本的 Kafka,存量的运维经验可能无法解决新版本的问题,所以在国庆假期期间把很久之前买的极客时间的课程《Kafka核心源码解读》的大部分章节结合着 2.1.1 和 3.9.1 的源码看了一遍,并且尝试让 AI (ClaudeCode + GLM4.6 & Trae)来辅助进行源码梳理。

Q4 - AI Coding

对于 Q4 (包括一部分 Q3)的总结,我想以 AI Coding 做为线索进行串联。在这 3 个月中,感觉在我自己的代码开发领域发生了天翻地覆的变化。

Leaving the Sea

在我的印象中,在 23 年的时候公司内部的代码编辑器就支持基于当前代码补全代码片段了。当前发现有些时候代码写到一半,稍微停一会,编辑器上就会显示出后续的代码,然后敲一个 tab 就补全了,感觉挺神奇的。到 24 年的时候, LLM 越来越火,我开始尝试使用 LLM 来搜索一些问题,以及给它提需求来生成一些代码片段或者简单的脚本。那个时候也下载过 Cursor,但是不会用那个聊天框,感觉和普通的 vscode 没区别,后面就卸载了。

在 7 月的时候和一位前同事约饭,他跟我说目前他的微服务代码都是由 ai 生成的,而自己只负责给 ai 提需求,当时听起来非常不可思议。到了今年 8 月末的时候,部门内搞了一个 AI Coding 的比赛,要求使用公司的 AI Coding Agent Zulu 来写代码或者修改已有的代码,将相关的 prompt 和 cr 作为参赛材料。正好我当时对 MCP 比较好奇,想是否可以借助 AI 的能力快速开发一个 Kafka 运维 MCP Server ,于是就是用 Zulu 进行了如下操作,这些步骤是我自己人工拆解的:

  1. 将 Golang 的 MCP sdk 的源码下载到本地,让 Zulu 基于这个 sdk 学习 MCP 协议细节和开发方式,并输出相关文档
  2. 基于第一步产出的文档,让 Zulu 编写一个简易的 MCP Server,支持查询本地指定路径下的文件列表以及读取文件内容,类似于 ls 和 cat ,目的是走通整个 sdk 使用流程
  3. 分析现有的 Kafka 运维工具代码,让 Zulu 输出相关的总结文档,为后面 MCP Server 改造做准备
  4. 让 Zulu 基于第一步产出的文档、第二步产出的 MCP 代码样例和第三步产出的工具总结,生成 Kafka 运维 MCP Server ,功能需要和运维工具的能力保持一致,例如查询指定 broker id 对应的 ip、app inst id 等信息 ,以及列出所有 broker 的简略信息

在 Zulu 开发的过程,出现了几次它无法自己完成编译问题的修复,然后把代码改的越来越乱的情况,此时就需要人工进行接入,来进行问题排查。另外可能使用的底层模式是 Deepseek V3.1 ,在输出的代码中偶尔会出现奇怪的字符,导致编译报错。不过整体而言体验还不错,当时就有了尝试其他 AI 代码编辑器的想法。至于这个比赛,因为参加的人不是很多,所以基本都获奖了,我也是其中之一,拿的奖品是 100 块的京东卡。

从 25 年 8 月开始,公司的电脑禁止安装 Cursor ,所以我开始尝试除 Cursor 外其它的 AI 代码编辑器。首先的阿里的 Qoder ,当时是在内测阶段,所以免费送了 1000 credit。我当时想搞一个通用的 MCP Server 框架,支持将诸如 cli 、http api 等形式的工具或者接口,通过配置的方式以 MCP Server 服务的形式暴露出来。当时出于好奇,给 Zulu 和 Qoder 输入相同的提示词,Qoder 都完成它输出的单元测试了,Zulu 还在那边解决编译报错的问题。当时我就在想,如果一开始用其它的 AI 代码编辑器开发 Kafka 运维 MCP Server,是不是速度可以快一些。

1000 credit 的消耗速度比想象中的快很多,并且当时 Qoder 并没有提供国内支付的途径,但这个 MCP 框架还没搞完,于是就又下载了字节的 Trae ,接着完成代码开发工作。但实际使用下来发现,即使在 Trae 中选择 Claude 4 模型,但是体感还是不如 Qoder,不过整体来说还是可用的。Trae 国际版支持支付宝支付,所以后面就开了它的会员。

另外我还尝试了基于命令行的 Claude Code ,但是因为它的公司禁止中国大陆使用,没法用国内的支付方式开会员,所以我就用了中转站,当时充了很少的钱,想试试水,后面发现 token 消耗的特别快,充的钱没一会就用完了,于是就放弃了。

后续的一些小工具的开发以及存量代码的迭代(不包含涉密代码库)基本都走 Trae 通过 AI 来做了,代码实际编写的量少了非常多,并且随着使用次数的增加,感觉已经适应了这种代码开发模式,也即在编辑器右侧的聊天框中给 AI 下达需求,然后 AI 生成代码,接着人工 review AI 生成的代码,完全回不去之前那种手撸代码的模式了。

Bones to Spaceships

上文中提到,在今年国庆期间,我尝试使用 AI 的能力学习 Kafka 的源码,也即让 AI 基于源码来回答我的一些问题,例如梳理 Kafka 处理一次 Produce 请求的完整流程。之前我也尝试过阅读 Kafka 的源码,但是由于 Kafka 的项目比较庞大,且我对于 Java 和 Scala 都不是很熟悉,所以一直都不太顺利。

一开始我用的是 Trae 来进行辅助源码学习的,整体感觉效果不错,后面也研究了一下系统提示词,想了一套阅读源码的提示词,并基于 AI 输出的效果进行了一些优化,这样可以让 AI 输出的分析结果更为清晰。

国庆节期间,GLM 4.6 发布,当时宣传代码能力比肩 Claude ,并且支持 Claude Code ,所以就开了一个月的会员想试一试。在阅读 Kafka 源码时,给 Claude Code 和 Trae 输入相同的提示词,然后对比它们输出的分析结果,互相验证。从整体上看,GLM 的效果还是比 Claude 要差一些。国庆节后,Claude 的公司宣布全面禁止中国公司的产品使用其模型,很快 Trae 也下架了 Claude 模型,不过 GPT 和 Gemini 系的模型还是可以用的,不过是使用的时候,总感觉效果比 Claude 系模型要差一些(也有可能是心理作用)。

我有点记不清我是通过什么途径了解到 Kiro 的,记得第一次尝试的时候,让它分析 Kafka 的源码,运行了半天都没完成分析,并且特别耗电脑 CPU ,所以后面很长一段时间都没用过。在 Trae 下架了 Claude 模型后,我突然想起 Kiro 好像是可以用 Claude 的,既然分析源码不太行的话,那就用它来生成代码试试,所以又尝试将主力编辑器从 Trae 切到了 Kiro 。Kiro 是亚马逊推出的产品,亚马逊本身也提供 Claude 的模型服务,所以 Kiro 使用的应该就是部署在亚马逊自己云上的 Claude ,没有中间商赚差价,刚注册的时候送了 500 credit ,感觉 credit 的消耗速度明显比 Qoder 要慢。在用了一段时间 Kiro 后,感觉体感居然比 Trae 要好,甚至再用它来读源码,也没出现第一次那种分析时间过长以及耗 CPU 的问题了,应该是在这期间做了一些优化。

Kiro 提供了一种名为 spec 的模式,这种模式我之前也听说过,并且还有一些开源工具可以使一些原本不支持 spec 模式的 AI 代码编辑器拥有这种能力。大致的流程为,先给 AI 提一个相对模糊的需求,然后 AI 和你进行多轮对话,对需求细节进行细化,拆分为了多个子需求。等需求确认后,AI 会根据这个细化后的需求内容,生成详细的设计方案,期间你也可以和 AI 进行多轮对话,对需求及设计方案进行调整。之后 AI 会基于这个设计方案,生成一个任务列表,列表中包含多个子任务,期间你也可以让 AI 对任务、设计以及需求进行调整。等任务列表完成后,此时有三份文件:需求文档、设计方案和任务列表。AI 会根据任务列表,基于设计方案, 依次执行每一个子任务,来实现需求。这种方式对于复杂的任务有着较好的完成效果,比如说上一节提到的将 Kafka 运维工具 MCP 化的工作,如果使用 spec 模式的话,由 AI 接入设计执行方案和拆分任务,效率应该会更高一些。

之前提到的对于报警的分析,是需要人工来触发,然后由模型来执行分析,这存在两个问题:1)报警信息的数量较多,人工触发效率低下。2)模型分析的速度较慢,且如果每条报警信息均走模型分析,那么比较耗费 token。为了解决这个问题,想尝试由模型为常见的问题场景预先生成一系列分析规则,然后在报警触发的时候,直接走现成的分析规则进行分析。基于这种模式,我用 Kiro 的 spec 模式先搞了一个执行框架,并定义了分析规则的生成方式输出至相关的文档中,最后在让模型对于不同的问题场景,基于生成方式的文档,生成分析规则的代码。在 Grafana 触发报警时,由 WebHook 调用问题分析服务生成分析结果,最后将分析结果附带在报警信息中推送至指定的群组。为了便于分析规则的复用,框架支持将多个子规则关联到一个父规则上,在父规则进行分析时,可以调用子规则进行进一步的分析,这种场景适合于新建库架构下的同步 RPC 调用场景,上游模块在分析时发现是调用下游模块存在问题,那么就可以直接复用下游模块的分析规则进行分析。服务上线后,监控分析场景覆盖 80% ,涵盖常见的故障场景。监控分析代码 95% 由 ai 自动生成,人工仅需接入进行微调。

由于建库报警众多,没有一个统一的看板来确定当前还有哪些报警未恢复,所以用 Kiro 搞了一个建库报警的 Web 看板,可以在其上看到当前有哪些报警正在触发中、历史上发生了哪些报警,并且直接按照业务粒度进行聚合。另外为了防止有部分低优报警长时间未恢复被忽略,还部署了一个定时任务,每天扫描超过一天未恢复的报警,然后将这个信息推到报警群中。

上述这两个工作都是使用 Kiro 来完成的。对于诸如框架设计与实现以及 Web 看板服务的搭建这种比较复杂的需求,都是通过 spec 模式,先反复和 ai 对需求细节以及实现方式,最后由 ai 编码完成。对于分析规则的实现以及一些脚本的编写,则是走的普通的模式,直接给 ai 提需求,由 ai 直接生成代码。整体来说,人工接入直接编码的频次非常的低,基本只负责 review 代码以及 debug 了。

回看两三个月前还是纯手工编写代码,而如今完全换了一种开发模式,感觉有点恍如隔世,让我想起了在《2001:太空漫游》开头的一幕:原始人兴奋地挥舞骨头并将它扔向空中,骨头旋转着飞向天空,尚未落地,就变成了在太空中航行的飞船。

To infinity and beyond

拿杠铃卧推举例,假设极限能推起 100 kg,此时 ai 是训练辅助,那么前面提到的工作都是在推 80 kg 的时候让 ai 辅助使得推的更轻松,但如果一个人也是可以完成的话,那么 12 月所进行的工作,就是在推 120 kg 完全靠 ai 辅助才可以勉强完成。也就是说,这些工作完全超出我自己当前的能力边界,如果没有 ai 辅助,那么我一个人是完全 cover 不住的,甚至即使进行了单元测试、人工自测、小流量等多个验证环节,对于最终实现的效果我内心还是没底的,不确定是否存在什么隐性的 bug。不过换一个角度看,ai 在某些程度上确实可以扩大个人的能力边界,但这个能力边界不能比原先的超出太多,比如说当前由 ai 辅助推 120 kg 时,ai 突然放手(写出了 bug)还可以勉强支撑做一个缓冲,不至于被杠铃砸的很惨,但如果让它辅助推 160kg,那么当它松手时,杠铃就完全自由落体了。

12 月主要做的三个工作都有和 Kafka 相关。之前提到,新部署的 Kafka 集群都采用的是新的 Kraft 模式,版本为 3.9.1。12 月在三个机房大规模部署后,陆陆续续发现了三个问题:

  1. 当 raft log 中存储的 voter 信息超过半数有问题时,例如 ip 错误,无法选出新的 leader ,此时没有外部干预的手段来更新 raft log 中存储的 voter 信息,只能清空集群的 metadata 信息。
  2. 当 raft log 中存储的 voter 信息更新时,例如 voter 的 ip 变更,普通的 broker 是无法感知到这个变化的。普通 broker 用的 voter 配置是写死在配置文件中的,所以只能通过更新配置文件后重启来更新 voter 列表信息。
  3. 当前倒排索引模块仅会读取一个 topic 中的一个分片的数据,但是单个集群目前有快 300 个 broker ,且倒排索引模块会建 4 个 kafka 客户端实例。由于使用的客户端实现是 librdkafka ,这个库会为每一个 broker 都建一个线程,这就导致一个倒排索引实例会额外新增近 1200 个线程。一台机器会部署很多个倒排索引实例,这会导致线程数爆炸。而实际上由于只需要读一个分片的数据,所以预期一开始只需要和一个 broker 建立连接。

这是三个问题严重影响线上稳定性,且升级新版本或者修改配置都无法解决,所以只能改源码了。对于 Kafka 本身的代码,虽然我在国庆节期间大致过了一遍,但也仅限于有了一个大概的了解,而对于 librdkafka 的代码,是用纯 c 实现的,每个源码文件有大几千行。如果让我自己一个人来对源码进行修改,在目前阶段是完全不可能完成的任务。但是一切都那么凑巧:正好 8 月的时候接触了 ai 编程;正好 9 - 11 月通过大量的实践熟悉了 ai 编程的流程以及适应了新的开发模式;正好 10 月在尝试用 ai 辅助学习了 Kafka 的源码;正好在实践期间为源码解读和代码编写各迭代出了一份还算好用的系统提示词;正好 Claude 在 11 月底推出的 4.5 Opus 模型,而 Kiro 上正好可以用。所以,虽然这完全超出了我的能力访问,但为何不可以用 ai 来辅助试试呢?

前两个问题在之前的博文中都有介绍,第三个等线上修复验证没问题后也会发博文介绍改造细节(不能提前开香槟),这里就不再赘述了。解决问题的流程大致可以分为四步:

  • 首先,先让 ai 分析源码来确认这个问题是否真实存在
  • 然后,让 ai 基于问题分析的结果给出涉及到的功能点,并结合源码对这些功能点进行详细的分析
  • 接着,让 ai 基于问题以及相关功能点的分析结果,列出改造方案以及进行改造,这里可以使用 spec 模式
  • 最后,需要人工接入,首先的跑源码自带的单测,这些单测必须跑过,跑不过让 ai 分析原因。然后基于问题的场景搭建线下环境进行验证,最好相同的验证场景重复验证多次。

总之,在 ai 的辅助下,在 Kiro 上烧了 1600 + 的 credit 后,勉强算是对上面三个问题都给出了一个初版的解决方案,其中均涉及到对于源码的改造。在一个月内对开源项目的源码完成了三处改造,这在之前真是想都不敢想的。

另外要提一嘴的是,到 25 年末,公司内部的 ai 编辑器 comate 里加了非常多的国外模型,Claude 系的和 Gemini 系的都有,后面也会多多进行尝试。

碎碎念

对于 25 年,在工作方面我感觉最大的提升就是学习并实践了 ai coding ,从最初的了解 LLM ,到开发简单的 Agent ,再到使用它来生成代码片段,最后由它来完成一个完整独立的需求,期间感觉自己对于编码的思维模式发生了非常大的转变,这种感觉不亚于 16 年大二时从完全小白到入门了编程,类似于有一种飞升的体验。

新的业务需要大规模地使用 Kafka,单个集群的 broker 数量当前已经快 300 个了,可能后面还会增加,一方面存在资源的问题,因为 Kafka 需要独占磁盘,但是目前独占的磁盘比较匮乏,已经有存量实例无法迁移的情况了,另一方面我内心对这种大规模的部署是没底的,不确定会出现什么坑。当前组内负责 Kafka 的就是我一个人,所以后面相关的电话报警应该都会由我来负责,可能 26 年开始要睡不好觉了。

25 年年中晋升了 T4 ,至少在我所在的组内,至少三年一升已经成为了常态了,而且随着级别的升高,也不一定能晋升成功,今年有两个 T5 可能是因为这个原因离职了。另外上面提到的那位 T9 今年也离职了,听说是和一个 T8 竞争晋升名额,没竞争过。年末的时候入职了一位社招的新人,但是干了三个礼拜就跑路了,和两年前的情况一模一样,都是刚工作几年且是上海本地人,真是难绷。

运动

今年在撸铁方面有了非常大的进步,可能是还在新手福利期吧。目前训练的时间是工作日晚上和周末下午去公司健身房训练。作为会调集全身大部分肌肉的动作,目前硬拉和深蹲都找了合适的发力方式,卧推没找到,新的一年争取补齐以取得突破。

  • 体重:83.5 kg
  • pr
    • 卧推:110kg
    • 深蹲:160kg
    • 硬拉:200kg
  • 主要辅助项
    • 负重引体 25 kg 做组,每组 4 - 6 次
    • 杠铃推举 65 kg 做组,每组 4 - 6 次

书籍

  • 奥本海默传
  • 尼安德特人
  • 深渊上的火
  • 银河帝国 基地
  • 赛马娘 芦毛灰姑娘(漫画)
  • 银河帝国 基地与帝国
  • 银河帝国 第二基地
  • 乡土中国
  • 战时灯火
  • 暗淡蓝点
  • 万历十五年
  • 人类简史
  • 未来简史
  • 强风吹拂

影视

  • 《电锯人》第一部
  • 《电锯人蕾塞篇》电影
  • 《更衣人偶坠入爱河》第二季
  • 《熏香花朵凌然绽放》
  • 《夏日重现》
  • 《哪吒:魔童闹海》电影
  • 《南京照相馆》电影
  • 《机动战士高达 NT》
  • 《机动战士高达 逆袭的夏亚》
  • 《机动战士高达 uc 独角兽》
  • 《赛马娘 卢毛灰姑娘》
  • 《机动战士高达 GQuuuuuuX》
  • 《安多》第一季 & 第二季
  • 《星球大战 绝地传说》
  • 《变形金刚:起源》电影
  • 《Ave Mujica》
  • 《青之箱》
  • 《败犬女主太多了》第一季

还有一些番剧没追完,这里就不列了

装备

没备注表示没有更换

  • 手机:iPhone 15 Pro
  • 脑:MacBookPro M2 13寸(公司)、MacBookPro M3Max 16寸(个人)
  • 耳机:AirPods Pro 2
  • 笔记:主力切换为 Logseq ,Obsidian 用于写长文
  • 日程:滴答清单
  • 阅读:微信读书
  • 浏览器:Arc
  • AI 编辑器:Kiro(自费,40 刀/月,Claude 系模型)、Comate(公司的)、Trae(偶尔使用,84 刀/年,GPT 系模型)
  • AI 助手:Claude 系模型解决编程问题,Deepseek/豆包/GPT 解决日常问题。除豆包外,其余模型使用 Cherry Studio 调用

一些软件的年度总结

  • b 站年度 UP 主:央视新闻(难绷 .. 可能是因为每周都看世界周刊的原因)
  • 网易云音乐年度歌手:YOASOBI(有一段时间健身歌单里都是这个组合的歌曲)
  • 招商银行:年度收入增加 16% (但我实在没想到 25 年居然总共支出了 10w 多 … 还没细分析都花在哪儿了)

写在最后

对于 26 年目前还没有什么想法,目前唯一的愿望就是快点找到一个比较清晰的前进方向,25 年周末的时候因为没有一个明确的方向常常不自主地就打开了 b 站刷视频了。