从传统行业转码三年,我终于把“技术探索”这事整明白了
去年冬天的一个深夜,我在公司加班改一个 Spring Boot 应用的配置文件。窗外成都的湿冷空气直往工位里钻,手边的咖啡早就凉透了。突然,测试群里弹出一条消息:“线上服务又崩了,K8s Pod 一直在重启,快看!”——那一刻,我真的想砸电脑。
但转念一想,三年前我还是个在建材市场跑业务的销售,每天跟客户吹牛、喝酒、签单,哪想过自己会坐在写字楼里,为一个 CrashLoopBackOff 状态抓耳挠腮?更没想到,有一天我能靠研究开源项目源码、搞云原生架构,把简历写得比以前卖瓷砖时的报价单还厚实。
今天这篇文,不讲高大上的理论,就聊聊我是怎么从一个“代码小白”,在实战中一点点理解“技术探索”这四个字的真正分量的。如果你也刚转行、或者正卡在某个技术瓶颈上,或许能从我的狼狈经历里找到点共鸣。
起因:老板一句“我们要上云”,把我推下火坑
时间倒回2023年初。我们团队(一家做本地生活服务的中型公司)决定全面拥抱云原生。原因很现实:老系统扛不住双11流量,去年光是扩容就花了二十多万,运维大哥差点辞职。老板拍板:“全上 Kubernetes,微服务化,Spring Boot 重构!”
而我,作为一个入职不到一年的“高龄新人”,因为之前在自学 K8s 时顺手给团队搭了个 Helm Chart 模板,莫名其妙被拉进了核心重构组。产品经理笑眯眯地说:“小王啊,你不是懂云原生吗?这个新订单服务就交给你了,两周上线哦~”
我当时内心 OS:我懂个锤子云原生!我连 Deployment 和 StatefulSet 的区别都是昨天晚上才搞明白的!
但简历上已经写了“熟悉 Spring Boot + K8s 微服务架构”,总不能打脸吧?
实战第一关:Spring Boot 应用怎么优雅地“死”?
新订单服务基于 Spring Boot 3.x 开发,要求支持水平扩展、健康检查、配置热更新。听起来很标准,对吧?但问题就出在“优雅”两个字上。
第一次部署到 K8s,测试一压测,Pod 直接 OOM Kill。日志里一堆:
java.lang.OutOfMemoryError: Java heap space
我心想:是不是堆内存设小了?于是把 -Xmx 从 512m 调到 1G。结果呢?Pod 内存使用直接飙到 1.2G,触发 K8s 的 memory limit,又被干掉。
这时候我才意识到:光会写 CRUD 接口没用,你得懂 JVM 和容器怎么“共处”。
翻了一堆开源项目(比如 Spring Cloud Gateway、Nacos 的 Dockerfile),发现大家都在用 -XX:+UseContainerSupport 这个参数。原来,JVM 默认不知道自己跑在容器里,会按宿主机的内存来分配堆,导致实际使用远超限制。
于是我在 Dockerfile 里加上:
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
配合 K8s 的资源限制:
resources:
limits:
memory: "1Gi"
cpu: "1"
requests:
memory: "512Mi"
cpu: "500m"
这次稳了!Pod 内存使用稳定在 700MB 左右,OOM 消失。
但你以为这就完了?No no no。
第二关:探针配错,服务“假死”还活着
K8s 有 liveness 和 readiness 探针。我一开始图省事,直接抄了 Spring Boot Actuator 的 /actuator/health:
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
结果某天数据库连接池爆了(别问,问就是测试环境 DB 没加索引),/health 返回 DOWN,K8s 立刻 kill 掉 Pod 重建。但问题是——这时候服务其实还能处理部分请求(比如缓存命中的查询),直接 kill 太粗暴了!
查了 Spring Boot 文档才发现,/health 是聚合状态,包含 db、disk、redis 等。而 readiness 探针应该只关心“是否准备好接收流量”,比如依赖的服务是否连通;liveness 才该判断“是否彻底挂了”。
于是我把 readiness 改成只检查关键依赖:
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
并在 application.yml 里配置:
management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: health,info
health:
livenessstate:
enabled: true
readinessstate:
enabled: true
这样,当 DB 挂了,readiness 返回 OUT_OF_SERVICE,K8s 就不再转发新请求,但老 Pod 还活着,可以继续处理已有连接或优雅关闭。而只有当 JVM 真正卡死(比如死锁),liveness 才触发重启。
这一改,线上事故率直接降了 60%。运维大哥终于不再在群里@我了。
第三关:配置管理的“天坑”——别再硬编码了!
早期为了赶 deadline,我把数据库地址、Redis 密码全写在 application-prod.yml 里,然后打成 jar 包。结果每次改配置都要重新构建镜像,CI/CD 流水线跑一次 8 分钟,测试疯狂吐槽:“你们开发改个密码要我等十分钟?”
更离谱的是,有一次我把测试环境的配置误打到生产镜像里,上线后服务连不上 DB……还好有 K8s 的 rollout 回滚,不然年终奖就没了。
痛定思痛,我决定上 ConfigMap + Secrets。
但问题来了:Spring Boot 怎么读 K8s 的 ConfigMap?
查了官方文档和 Nacos 的源码,发现其实 Spring Boot 本身不直接支持,但可以通过 Volume 挂载成文件,再用 spring.config.import 加载。
最终方案:
- 把配置拆成
config/application.yml - 创建 ConfigMap:
kubectl create configmap order-service-config --from-file=config/
- 在 Deployment 里挂载:
spec:
containers:
- name: app
volumeMounts:
- name: config-volume
mountPath: /app/config
volumes:
- name: config-volume
configMap:
name: order-service-config
- 启动命令加上:
command: ["java"]
args: ["-jar", "/app/app.jar", "--spring.config.import=/app/config/application.yml"]
这样,改配置再也不用 rebuild 镜像了!运维甚至可以直接 kubectl edit configmap 热更新(虽然不推荐,但紧急情况真香)。
从“调参侠”到“源码党”:为什么我要啃 Spring Boot 源码?
说实话,前面这些坑,网上搜一搜都能找到答案。但真正让我突破瓶颈的,是开始读 Spring Boot 和 Spring Cloud 的源码。
比如,为什么 @RefreshScope 能热更新 Bean?
翻了 spring-cloud-context 的源码,发现它其实是通过 ContextRefresher 触发 refresh(),重建带 @RefreshScope 注解的 Bean 实例。
再比如,K8s 的 Service 是怎么被 Spring Cloud Kubernetes 自动注入成 Feign Client 的?
看 spring-cloud-kubernetes-discovery 模块,原来它实现了 DiscoveryClient,定期从 K8s API 拉取 Endpoints。
一旦你看懂了底层机制,就不再是“调参侠”,而是能预判问题、设计更健壮架构的人。
这也让我在最近一次跳槽面试中,面对“如何实现配置中心灰度发布”的问题,能从 Spring Environment 抽象聊到 Apollo 的 Namespace 设计,最后拿下 offer。简历上那句“深入理解 Spring Boot 自动装配与扩展机制”,终于不是吹牛了。
经验总结:技术探索 ≠ 盲目追新
回过头看,我踩过的坑,本质上都是脱离业务场景、盲目套用技术导致的。比如:
- 为了用 K8s 而上 K8s,却没考虑团队运维能力;
- 盲目拆微服务,结果分布式事务搞到凌晨三点;
- 追求最新版 Spring Boot,结果兼容性问题拖慢迭代。
所以现在我给自己定了三条“技术探索守则”:
| 原则 | 说明 |
|---|---|
| 业务驱动 | 先问“解决什么问题”,再选技术。别为了炫技搞复杂架构。 |
| 渐进演进 | 能用 ConfigMap 就别急着上 Nacos;能单体就别硬拆微服务。 |
| 源码兜底 | 遇到诡异问题,第一时间看源码。文档会骗人,代码不会。 |
写在最后:代码人生,贵在“真刀真枪”
从建材销售到云原生程序员,我的“代码人生”没有光环,全是 bug、加班和凌晨三点的 Stack Overflow。但正是这些实战经验,让我明白:技术探索不是看几篇博客、跑几个 demo 就完事的,它必须扎根于真实的业务痛点、真实的线上环境、真实的 deadline 压力。
现在的我,依然会在 K8s 里迷路,依然会被产品经理的需求整懵,但至少——我不再害怕报错日志了。因为我知道,每一个 CrashLoopBackOff 背后,都藏着一个等待被理解的真相。
如果你也在转行路上,或者正被某个技术难题折磨,别慌。所有看似高深的技术,拆开来看,不过是一行行代码、一个个配置、一次次试错。
就像成都的火锅,再辣的锅底,只要敢下筷子,总能涮出味道来。
共勉。

评论 0