从传统行业转码三年,我终于把“技术探索”这事整明白了

◆高强
2025-12-19 15:32
阅读 313

去年冬天的一个深夜,我在公司加班改一个 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 加载。

最终方案:

  1. 把配置拆成 config/application.yml
  2. 创建 ConfigMap:
kubectl create configmap order-service-config --from-file=config/
  1. 在 Deployment 里挂载:
spec:
  containers:
  - name: app
    volumeMounts:
    - name: config-volume
      mountPath: /app/config
  volumes:
  - name: config-volume
    configMap:
      name: order-service-config
  1. 启动命令加上:
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

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