技术文章
早起的后端人,都在优化什么?
每天早上八点,办公室还没几个人,我泡好咖啡,打开终端,顺手 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