从产品到后端:我在成都写代码踩过的坑
每天早上八点,当大部分成都的程序员还在赖床吃火锅味儿的早餐时,我已经坐在电脑前敲代码了。可能是因为之前做产品经理养成的习惯——总想比开发早一步上线需求(笑)。如今转战后端开发快三年,身份切换得还算丝滑,但说实话,从画原型图到调分布式锁,中间摔过的跟头可不少。
这篇文章不是什么高大上的架构白皮书,就是我这个“半路出家”的斜杠青年,在真实项目里摸爬滚打攒下的一点经验。如果你也正从非技术岗转向后端,或者在技术探索路上感到迷茫,或许能从我的踩坑史里少走几步弯路。
那个让我通宵改配置的“优雅关闭”问题
去年双11前两周,我们团队负责的订单服务突然在线上出现大量 Connection reset by peer。监控面板红得像火锅底料,运维老哥在群里@我:“兄弟,你这服务是不是没处理好优雅关闭?”
我当时一脸懵:优雅关闭?不就是加个 @PreDestroy 吗?结果查日志才发现,K8s 滚动更新时,Pod 被 SIGTERM 干掉得太快,正在处理的请求直接被掐断,用户下单失败率飙升。那一刻我真想把产品经理拉过来对线——当初谁说“简单加个接口就行”的?
技术选型与权衡
我们用的是 Spring Boot + Dubbo 的组合。要实现真正的优雅关闭,得同时考虑:
- HTTP 请求(Tomcat)
- RPC 调用(Dubbo)
- 线程池任务
- 外部连接(比如数据库、MQ)
翻遍官方文档,发现 Spring Boot 2.3+ 原生支持 graceful shutdown,但 Dubbo 得自己处理。于是参考《Spring实战(第5版)》和《分布式系统原理与范式》里的思路,搞了个混合方案:
# application.yml
server:
shutdown: graceful # 启用 HTTP 优雅关闭
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 等待30秒
然后在 Dubbo 侧注册一个 ShutdownHook:
@Component
public class DubboGracefulShutdown {
@EventListener
public void onContextClosed(ContextClosedEvent event) {
// 先反注册服务,不再接收新请求
ProtocolConfig.destroyAll();
// 等待正在执行的任务完成(最大30秒)
try {
Thread.sleep(25000); // 留点余量给 Tomcat
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
上线后,滚动更新期间的错误率从 5% 降到了 0.02%,运维终于没在半夜打电话骂我了。
书籍救我狗命:从“能跑就行”到“能扛住流量”
刚转后端那会儿,写代码全靠 Stack Overflow 和 CSDN 博客。有一次为了优化 MySQL 查询,我把 SELECT * 改成只查必要字段,自以为性能提升了,结果 DBA 找上门:“你这没加索引,全表扫描更慢了!”
羞愧之下,我开始系统性读书。不是那种“三天速成微服务”的快餐书,而是真正沉下心啃经典。对我帮助最大的三本:
| 书籍 | 关键收获 | 应用场景 |
|---|---|---|
| 《高性能MySQL》 | 覆盖索引、回表、最左前缀 | 订单查询优化 |
| 《数据密集型应用系统设计》 | 分布式一致性、幂等设计 | 支付状态同步 |
| 《Java并发编程实战》 | 线程池参数、AQS原理 | 异步任务调度 |
举个例子,《DDIA》里讲的“幂等令牌”机制,直接解决了我们支付回调重复的问题。以前靠数据库唯一索引防重,但高并发下还是有漏网之鱼。现在改成:
- 客户端生成
request_id - 服务端先查 Redis 是否存在该 ID
- 不存在则执行业务逻辑,并 setex 过期时间
public String handlePaymentCallback(PaymentRequest req) {
String requestId = req.getRequestId();
Boolean isAbsent = redisTemplate.opsForValue()
.setIfAbsent(requestId, "processed", Duration.ofMinutes(10));
if (Boolean.FALSE.equals(isAbsent)) {
return "DUPLICATE_REQUEST"; // 直接返回,不进DB
}
// 执行真实支付逻辑...
return "SUCCESS";
}
线上重复支付投诉量下降了98%,老板看我的眼神都温柔了。
成都节奏 vs 技术焦虑:如何平衡深度与广度?
在成都,生活节奏慢,但技术迭代可一点没慢下来。上周五晚上,组里讨论要不要把 Kafka 换成 Pulsar。新来的实习生激情演讲:“Pulsar 多租户、分层存储、无限扩展……”
我默默喝了口茶(没错,程序员也可以喝茶),问了一句:“咱们现在的消息积压问题,真的是 Kafka 架构限制导致的吗?”
后来发现,根本原因是消费者线程池配置不合理,加上反序列化太重。一顿优化后,吞吐量从 5k/s 提升到 18k/s,完全够用。有时候不是工具不行,是我们没用对。
这也让我反思:技术人容易陷入“新框架崇拜”,总觉得换个轮子就能解决问题。但实际上,80% 的性能瓶颈,都藏在业务代码的细节里。
比如我们有个导出接口,以前是全量查 DB 再拼 Excel,内存爆炸是常事。后来改成:
- 分页流式查询
- 边查边写入本地临时文件
- 用 EasyExcel 的 SAX 模式避免 OOM
关键代码就这几行:
@GetMapping("/export")
public void exportOrders(HttpServletResponse response) throws IOException {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename=orders.xlsx");
ExcelWriter writer = EasyExcel.write(response.getOutputStream()).build();
// 分页流式处理
int pageSize = 1000;
for (int i = 0; ; i++) {
List<Order> orders = orderMapper.selectPage(i, pageSize);
if (orders.isEmpty()) break;
writer.write(orders, EasyExcel.writerSheet().build());
}
writer.finish(); // 自动 flush
}
内存占用从 2GB+ 降到 200MB,再也不用担心被 OOM kill 了。
给转型者的三条血泪建议
作为从产品转技术的“异类”,我总结了三条特别实在的经验:
1. 别怕暴露无知,但要带着解决方案提问
刚转岗时,我不懂什么是“CAP 理论”,直接问架构师。他没嘲笑我,反而推荐了《从Paxos到Zookeeper》。关键是你得表现出“我想搞懂,不只是甩锅”。现在我每次提问题,都会附上自己的排查思路:“我看了日志,怀疑是线程阻塞,试过 jstack,这是堆栈信息……”
2. 用产品思维做技术设计
以前画 PRD 时,我会思考用户路径;现在写接口,也会想“调用方怎么用最爽”。比如设计一个风控接口,除了返回 pass/reject,还加上 reason_code 和 suggestion 字段,前端能直接展示友好提示。技术价值最终要落到用户体验上。
3. 晨间黄金两小时,留给最难的问题
成都的早晨安静,咖啡香配上键盘声,是我一天效率最高的时候。复杂的设计、难啃的源码,我都安排在这两小时。下午留给会议、CR 和救火。保护你的深度工作时间,比刷100篇技术文章都管用。
最后:技术没有银弹,但有套路
写这篇文章的时候,窗外是成都难得的晴天。回想这几年,从被开发吐槽“这需求不合理”,到现在被测试夸“接口文档写得清晰”,身份转变的背后,其实是思维方式的升级。
技术探索从来不是一蹴而就的。它需要你:
- 读经典(别只看博客)
- 抠细节(性能藏在每一行代码里)
- 敢动手(本地搭个集群试试又不会炸)
- 会复盘(事故是最好的老师)
上周我们刚上线了一个基于 Seata 的分布式事务方案。过程中遇到 AT 模式锁冲突、TC 高可用等问题,但靠着《微服务架构设计模式》里的理论指导,加上反复压测验证,最终平稳落地。上线那天,我请全组吃了顿火锅——成都程序员的庆功仪式,必须要有毛肚和鸭血。
所以,别被“大厂架构”吓到。每个复杂的系统,都是从一行能跑的代码开始的。你缺的不是天赋,而是开始动手的勇气,和持续迭代的耐心。
共勉。

评论 0