技术文章

朱敏_算法
2025-12-21 09:29
阅读 259

早起的后端人,都在优化什么?

每天早上八点,办公室还没几个人,我泡好咖啡,打开终端,顺手 git pull 一下主干代码——这几乎成了我的晨间仪式。作为一枚 DevOps 工程师,最近一边在现公司扛着双11压测的锅,一边偷偷刷 LeetCode 准备跳槽。说来惭愧,本来只想搞搞 CI/CD 流水线自动化,结果因为一个线上慢查询事故,被拖进了后端性能调优的深水区。

这事得从上个月说起。那天周五晚上九点,刚准备关电脑去吃火锅,钉钉突然炸了:“用户下单接口 P99 延迟飙到 2.3 秒!” 我心里一咯噔——这可是核心链路,平时稳在 150ms 以内。打开 Grafana 一看,数据库 CPU 打满,慢日志里全是同一条 SQL:SELECT * FROM orders WHERE user_id = ? AND status IN (...) ORDER BY created_at DESC LIMIT 10

乍看没啥问题,但数据量一上来就原形毕露。用户表早过亿了,status 字段又没建复合索引。更骚的是,前端为了“用户体验”,一口气拉了 5 个不同状态的订单,后端直接拼成一个大查询。产品经理还振振有词:“反正都是同一个用户的数据,合并查不是更快吗?”

我差点把键盘砸了。


技术探索,往往始于一场“线上救火”

这次事故让我意识到:DevOps 不只是搭管道、写脚本,更要深入业务逻辑,理解后端的“代码人生”。你不能只盯着 K8s Pod 起没起来,还得知道为啥某个服务突然 CPU 飙升。于是,我开始系统性地补后端知识,尤其是性能优化这块。

我给自己定了个小目标:用 DevOps 的视角,重新审视后端架构的瓶颈点。不是单纯地加机器、扩副本,而是从代码、SQL、缓存、部署策略全链路找优化空间。

第一个动作:拆分大查询
别再让一条 SQL 背负所有状态的查询压力。改成按状态分批次查,虽然请求次数多了,但每条都走索引,响应时间反而降下来了。配合 Redis 缓存热点用户的订单列表,P99 直接干到 80ms。

// 伪代码示意:原来的一锅炖
List<Order> orders = orderMapper.findByUserIdAndStatusIn(userId, Arrays.asList("paid", "shipped", "delivered"));

// 改造后:分状态 + 缓存
for (String status : statuses) {
    String cacheKey = "user:orders:" + userId + ":" + status;
    List<Order> cached = redis.get(cacheKey);
    if (cached == null) {
        cached = orderMapper.findByUserIdAndStatus(userId, status); // 单状态查询,走索引
        redis.setex(cacheKey, 300, cached);
    }
    allOrders.addAll(cached);
}

第二个动作:给 CI/CD 流水线加上“性能门禁”
以前我们只跑单元测试,现在我在 Jenkins Pipeline 里加了一步:对涉及数据库变更的 PR,自动跑 explain 分析,拒绝全表扫描的 SQL 合并

stage('SQL Performance Check') {
    steps {
        sh '''
            # 提取本次提交中新增/修改的 SQL 文件(或 MyBatis XML)
            git diff --name-only HEAD~1 | grep -E "\\.xml$|\\.sql$" | xargs -I {} sh -c '
                if ! mysql -e "EXPLAIN $(cat {})" | grep -q "Using index"; then
                    echo "ERROR: Full table scan detected in {}"
                    exit 1
                fi
            '
        '''
    }
}

当然,这招一开始被后端兄弟骂惨了:“你管得太宽了吧?” 但等他们发现上线后再也没因为慢 SQL 被半夜 call 起来,态度立马 180 度转弯。


架构设计的本质,是权衡

很多人以为性能优化就是堆缓存、上 SSD、换更好的框架。但真正难的,是在复杂业务约束下做合理取舍

比如我们有个报表服务,每天凌晨跑批处理,生成上万张商户日报。最初用 Spring Boot 单机跑,经常超时。有人提议上 Flink,我说先别急——Flink 虽强,但引入新组件意味着运维成本、学习曲线、故障排查复杂度上升。

我做了个折中:保留 Spring Batch,但用 Kubernetes Job 拆分成小任务并行跑。每个 Job 处理一个商户 ID 段,失败自动重试,资源隔离。结果:处理时间从 4 小时缩到 45 分钟,而团队只需维护熟悉的 Java 技术栈。

方案 开发成本 运维复杂度 故障恢复 最终选型
单机 Spring Boot 差(全挂)
Flink 流处理 ❌(过度设计)
K8s Job + Spring Batch 好(局部重试)

你看,技术选型不是比谁用的工具更酷,而是看谁能用最稳的方式把活干完。这也是我越来越觉得 DevOps 和后端必须深度协同的原因——你得懂他们的痛点,才能设计出真正落地的自动化方案。


代码人生,不止于 CRUD

说实话,刚入行那会儿,我也觉得“能跑就行”。直到有次线上 OOM,dump 出来一看,某个 DTO 里塞了个 10MB 的 base64 图片字段,还被序列化进 Redis……那一刻我才明白:每一行代码,都是对系统稳定性的投票

现在我写自动化脚本,都会多问一句:“这个操作会不会在极端场景下爆炸?” 比如部署脚本里加了 --timeout=300s,监控告警设置了“连续失败 3 次才通知”,避免半夜被误报吵醒。

最近刷题时也发现,LeetCode 里的算法思维其实在运维场景也能用。比如用滑动窗口思想优化日志采样率,用图论模型分析微服务依赖拓扑。技术是相通的,关键是你愿不愿意跨出去那一步


写在最后:别把自己活成工具人

回看这一年,从只会写 Ansible Playbook,到现在能和后端一起重构核心链路,最大的收获不是技术本身,而是建立了一种“端到端负责”的心态

DevOps 不是夹在开发和运维之间的传话筒,而是系统稳定性的守门人。你要敢对烂代码说不,也要能用自动化手段帮团队提效。

下周我就要去新公司面试了,岗位是 SRE(站点可靠性工程师)。面试官问我:“你为什么想转 SRE?” 我说:“因为我受够了半夜被慢 SQL 叫醒。我想从根上解决问题。”

其实吧,无论叫 DevOps、SRE 还是后端工程师,我们都在过同一种代码人生——在 bug 与 deadline 的夹缝中,努力让系统跑得更快、更稳、更优雅

共勉。

评论 0

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