后端架构演进:从单体到云原生,我的实战之路
作为一名后端工程师,我在过去几年亲历了团队从一个简单的 PHP 单体应用一步步演进为微服务、再到今天全面拥抱云原生架构的全过程。这中间有迷茫、有踩坑、也有收获和成长。我想通过这篇文章,结合我们的项目实践,分享一下这段架构演进的真实经历,希望对正在面临类似技术选型的同学有所帮助。
一、背景:为什么我们要“重构”

我们是一个做电商导购平台的技术团队。最开始的业务逻辑并不复杂,用户浏览商品推荐,点击跳转购买,根据成交返佣。最初的代码仓库是用 Laravel 搭建的单体架构,所有功能模块都在一个项目里,部署在一台物理服务器上。
最初的状态其实挺香的:
- 开发快,增删改查一把梭
- 部署简单,上线只需要跑个
php think migrate就完了 - 运维成本低,没那么多花里胡哨的东西
但随着业务发展,问题逐渐浮现出来:
- 代码臃肿不堪,controller 层动辄上千行,各种耦合调用让人抓狂;
- 性能瓶颈明显,并发一上来,数据库直接打满 CPU;
- 上线风险高,一个小改动可能导致整个系统瘫痪;
- 运维效率低下,出问题时定位困难,日志杂乱无章。
这些痛点倒逼我们必须进行架构上的调整。
二、第一次尝试:分层架构 + 数据库拆分

我们做的第一个改进是将原来的单体系统进行逻辑分层和数据库拆分。
分层思路:
我们将原本混在一起的 controller、service、model 层进一步细化成:
app/
├── Controller
├── Service
├── Repository
├── DTO
├── Model
└── Exceptions
这样做的好处是让每个层级职责更清晰。例如,Controller 只处理请求和参数校验,Service 处理业务逻辑,Repository 负责与 DB 交互。虽然还是一个服务,但结构变得更可维护了。
数据库层面:
原来的 MySQL 表越来越多,锁表频繁。于是我们做了垂直拆库:
- 用户相关放到 user_db;
- 订单相关放到 order_db;
- 推荐内容放在 content_db。
同时引入了一个基础配置中心(我们当时基于 ZooKeeper 实现了一个轻量级的 Config Center),解决不同库之间的数据访问配置问题。
这个阶段的优化让我们的发布频率和稳定性有了明显提升,但依然受限于单机容量和故障隔离能力。
三、进入微服务时代:Spring Boot + Dubbo
2019 年左右,整个行业都在谈论微服务。我们也决定试水一把。这次我们选用了 Java 技术栈(Spring Boot + Dubbo)来构建新的服务。
架构设计要点:
- 领域划分清晰:按业务边界切分为用户服务、推荐服务、订单服务、统计服务等;
- 接口抽象规范:定义统一的服务接口,使用 Protobuf 做数据交换格式;
- 注册中心支持:Dubbo + Zookeeper 组合实现服务发现;
- 异步解耦:Kafka 用于关键链路异步处理,比如下单后触发佣金结算;
这时候的架构图大致如下:
┌────────────┐
│ Gateway │
└────┬───────┘
│
┌───────────▼────────────┐
│ Dubbo Invoker │
└──────────┬────────────┘
┌───────▼──────┐
│ UserService │ ┌───────────┐
└──────┬───────┘ │ │
│ │ OrderSvc │
┌───────▼──────┐ │ │
│ RecommendSvc │ └───────────┘
└──────────────┘
举个真实案例:订单结算延迟导致整体卡顿
有一次促销活动期间,大量用户下单后需要计算佣金,因为结算逻辑是同步执行的,直接把整个网关拖垮。后来我们将其改为 Kafka 异步消费模式,并加了个补偿机制:
// 生产者
kafkaTemplate.send("order_settle_topic", JSON.toJSONString(orderDTO));
// 消费者
@KafkaListener(topics = "order_settle_topic")
public void processOrderSettle(String message) {
try {
OrderDTO dto = JSON.parseObject(message, OrderDTO.class);
settleCommission(dto);
} catch (Exception e) {
log.error("结算失败: {}", e.getMessage());
// 入重试队列或落盘重试
}
}
这个改造之后,系统的吞吐能力和稳定性得到了很大提升。
四、向云原生迈进:Kubernetes 和容器化
到了 2021 年,公司开始推动“云原生”战略。作为技术负责人,我带领团队逐步将原有的微服务架构迁移至 Kubernetes 上运行,并引入了一系列云原生工具链。
技术选型和落地路径:
| 项目 | 说明 |
|---|---|
| 容器编排 | Kubernetes |
| 配置管理 | Helm Charts + Consul |
| 日志收集 | ELK Stack |
| 监控告警 | Prometheus + Grafana |
| 发布策略 | ArgoCD 实现 GitOps |
| 网络代理 | Istio 微服务治理 |
一次典型的上线流程如下:
git commit -am "feat: new coupon module"
git push origin dev
# CI 触发 Jenkins Pipeline,打包 Docker Image
docker build -t registry.mydomain.com/app/coupon-svc:latest .
docker push registry.mydomain.com/app/coupon-svc:latest
# CD 自动更新 Helm Values.yaml 中镜像版本并升级 release
helm upgrade --install coupon-release ./coupon-chart --values values-prod.yaml
五、遇到的坑和经验教训
转型过程中当然不是一帆风顺,下面是一些我们在实际工作中踩过的坑:
1. K8s 的滚动更新时间过长
一开始我们的 Deployment 设置的是默认的 maxSurge=1, maxUnavailable=0,结果每次发布新版本,等待太久,影响用户体验。后来我们改为:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
配合 readinessProbe 做好健康检查,发布速度明显加快。
2. Istio 引入带来网络抖动
刚开始引入 Istio 后,偶尔出现服务间调用超时的情况。排查发现是因为 Sidecar Proxy 初始化时资源竞争。我们做了几个优化:
- 使用 istioctl kube-inject 注入方式;
- 对关键路径的服务设置更高的 CPU limit;
- 对于 QPS 较高的服务,采用 per-route timeout 控制。
3. Prometheus 查询性能慢,监控指标爆炸
随着服务变多,Prometheus 的查询变得很慢,我们后来做了几件事情缓解这个问题:
- 引入 Thanos 来做分布存储;
- 合理使用指标标签,避免组合爆炸;
- 加上 recording rule 提前聚合高频指标;
六、效果对比与收益总结
经过三年多的努力,我们可以看到明显的成效:
| 指标 | 改造前 | 改造后 |
|---|---|---|
| 部署频率 | 一周一次 | 每天多次 |
| 故障恢复时间 | 2小时以上 | 10分钟以内 |
| 系统并发能力 | 几百QPS | 数万QPS |
| 新人上手成本 | 两周起步 | 一天搞定 |
| 成本控制 | 固定服务器 | 按需弹性伸缩 |
更重要的是,我们现在可以做到灰度发布、全链路压测、服务治理等功能,这些都成为了后续持续交付的基础能力。
七、给同行们的一些建议
如果你正在考虑架构演进,或者已经在路上,以下是我这些年踩坑后的一些建议:
✅ 架构不是越先进越好,而是越合适越好
不要为了“炫技”而引入一堆新技术,一定要结合当前团队的技术积累、业务发展阶段来做选择。
✅ 技术债要早点还
别想着“先跑起来再说”,否则后面你会花十倍代价去修复它。特别是在服务拆分、接口定义上,越早规范化越好。
✅ 重视可观测性
在服务越来越复杂的前提下,必须有一整套完善的日志、监控和报警体系。否则出了问题就是黑盒操作。
✅ 推动 DevOps 文化
开发人员要了解部署细节,运维也要参与代码评审。自动化程度越高,迭代越从容。我们现在已经做到了提 PR 自动化单元测试 + 预发布验证 + 自动合并。
八、写在最后的话
回首这段架构演进之路,从单体到微服务再到云原生,每一步都不是轻松的选择,也不是一蹴而就的过程。但在每一个关键时刻的思考和决策,不仅锻炼了团队的技术深度,也提升了我们在面对复杂系统时的工程思维。
希望这篇文章能给大家一些启发,少走弯路。如果你也有类似的架构转型经历,欢迎留言交流!
🚀 技术没有终点,我们一直在路上。

评论 0