技术探索与实践:从考研失败到深圳一线大厂的血泪踩坑指南
去年三月,我坐在自习室里看着考研成绩出来的那一刻,心里五味杂陈。389分,离目标院校差了12分。当时脑子里就一个念头:“完了,得找工作了。”
作为一个非科班出身、靠自学一路磕磕绊绊走过来的应届生,我其实一直有点技术洁癖——喜欢用 Mac 写代码(M1 芯片真香),Windows 仅限于测试兼容性;对底层原理有种近乎偏执的兴趣,比如看到 malloc 就想翻 glibc 源码;坐标深圳,投简历时自然瞄向了腾讯系那几家公司(毕竟鹅厂食堂真不是盖的)。
但现实很骨感。面试时被问到“如何做技术探索与实践”,我支支吾吾说了半天“看文档、写 demo”,结果挂得明明白白。后来进了某家对标腾讯的中型互联网公司,才真正体会到:技术探索不是炫技,而是为了解决业务痛点、提升交付效率、避免线上 P0 事故。
今天这篇文章,就是我在过去一年里,从“纸上谈兵”到“线上救火”的真实复盘。不讲高大上的理论,只聊怎么用有限的资源,在 deadline 压顶下高效完成一次靠谱的技术探索与实践。顺便,也会穿插一些被产品经理催需求、被运维甩锅、被测试提 bug 的日常(别问,问就是生活)。
起因:一个要命的需求 + 一道高频面试题
事情发生在去年双 11 前两周。我们团队负责一个核心交易链路的性能优化。产品经理拍着桌子说:“用户支付卡顿率太高了,必须压下去!否则 KPI 没了!”
我一查监控,发现瓶颈出在序列化/反序列化环节。当前用的是 JSON,数据量一大,CPU 直接飙到 80%。更惨的是,这模块是几年前外包写的,注释比代码还少,连单元测试都没有。
这时候,我脑子里蹦出一道经典面试题:“JSON 和 Protobuf 在性能上有啥区别?什么场景该用哪个?”
以前面试时,我只会背“Protobuf 更快、更小”,但到底快多少?小多少?兼容性咋办?升级成本多高?一问三不知。
这次,老板直接甩话:“你去搞个方案,下周上线。搞不定,你就去搞。”
(潜台词:搞不定就滚蛋)
行吧,只能硬着头皮上。
第一步:明确目标,别一上来就造轮子
很多新人(包括曾经的我)一听到“技术探索”,立马冲去 GitHub 找 star 最多的库,clone 下来跑个 demo,发个朋友圈:“今日份学习!”
然后呢?没了。
真正的技术探索,必须锚定三个问题:
- 业务目标是什么? → 降低 CPU 使用率,提升吞吐量
- 约束条件有哪些? → 必须兼容现有接口、不能改数据库 schema、上线窗口只有 5 天
- 失败代价多大? → 如果新协议解析失败,会导致用户支付中断 → P0 级事故
有了这三点,我才敢开始动手。否则就是“为了新技术而新技术”,最后只会被运维在群里 @:“兄弟,线上又崩了,是不是你改的?”
第二步:资源有限?那就精准打击
作为应届生,我手头资源极其有限:
- 没有专属测试环境(和 QA 共用)
- 没有性能压测权限(得找 SRE 申请,排队三天)
- 连本地调试都受限(Mac 上跑 Docker 有时候莫名其妙 network timeout)
怎么办?用最小成本验证核心假设。
我做了三件事:
1. 本地 micro-benchmark
先不管业务逻辑,只对比 JSON vs Protobuf 的 encode/decode 速度。用 Go 写了个简单 benchmark(因为我们后端是 Go):
// benchmark_serialization_test.go
func BenchmarkJSONMarshal(b *testing.B) {
data := generateTestData()
b.ResetTimer()
for i := 0; i < b.N; i++ {
json.Marshal(data)
}
}
func BenchmarkProtoMarshal(b *testing.B) {
data := generateProtoData()
b.ResetTimer()
for i := 0; i < b.N; i++ {
data.Marshal()
}
}
跑完结果如下(单位:ns/op):
| 操作 | JSON | Protobuf |
|---|---|---|
| Marshal (encode) | 12,450 | 2,100 |
| Unmarshal (decode) | 18,760 | 1,890 |
好家伙,Protobuf 快了 6 倍以上!而且生成的二进制体积只有 JSON 的 1/4。
但这只是理想情况。真实业务中,数据结构复杂、嵌套深、还有 optional 字段……会不会有坑?
2. 构建影子流量回放
我偷偷(其实是跟测试同学打了个招呼)把生产环境的请求日志 dump 了一份,脱敏后写了个 replay 工具,用影子流量同时跑 JSON 和 Protobuf 两套解析逻辑,对比结果是否一致。
关键代码:
// replay.go
func replayRequest(req *OriginalRequest) {
// 原始 JSON 解析
jsonResult, _ := json.Unmarshal(req.Payload)
// 新 Protobuf 解析
protoReq := &TradeRequest{}
protoResult, err := protoReq.Unmarshal(req.ProtoPayload)
if !reflect.DeepEqual(jsonResult, protoResult) {
log.Fatalf("Mismatch! ID=%s", req.ID)
}
}
跑了 10 万条数据,发现一个问题:Protobuf 默认忽略 unknown fields,而 JSON 会保留。我们的旧接口有些字段是动态透传的,直接丢掉会导致下游服务报错。
解决方案:在 Protobuf 定义里加 reserved 字段,或者用 Any 类型包裹动态部分。虽然丑,但能保兼容。
3. 做渐进式灰度
直接全量切换?想都别想。我设计了一个双写 + 双读 + 开关控制的方案:
- 写入时:同时写 JSON 和 Protobuf 到 Redis(不同 key)
- 读取时:通过配置中心开关决定读哪种
- 监控:对比两种格式的解析耗时、错误率
这样即使 Protobuf 出问题,秒级切回 JSON,用户无感知。
第三步:别被“最佳实践”绑架
网上一堆文章说:“Protobuf 是银弹”、“必须用最新版本”、“一定要配合 gRPC”。
但在真实项目里,最佳实践 = 能跑 + 能维护 + 能兜底。
我们团队用的是 HTTP + 自定义协议,强行上 gRPC 得改整个网关,工期不够。于是我们做了个妥协:HTTP Body 里塞 Protobuf 二进制,Content-Type 设为 application/x-protobuf。
客户端(前端 + App)也得改。但 App 版本碎片化严重,老版本不支持 Protobuf 怎么办?
答案:服务端自动识别 Content-Type。如果是 application/json,就走老逻辑;如果是 x-protobuf,走新逻辑。用中间件搞定:
func ProtoMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Content-Type") == "application/x-protobuf" {
// 解析 protobuf
body, _ := io.ReadAll(r.Body)
req := &TradeRequest{}
req.Unmarshal(body)
r.Context = context.WithValue(r.Context, "request", req)
} else {
// 保持原样
}
next(w, r)
}
}
虽然代码有点 dirty,但在 deadline 面前,优雅是奢侈品。
第四步:把探索过程变成“可复用资产”
做完这次优化,CPU 降了 40%,支付成功率提升 0.8%(听起来不多,但按 GMV 算是百万级收益)。老板很高兴,请我吃了顿海底捞(其实是团建,但我也去了)。
但更重要的是,我把整个过程沉淀成了团队内部的《序列化选型决策树》:
- 数据是否需要跨语言? → 是 → Protobuf / Thrift
- 是否需要人类可读? → 是 → JSON / YAML
- 是否对带宽极度敏感? → 是 → MessagePack / CBOR
- 是否已有成熟生态? → 否 → 别造轮子!
还写了份《Protobuf 升级 checklist》:
- 字段序号不要乱改
- optional 字段加默认值
- 保留 old fields 至少两个版本
- 客户端必须支持 fallback
这些文档后来成了新同学入职必读,甚至被隔壁组拿去参考。技术探索的价值,不在于你用了多牛的 tech stack,而在于你让团队少踩了多少坑。
面试题?现在我能答出花了
最近帮学弟模拟面试,又被问到那道题:“JSON 和 Protobuf 选型?”
我现在会这么答:
“首先看业务场景。如果是内部微服务通信,且对性能敏感,Protobuf 更优;如果是 Web API 或需要调试,JSON 更合适。
其次看团队能力。如果前端、移动端、后端都能快速接入,且有自动化工具链(比如 protoc-gen-go),那可以推。
最后看演进成本。我们上次用双写+灰度的方式平滑迁移,确保零故障。
对了,我还跑过 benchmark,Protobuf 在 encode/decode 速度上快 5-6 倍,体积小 70%,但调试困难,需要配套工具。”
你看,面试题的答案,从来不在八股文里,而在你解决过的实际问题中。
给同样“失败过”的人的建议
作为考研失败后杀入职场的应届生,我深知那种“学历焦虑”和“技术恐慌”。但这一年下来,我悟了:
- 公司不 care 你是不是研究生,只 care 你能不能搞定问题
- 技术深度 ≠ 知道多少框架,而是知道在什么场景下不用它
- 资源少不可怕,可怕的是闭门造车。多问、多试、多记录
上周五晚上加班到 11 点,修复一个因 Protobuf 字段类型 mismatch 导致的线上 bug。当时真的想砸电脑。但 fix 推上去,监控曲线恢复正常那一刻,突然觉得:“嘿,老子还真行。”
所以,别怕从失败开始。技术这条路,本就是踩着坑往前走的。
共勉。
附:关键配置 & 工具清单(自取)
| 类别 | 工具/配置 | 说明 |
|---|---|---|
| Benchmark | go test -bench=. |
本地性能对比 |
| 流量回放 | 自研 replay 工具(基于 Kafka 日志) | 验证兼容性 |
| 灰度控制 | Apollo 配置中心 + feature flag | 动态开关 |
| Protobuf | protoc --go_out=. trade.proto |
生成 Go 代码 |
| 监控 | Prometheus + Grafana | 对比 CPU/延迟 |
注:所有代码均已在生产环境验证,如有雷同,纯属巧合(或者你也踩过同样的坑)。
P.S. 如果你在深圳,也在腾讯系公司附近晃悠,欢迎约咖啡聊聊技术(或者吐槽产品经理)。考研失败不可怕,可怕的是从此不敢探索。

评论 0