后端架构演进:从单体到云原生,我这五年的成长之路
背景介绍:为什么我要谈这个话题?

我是小李,一名有着五年后端开发经验的程序员。记得刚入行那会儿,公司还用的是传统的单体架构,所有代码都在一个项目里,部署在一个 Tomcat 上。那时候的我们每天就是写业务逻辑、打补丁、修 Bug,日子过得也算安逸。直到后来,业务越做越大,用户越来越多,系统开始频繁宕机、响应变慢、上线出错……
那阵子,我经常半夜被监控告警叫醒,心里苦不堪言。也是从那时起,我才真正意识到:架构不是为了炫技,而是为了解决实际问题。
于是我们开始尝试拆分服务、引入微服务、拥抱云原生。一路走来,踩过不少坑,也积累了不少经验。今天我想结合自己的真实经历,和大家聊聊:
“从单体应用,到云原生架构,我们是怎么一步步演进过来的?”
一、最初的起点 —— 单体架构带来的挑战

项目背景
我参与的第一个中大型项目是一个电商平台。前端是 Vue,后端是 Spring Boot,数据库用 MySQL + Redis,部署在一台 8C16G 的服务器上。
当时整个项目都是一个 Maven Module,包含了用户、订单、商品、支付等多个模块。接口设计也很简单粗暴,一个 Controller 包含多个方法,Service 层直接调用 DAO 操作数据库。
遇到的问题
随着用户量上涨,我们很快就遇到了以下几个棘手的问题:
部署困难:
- 每次上线都要全量打包部署,时间长、风险大
- 小改一个小功能也要重启整个服务,影响其他模块
性能瓶颈:
- 所有请求都打到同一个 JVM,线程池不够用
- 数据库连接池资源争抢严重,导致响应延迟高
维护成本高:
- 代码臃肿复杂,新人上手困难
- 接口之间耦合度高,一处修改,处处受影响
稳定性差:
- 出现内存溢出、线程死锁等异常,整个服务就挂了
- 监控和日志分散,定位问题困难
有一次因为一个 SQL 写错了 left join,把整个订单查询页面拖垮了,连带着首页都加载不出来。客户投诉电话接了一上午,那次教训真的刻骨铭心。
二、第一次升级 —— 模块化改造 + 垂直拆分

架构设计思路
为了避免动不动就全量发布,我们决定先做一个基础改进:按业务垂直拆分模块,但不彻底独立成服务。
也就是将原来的大工程,按业务切分成多个 Maven Module:
project/
├── user-service/ # 用户模块
├── product-service/ # 商品模块
├── order-service/ # 订单模块
├── payment-service/ # 支付模块
└── common/ # 公共类和工具包
每个模块独立封装 Service 和 Repository,通过 Spring 的 @Import 或依赖注入的方式进行集成。这样虽然还是单体服务,但结构更清晰,便于后续进一步拆解。
效果与局限性
这次改造让我们实现了两个目标:
- 发布更安全了:每次只改动相关模块的代码,降低出错概率
- 代码更好维护了:各团队分工明确,各自负责自己模块的功能开发
但本质还是一个服务,所以当访问量继续上升时,性能瓶颈依然存在,尤其是在促销期间,服务动不动就卡顿甚至挂掉。
三、第二阶段:迈入微服务时代
架构升级方案
我们决定正式迈出关键一步——将各个业务模块拆分为独立的微服务。
技术选型如下:
- 注册中心:使用 Alibaba Nacos
- 远程通信:Spring Cloud Feign + Ribbon
- 配置中心:Nacos Config
- 网关层:Spring Gateway
- 日志收集:ELK(Elasticsearch + Logstash + Kibana)
- 链路追踪:Sleuth + Zipkin
- 数据库:按业务分别建库,部分做了读写分离
数据库设计优化
以前所有数据都放在一个库中,随着表数量增加,索引混乱,维护成本极高。我们在微服务阶段做了几点优化:
- 每个业务使用独立数据库,保证数据隔离
- 使用 MyCat 实现简单的水平分表(比如用户和订单)
- 对热点数据引入 Redis 缓存(如商品信息、库存)
网络模型调整
原来的网关用的是 Nginx 反向代理,现在换成 Spring Gateway 做统一入口路由。它支持动态配置更新,也能配合 Nacos 做负载均衡。
项目效果
拆完之后,明显感觉几个好处:
- 服务部署灵活了:哪个服务出问题,单独重启就行,不影响其他功能
- 性能提升了:不同服务可以部署在不同机器上,压力分散
- 团队协作效率提高了:每个团队专注自己的服务,不再互相干扰
但新的问题也随之而来。
四、微服务之痛:分布式带来的一系列挑战
主要遇到的问题
服务治理难:
- 服务注册与发现容易失联,需要手动处理下线重连
- Feign 接口调用超时、重试策略没处理好,偶尔会引发雪崩效应
事务一致性难以保证:
- 下单操作涉及到用户余额扣减、库存减少、生成订单等多个服务操作
- 最初我们用了本地事务+回调机制,结果失败率非常高,最后被迫引入了 TCC 分布式事务框架
部署和运维复杂:
- 每个服务都需要启动 jar 包、配环境变量、管理日志路径……
- 定位问题靠翻日志,效率低下,而且服务太多,记不清谁部署在哪台服务器上
性能波动不定:
- 在大促高峰期,服务实例扩容慢,不能自动伸缩,还得人工加机器
- 某些服务调用链太长,响应慢成了常态
有一年双十一,我们临时加了十几台服务器才扛住流量,事后复盘才发现很多服务其实是空跑状态,根本没必要那么多资源。
五、迈向云原生:容器化 + K8s 自动化编排
为什么要上 Kubernetes?
我们意识到必须让服务具备弹性扩缩容能力,才能应对未来的高并发场景。
于是我们开启了新一轮的技术迭代:服务容器化 + Kubernetes 自动编排部署。
关键技术点:
- 使用 Docker 将每个微服务打包成镜像
- 利用 Helm Chart 统一管理部署配置
- 部署到 K8s 集群,实现 Pod 自动调度
- 使用 Ingress 控制对外访问入口
- 配合 Prometheus + Grafana 监控系统指标
- ELK 改造为 EFK(加入 Fluentd)用于日志采集分析
示例:Dockerfile 配置片段
FROM openjdk:8-jdk-alpine
COPY *.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
示例:Kubernetes Deployment 配置
apiVersion: apps/v1
kind: Deployment
metadata:
name: product-service
spec:
replicas: 2
selector:
matchLabels:
app: product-service
template:
metadata:
labels:
app: product-service
spec:
containers:
- name: product-service
image: registry.example.com/product-service:latest
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: product-service-config

配置中心迁移
我们将配置文件全部迁移到 ConfigMap 中,并通过 Nacos 做运行时热更新,实现了无需重新部署即可变更配置。
六、实践中的坑和解决方法
坑一:服务注册不上,或者反复上下线
刚开始用 Nacos 的时候,有些服务总是注册不上去,或者心跳丢失导致被踢除。
解决方案:
- 加大心跳间隔,默认 5 秒,改为 10 秒
- 提升健康检查频率,防止误删实例
- 给每个节点加上探针检测 /health 接口,提高可用性判断准确率
坑二:Feign 调用不稳定,出现 Timeout
有时候某个服务响应时间突然飙升,导致整个调用链崩溃。
解决方案:
- 给 Feign 添加熔断器(Hystrix),设置 fallback 方法
- 使用 Resilience4j 增加限流和降级策略
- 设置合适的超时时间,不要一刀切默认值
坑三:日志收集不完整,查不到报错日志
EFK 搭建完成后,部分日志迟迟采集不到,或者格式混乱。
解决方案:
- 统一日志输出格式(采用 JSON 格式)
- Fluentd 增加字段解析插件
- 给每个服务指定不同的日志路径,避免冲突
坑四:Prometheus 监控指标不准
刚开始部署监控的时候,很多指标看起来都是 0,或者数值不准确。
解决方案:
- 给服务添加 Actuator 监控接口
- 检查 metrics 是否暴露正确端口和路径
- 适当调整 Scrape 配置的时间间隔
七、最终成果和收获
从最开始的单体架构,到现在的云原生部署体系,我们团队实现了几个重大转变:
系统更稳定了:
- 服务自我恢复能力强,即使个别 Pod 挂掉也不影响整体可用性
- 异常告警响应迅速,问题可快速定位
部署效率提升了:
- 新服务上线只需提交一次 Git,CI/CD 流水线自动构建部署
- 支持灰度发布、回滚等高级操作
资源利用率更高:
- HPA 自动扩缩容,节省了大量服务器资源
- 资源分配更合理,避免了浪费
团队协作更高效:
- 每个服务都有对应负责人,责任明确
- 新人更容易理解架构,快速上手开发
八、我的一些经验和建议
作为过来人,我觉得对正在做架构演进的朋友,有几个重要的建议:
1. 不要一开始就追求完美架构
很多人上来就想搞中台、搞服务网格、搞 DDD,其实大可不必。
架构是为了解决当前的问题。先从小处做起,循序渐进才是正道。
我们当年就是太急于求成,想着一次搞定所有问题,结果各种组件没搭好反而拖慢进度。
2. 多关注性能和稳定性细节
很多时候问题不是出在架构设计本身,而是在细节上。
比如:
- 线程池配置不合理导致线程阻塞
- 数据库索引缺失导致查询缓慢
- 日志级别没控制好刷爆磁盘
这些看似不起眼的小事,往往才是压垮系统的最后一根稻草。
3. 线上环境要有完善的监控和报警机制
我们吃过很大的亏,很多线上问题是用户反馈之后才知道的。
后来我们建立了完整的监控体系:
- 应用层面的接口调用量、耗时
- 数据库慢查询、CPU 负载
- Redis、RabbitMQ 连接数、队列积压情况
这些监控项帮助我们提前预警了很多潜在问题。
4. 文档一定要跟上
每次架构变动,别忘了及时更新文档!
否则下次再接手的人,面对一堆 Kubernetes 配置和服务定义,根本不知道怎么下手。
我们现在的做法是:
- 每个服务都有 README.md 说明用途和启动方式
- 使用 Confluence 维护架构图、调用关系、部署流程
- CI/CD 脚本也同步保存在 GitLab 中,方便查看历史变更
结语:架构的演化,是一条不断试错、持续打磨的路
这五年,从单体架构到如今的云原生体系,我们的系统越来越强壮,我也从中学到了很多宝贵的经验。
有时候回头看,那些深夜加班调试、排查问题的日子虽然辛苦,但也正是这些点滴积累,让我真正成长为一名成熟的后端工程师。
如果你也在架构演进的路上,希望这篇文章能带给你一点启发和帮助。记住:
不要害怕改变,也不要急着追求时髦。一切技术方案的核心,始终是要解决你当下遇到的实际问题。
与君共勉 🙏
如有兴趣交流更多实战经验,欢迎留言或私信探讨!

评论 0