技术探索与实践:从考研失败到深圳一线大厂的血泪踩坑指南

深度学习小白
2025-12-13 22:41
阅读 231

去年三月,我坐在自习室里看着考研成绩出来的那一刻,心里五味杂陈。389分,离目标院校差了12分。当时脑子里就一个念头:“完了,得找工作了。”

作为一个非科班出身、靠自学一路磕磕绊绊走过来的应届生,我其实一直有点技术洁癖——喜欢用 Mac 写代码(M1 芯片真香),Windows 仅限于测试兼容性;对底层原理有种近乎偏执的兴趣,比如看到 malloc 就想翻 glibc 源码;坐标深圳,投简历时自然瞄向了腾讯系那几家公司(毕竟鹅厂食堂真不是盖的)。

但现实很骨感。面试时被问到“如何做技术探索与实践”,我支支吾吾说了半天“看文档、写 demo”,结果挂得明明白白。后来进了某家对标腾讯的中型互联网公司,才真正体会到:技术探索不是炫技,而是为了解决业务痛点、提升交付效率、避免线上 P0 事故。

今天这篇文章,就是我在过去一年里,从“纸上谈兵”到“线上救火”的真实复盘。不讲高大上的理论,只聊怎么用有限的资源,在 deadline 压顶下高效完成一次靠谱的技术探索与实践。顺便,也会穿插一些被产品经理催需求、被运维甩锅、被测试提 bug 的日常(别问,问就是生活)。


起因:一个要命的需求 + 一道高频面试题

事情发生在去年双 11 前两周。我们团队负责一个核心交易链路的性能优化。产品经理拍着桌子说:“用户支付卡顿率太高了,必须压下去!否则 KPI 没了!”

我一查监控,发现瓶颈出在序列化/反序列化环节。当前用的是 JSON,数据量一大,CPU 直接飙到 80%。更惨的是,这模块是几年前外包写的,注释比代码还少,连单元测试都没有。

这时候,我脑子里蹦出一道经典面试题:“JSON 和 Protobuf 在性能上有啥区别?什么场景该用哪个?”

以前面试时,我只会背“Protobuf 更快、更小”,但到底快多少?小多少?兼容性咋办?升级成本多高?一问三不知。

这次,老板直接甩话:“你去搞个方案,下周上线。搞不定,你就去搞。”
(潜台词:搞不定就滚蛋)

行吧,只能硬着头皮上。


第一步:明确目标,别一上来就造轮子

很多新人(包括曾经的我)一听到“技术探索”,立马冲去 GitHub 找 star 最多的库,clone 下来跑个 demo,发个朋友圈:“今日份学习!”
然后呢?没了。

真正的技术探索,必须锚定三个问题:

  1. 业务目标是什么? → 降低 CPU 使用率,提升吞吐量
  2. 约束条件有哪些? → 必须兼容现有接口、不能改数据库 schema、上线窗口只有 5 天
  3. 失败代价多大? → 如果新协议解析失败,会导致用户支付中断 → 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

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝