从单体到云原生:一个深圳后端仔的架构血泪史
上周五晚上十点半,我还在公司对着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的话:“架构演进不是技术炫技,而是为业务护航。”
如果你和我一样,在一家中等规模公司,手握一个日渐臃肿的单体应用,我的建议是:
- 先做垂直拆分:哪怕只是把DB拆开,也能缓解不少压力
- 容器化优先:比直接上K8s成本低得多,收益却很明显
- 监控先行:没有Metrics/Logs/Traces,云原生就是空中楼阁
- 小步快跑:别指望一次重构成功,用Strangler Fig模式逐步替换
对了,GitHub上有不少优秀项目可以参考:
- microservices-demo:经典电商微服务示例
- k8s-demo:官方K8s示例集合
- awesome-cloud-native:云原生工具大全
写在最后:跳槽 or 留下?
回到开头的问题:我到底要不要跳槽?
说实话,这段架构演进的经历让我成长飞快。从只会写接口,到现在能设计高可用系统,甚至能和运维聊K8s调度策略——这种成就感,是纯业务开发给不了的。
但我也清楚,不是每家公司都有资源让你折腾云原生。有些厂还在用Dubbo 2.5 + ZooKeeper,有些连CI都没有。所以,如果你正考虑跳槽,不妨问问自己:你想要的是稳定安逸,还是技术成长?
对我而言,可能还是会留下。毕竟亲手把一个“遗产系统”改造成云原生架构,这种经历简历上可不好写——但面试时,绝对能让面试官眼睛一亮。
共勉吧,打工人。下次技术分享会,我在前排给你占座。

评论 0