关于技术探索与实践的一些经验:一个北漂深漂程序员的深夜碎碎念
上周五晚上 11 点半,我还在公司加班。窗外深圳湾的灯火依旧通明,隔壁工位的同事刚刚被产品经理拉去对需求——又是那种“明天上线前加个开关”的经典操作。而我呢?正对着一个诡异的 Spring Boot 应用内存泄漏问题抓耳挠腮,心里默默盘算着下个月房贷怎么还。
是的,我就是那个刚在深圳咬牙上车、背着 30 年房贷的北漂(现在应该叫深漂了)程序员。坐标南山科技园,就职于某鹅厂系公司,日常主攻后端开发,偶尔兼职“背锅侠”。写这篇文章的时候,其实是想给过去的自己留点念想——也顺便记录下这几年在技术路上踩过的坑、熬过的夜、和那些差点让我砸键盘的瞬间。
起因:不是我想折腾,是业务逼的
去年双 11 前夕,我们组接到一个“紧急”任务:把老系统里一个日活百万级的营销活动模块重构掉。原来的架构是 PHP + MySQL,跑在物理机上,运维大哥每次扩容都像在拆炸弹。领导拍板:“这次必须上微服务,用 Spring Boot 重写,要高可用、要弹性、要能扛住流量洪峰!”
听起来很酷,对吧?但现实是:资源有限、时间紧迫、人手紧张。我们组总共 4 个后端,其中两个还在支援另一个项目。我作为“稍微懂点架构”的那个(其实只是看了几篇掘金文章),被推上了技术方案设计的位置。
那一刻,我脑子里只有一句话:“完了,又要熬夜了。”
踩坑一:Spring Boot 不是银弹,配置不当反成毒药
很多人以为用了 Spring Boot 就自动“现代化”了,其实不然。我们一开始直接套用官方 starter,加上默认配置,本地跑得飞快。结果一上测试环境,QPS 刚到 500,CPU 就飙到 90%+。
排查了一整晚,才发现问题出在 线程池和连接池配置 上。Spring Boot 默认的 HikariCP 连接池最大连接数是 10,而我们的服务要同时调用 3 个下游接口(用户中心、商品中心、风控中心),每个请求都要开新连接。高峰期连接池直接打满,大量请求排队,响应时间从 200ms 涨到 5s+。
更惨的是,我们还没加熔断机制。某个下游服务偶尔抖动一下,整个链路雪崩。测试同学第二天早上来找我:“你们这服务是不是又挂了?监控图都平了。”
当时真的想砸电脑。
解决方案:别信默认值,按需调优
我们花了两天时间重新梳理资源使用模型,最终做了以下调整:
# application-prod.yml
spring:
datasource:
hikari:
maximum-pool-size: 50
minimum-idle: 10
connection-timeout: 3000
idle-timeout: 600000
max-lifetime: 1800000
server:
tomcat:
max-threads: 200
min-spare-threads: 20
同时引入 Resilience4j 做熔断降级:
@CircuitBreaker(name = "user-service", fallbackMethod = "fallbackGetUser")
public User getUser(Long id) {
return userClient.get(id);
}
private User fallbackGetUser(Long id, Exception e) {
log.warn("User service degraded, id: {}", id, e);
return User.defaultUser();
}
教训:Spring Boot 的“约定优于配置”是把双刃剑。在高并发场景下,你必须清楚每一项资源配置背后的含义,否则默认值可能成为性能瓶颈的导火索。
踩坑二:资源管理不当,线上 OOM 直接送你进事故复盘会
上线前一周,我们在预发环境做压测。一切看起来 OK,直到凌晨 2 点,监控报警:JVM Old Gen 占用 98%。
赶紧 jstack + jmap 一顿操作,发现是缓存没设 TTL,加上对象引用没清理,导致大量 MarketingActivityConfig 对象堆积在堆里。更讽刺的是,这段代码是我写的——为了“提升性能”,我把所有活动配置全加载到内存,结果忘了活动是有生命周期的!
那天早上,运维大哥黑着脸把我叫过去:“兄弟,你这 OOM 差点把整个 K8s namespace 拖垮,Pod 重启了 7 次。”
我只能苦笑:“房贷压力大,脑子不好使了。”
教训:资源≠无限,用完记得释放
后来我们做了三件事:
- 缓存加 TTL:用 Caffeine 替代手写 Map,设置过期时间
- 引入内存监控:通过 Micrometer + Prometheus 暴露 JVM 指标
- 定期 Review 资源持有逻辑:尤其是静态集合、ThreadLocal、Stream 未关闭等
// 改造前(危险!)
private static Map<Long, Activity> cache = new HashMap<>();
// 改造后(安全)
private final Cache<Long, Activity> activityCache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(1000)
.build();
核心原则:任何“持有”资源的操作(内存、连接、文件句柄),都必须有明确的释放策略。别让“优化”变成“埋雷”。
踩坑三:求职时吹的牛,上班后得自己圆
说到这个就有点扎心了。去年我跳槽面试时,为了显得“有架构视野”,在简历上写了“主导微服务治理”、“精通 Spring Cloud Alibaba”。结果入职后发现,团队连 Nacos 都没上,服务注册还是靠硬编码 IP。
更尴尬的是,leader 让我牵头搞服务注册中心迁移。我当时内心 OS:“我只是背了八股文啊!”
但没办法,房贷不会等你准备好。于是硬着头皮啃文档、搭集群、写脚本、灰度发布……中间还因为 Nacos 配置推送延迟,导致一次灰度流量打到了旧版本,被 QA 抓了个正着。
不过也正是这次“赶鸭子上架”,让我真正理解了服务发现、配置中心、元数据管理这些概念的实际意义。技术深度往往是在解决问题的过程中被迫长出来的。
技术选型:不是越新越好,而是“刚好够用”
我们曾经纠结要不要上 Kafka。业务场景其实很简单:用户参与活动后,异步发个通知、记个日志。但团队里有个同学极力推荐 Kafka,理由是“大厂都在用”。
结果呢?搭集群、配 ZooKeeper、写消费者组、处理重复消费……整整两周,就为了一个每天几万条的消息量。运维大哥吐槽:“你们这 Kafka 集群比我们核心交易系统的还复杂。”
最后我们换成了 RabbitMQ + 内部队列封装,开发效率提升 50%,运维成本直降。技术选型的核心不是“酷不酷”,而是 ROI(投入产出比)。
| 方案 | 开发成本 | 运维成本 | 扩展性 | 适合场景 |
|---|---|---|---|---|
| Kafka | 高 | 极高 | 极强 | 百万级 QPS、流处理 |
| RabbitMQ | 中 | 中 | 中 | 万级 QPS、可靠投递 |
| Redis Stream | 低 | 低 | 弱 | 轻量级异步、临时队列 |
记住:你的资源(人力、时间、机器)是有限的。别为了“技术先进性”牺牲交付效率——尤其是在 deadline 压顶、房贷待还的现实世界里。
代码质量:别让“能跑就行”毁了你的职业生涯
我们组有个不成文的规定:CR(Code Review)不过,不准合代码。一开始我觉得烦,后来真香。
有一次我写了个“巧妙”的 SQL,用子查询嵌套三层,自以为性能优化到位。结果 CR 时被 senior 一眼看穿:“这玩意儿在数据量大了之后会全表扫描,而且可读性为零。”
他改成了 JOIN + 索引覆盖,执行时间从 1.2s 降到 30ms。那一刻我悟了:代码不是写给机器看的,是写给人看的。
现在我写代码会多问自己几个问题:
- 这段逻辑别人能看懂吗?
- 如果明天我离职了,接手的人会不会骂我?
- 这个异常真的处理了吗?还是只是
e.printStackTrace()?
尤其在 Spring Boot 项目里,别滥用 @Autowired 和 static 方法,别让 Service 层变成上帝类。良好的分层和单一职责,是后期维护的生命线。
最后的碎碎念:技术探索 ≠ 盲目追新
作为一个背着房贷的普通程序员,我深知:我们没有资本“纯玩技术”。每一次技术探索,都必须服务于业务价值或个人成长(比如跳槽涨薪)。
我现在的学习策略是:
- 带着问题学:遇到瓶颈再深入,比如“为什么 Spring Boot 启动这么慢?” → 研究自动配置原理
- 小步快跑验证:新技术先在非核心模块试水,比如用 GraalVM 编译一个工具脚本
- 输出倒逼输入:像今天这样写博客,逼自己理清思路
上周我终于把那个内存泄漏问题解决了——原来是某个第三方 SDK 在 Spring Context 关闭时没注销监听器。修复后,服务稳定运行一周,Leader 在周会上表扬了我。
那一刻,虽然房贷账单还在,但心里踏实了不少。
写在最后
技术这条路,没有捷径。所谓的“经验”,不过是把别人的坑自己再踩一遍,然后记下来别再掉进去。作为深漂程序员,我可能永远成不了架构师,但至少,我想做一个靠谱的、能交付的、不让队友背锅的后端工程师。
如果你也在深夜调试 Spring Boot 应用,也在为资源不足发愁,也在求职和房贷之间挣扎——别慌,你不是一个人。
关掉 IDE 前,记得 git push。
明天,继续搬砖。
(完)
注:本文所有事故均为真实经历改编,人物已做模糊处理。如有雷同,说明你也经历过“程序员的宿命”。

评论 0