从单体到云原生:一个深圳后端仔的架构血泪史

需求文档失踪
2026-01-03 04:13
阅读 1528

上周五晚上十点半,我还在公司对着K8s的Pod日志干瞪眼。产品那边催着上线新功能,测试提了一堆P0级Bug,运维在群里@我说“你这服务又OOM了”。那一刻我真的想把电脑砸了——不是因为代码写得烂,而是因为我们那个五年老单体应用,已经快撑不住了。

我是谁?一个在深圳某腾讯系公司干了快四年的Java后端,最近正处在要不要跳槽的迷茫期。平时除了写CRUD,最大的爱好就是周末去南山或者福田参加各种技术分享会,听听大佬们吹吹云原生、Service Mesh这些高大上的东西。但现实很骨感:我们系统还是Spring Boot打天下,数据库一主两从,部署靠Jenkins脚本,监控全靠Sentry报警。每次分享会回来都热血沸腾,第二天上班看到祖传代码又瞬间冷静。

所以今天这篇文,不光是为了记录我自己踩过的坑,也是给和我一样在传统架构里挣扎、又想往云原生转型的兄弟们一点参考。顺便,说不定哪天面试官问起“你们系统怎么演进的”,我也能答得头头是道(笑)。


起点:那个让人又爱又恨的单体应用

说起来,我们最初那个系统其实挺优雅的。2019年用Spring Boot + MyBatis搭起来,MVC分层清晰,接口文档用Swagger自动生成,连前端都说“你们后端真规范”。那时候业务简单,日活就几万,数据库一张订单表撑全场,部署流程也就三步:mvn clean package → 上传jar包 → nohup java -jar xxx.jar &

但好景不长。随着业务膨胀,这个“全能型选手”开始力不从心。最夸张的时候,一个Git提交要跑40分钟CI,因为单元测试+集成测试加起来快两千个。更别提改个用户模块的逻辑,结果把支付模块搞挂了——因为所有逻辑都在同一个JVM里跑,一个线程池满了,全站卡死。

去年双11前夕,我们系统直接崩了半小时。原因?一个第三方短信接口超时,导致线程池耗尽,整个服务雪崩。当时运维大哥在群里怒吼:“你们能不能把核心链路拆出来?!” 我们团队面面相觑:拆?说得轻巧,这可是上百万行代码的巨无霸啊。


第一步:微服务化,先活下来再说

痛定思痛,领导拍板:必须拆!于是我们开始了“痛苦但必要”的微服务改造。初期没敢动大刀,只是按业务域粗粒度拆分:用户中心、订单中心、商品中心、支付中心……每个服务独立部署,通过Feign调用。

技术栈还是熟悉的Java全家桶:

  • Spring Cloud Alibaba(Nacos做注册中心,Sentinel限流)
  • MySQL分库分表(ShardingSphere)
  • Redis缓存
  • RocketMQ解耦

好处立竿见影

  • 某个服务挂了,不会拖垮全站
  • 团队可以并行开发,不用天天merge冲突
  • 部署粒度变小,上线更快

但坑也不少

  • 分布式事务搞到头秃(最后妥协用了Saga模式 + 补偿机制)
  • 链路追踪全靠手动埋点,排查问题像考古
  • 本地开发环境搭建复杂到新人入职一周都跑不起来

那段时间,GitHub上疯狂搜“Spring Cloud best practices”,收藏夹都快爆了。但很多教程都是理想化场景,比如“用Seata搞定分布式事务”——现实是Seata对业务侵入太强,我们老系统根本改不动。


中期:容器化,告别“在我机器上能跑”

微服务拆完,新问题来了:环境一致性。测试说“你这接口在我本地跑得好好的”,结果上预发就500。运维更是苦不堪言:每个服务依赖的JDK版本、Tomcat配置、甚至时区都不一样,部署脚本写了上千行Shell。

这时候,Docker成了救命稻草。我们开始把每个服务打包成镜像,用Docker Compose在本地模拟整套环境。终于,新人第一天就能跑通全流程——虽然要等20分钟拉镜像就是了。

关键工具链:

# 示例:一个典型的Java服务Dockerfile
FROM openjdk:11-jre-slim
COPY target/app.jar /app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]

配合Jenkins Pipeline,实现从代码提交到镜像构建的自动化:

pipeline {
    agent any
    stages {
        stage('Build') {
            steps { sh 'mvn clean package -DskipTests' }
        }
        stage('Docker Build & Push') {
            steps {
                sh 'docker build -t registry.company.com/order-service:${BUILD_ID} .'
                sh 'docker push registry.company.com/order-service:${BUILD_ID}'
            }
        }
    }
}

说实话,容器化之后,运维找我们吵架的次数少了80%。毕竟“环境问题”这个万能甩锅理由,终于被Docker干掉了。


终极目标:拥抱云原生

但故事还没完。微服务 + 容器只是起点,真正的挑战是如何让系统具备弹性、可观测性、自愈能力——也就是云原生的核心。

去年开始,公司推K8s落地。作为团队里“对K8s比较熟”的人(其实也就看了几遍《Kubernetes in Action》),我被推上了前线。过程堪称地狱模式:

  • 初期YAML写得像天书,Deployment、Service、Ingress、ConfigMap混在一起
  • Pod频繁重启,查了半天发现是livenessProbe配置太激进
  • 网络策略搞错,服务间调用直接超时

但熬过来之后,真的香!比如自动扩缩容:大促期间CPU超过70%,HPA自动拉起新实例;流量回落,又自动缩回去。再也不用提前一周求运维“帮我们多开几台机器”。

我们现在的云原生技术栈:

  • 编排:Kubernetes(EKS托管集群)
  • 服务网格:Istio(虽然只用了基础的流量管理)
  • 可观测性:Prometheus + Grafana + Loki(日志聚合)
  • CI/CD:Argo CD(GitOps模式)

特别提一下GitOps——把K8s配置也当代码管,提交到GitHub仓库,Argo CD自动同步。这意味着:部署 = git push。再也不用手动敲kubectl apply,运维安全感拉满。


技术选型对比:不是越新越好,而是适合才好

很多人问我:“为啥不用Go/Node.js重写?”“Service Mesh是不是必须上?”我的答案始终如一:看团队、看业务、看成本

下面是我整理的一些关键决策点对比:

维度 单体架构 微服务(VM部署) 云原生(K8s)
开发效率 ⭐⭐⭐⭐(初期快) ⭐⭐(环境搭建慢) ⭐⭐⭐(需学习曲线)
部署复杂度 ⭐(简单) ⭐⭐⭐(脚本维护难) ⭐⭐(YAML管理)
弹性伸缩 ❌(整机扩缩) ⭐(需额外工具) ⭐⭐⭐⭐(原生支持)
故障隔离 ❌(全局影响) ⭐⭐⭐(服务级) ⭐⭐⭐⭐(Pod级)
团队要求 初级即可 需懂网络/中间件 需懂K8s/云原生
适用阶段 MVP/小团队 中大型业务 高并发/全球化

举个真实例子:我们有个内部工具服务,日请求不到1000次,团队就两个人。硬上K8s?纯属自虐。现在它还在用单体+Docker跑着,稳得很。


给同行的建议:别为了云原生而云原生

写到这里,突然想起上周技术分享会上一位阿里P8的话:“架构演进不是技术炫技,而是为业务护航。”

如果你和我一样,在一家中等规模公司,手握一个日渐臃肿的单体应用,我的建议是:

  1. 先做垂直拆分:哪怕只是把DB拆开,也能缓解不少压力
  2. 容器化优先:比直接上K8s成本低得多,收益却很明显
  3. 监控先行:没有Metrics/Logs/Traces,云原生就是空中楼阁
  4. 小步快跑:别指望一次重构成功,用Strangler Fig模式逐步替换

对了,GitHub上有不少优秀项目可以参考:


写在最后:跳槽 or 留下?

回到开头的问题:我到底要不要跳槽?

说实话,这段架构演进的经历让我成长飞快。从只会写接口,到现在能设计高可用系统,甚至能和运维聊K8s调度策略——这种成就感,是纯业务开发给不了的。

但我也清楚,不是每家公司都有资源让你折腾云原生。有些厂还在用Dubbo 2.5 + ZooKeeper,有些连CI都没有。所以,如果你正考虑跳槽,不妨问问自己:你想要的是稳定安逸,还是技术成长?

对我而言,可能还是会留下。毕竟亲手把一个“遗产系统”改造成云原生架构,这种经历简历上可不好写——但面试时,绝对能让面试官眼睛一亮。

共勉吧,打工人。下次技术分享会,我在前排给你占座。

评论 0

最热最新
暂无评论
需求文档失踪Lv.1
0
影响力
0
文章
0
粉丝