后端架构演进:从单体到云原生的实战思考
引言:一次系统崩溃带来的反思

事情发生在三年前的一个平常工作日,我们负责的一套核心业务系统突然挂掉了。当时的情况非常紧急:用户投诉、客服电话被打爆、值班运维人员手忙脚乱地重启服务,但问题依旧频发。更糟的是,数据库连接池在高峰期直接被打满,后端服务像多米诺骨牌一样接连倒下。
那是一套传统的单体架构,代码库超过百万行,模块之间高度耦合,部署方式是典型的JAR包+Tomcat。每次上线都需要全量更新,测试环境和生产环境差异大,灰度发布基本靠“玄学”。这个项目在我加入团队的时候就存在了,一路走来,虽然不断进行微调,但架构上的根本性问题始终没有解决。
这次事故之后,我们开始着手重构整个后端架构,逐步从单体走向微服务、再到容器化,最终落地于云原生体系。这篇文章想和大家分享的就是这段真实经历中的所思所感。
一、单体架构下的痛点

1.1 初期的美好与后期的噩梦
最初采用单体架构是有其合理性的:
- 开发速度快,适合初期快速试错
- 部署简单,一台服务器跑一个JAR包就能上线
- 不用考虑网络通信、分布式事务等问题
但随着业务发展,几个关键问题浮出水面:
- 迭代效率低:一个小功能改动可能要牵连多个模块,提测流程动辄两天起步。
- 资源利用率差:所有接口共享同一个线程池,高并发接口拖垮低频接口。
- 稳定性差:一个模块内存泄漏或CPU占用飙升,会波及整个系统。
- 扩缩容困难:扩容只能整体复制,不能按需弹性伸缩。
有一次,我们一个导出报表的功能由于设计不当,占用了大量数据库连接和计算资源,导致下单功能也跟着受影响——这在微服务架构中本不该发生的问题。
1.2 数据库设计的隐痛
数据层面的问题也很突出:
- 所有表都放在同一张MySQL实例上,表关联严重,SQL语句复杂。
- 读写分离做得很表面,很多查询依然走主库。
- 缓存使用不规范,有的接口缓存过期策略混乱,导致缓存击穿。
- 没有做数据库分片,数据量上来以后查询效率明显下降。
这些因素叠加在一起,形成了一个典型的“技术债黑洞”——你越想改进它,就越发现处处是雷区;而你不改它,又天天被它折磨。
二、第一次拆分:引入微服务架构

2.1 技术选型与规划
我们决定将单体应用拆分成若干个服务。拆分的原则是按照业务边界来划分(这也是后来踩坑的地方之一)。选型方面:
- 使用Spring Cloud全家桶作为微服务框架
- 用Consul做服务注册与发现(后续换成Nacos)
- Feign做远程调用
- Zuul做网关(后面换成了Spring Cloud Gateway)
- Apollo做配置中心
当时还在本地搭建了一整套Kubernetes集群用于预研,但在初期并没有投入实际使用。
2.2 拆分实践中的经验教训
第一次拆分选择了订单、用户、商品三个核心模块,拆分过程其实比想象中复杂得多:
数据一致性问题突显
比如一个新建订单的接口,需要同时操作用户、库存、优惠券等多个服务的数据。一开始我们尝试使用分布式事务(如Seata),结果性能极其糟糕,TPS直接跌了一半不说,还出现了大量事务日志堆积。
最终采用了本地事务+消息队列补偿机制:
// 伪代码示例:订单服务处理订单创建
@Transactional
public void createOrder(OrderDTO dto) {
orderRepository.save(dto);
// 发送事件到消息队列
messageQueue.send("order.created", new OrderEvent(dto));
}
其他服务订阅该事件,并根据事件异步更新自身数据,通过重试机制保证最终一致性。这种方式虽然牺牲了强一致性,但大大提升了系统的可用性。
接口设计的挑战
微服务之间的调用不像本地方法调用那么随意,必须重视接口契约:
- 统一返回结构
- 明确异常定义
- 版本控制机制(如API路径带版本
/api/v1/user)
我们在早期忽视了接口变更的影响范围,导致一次升级引发下游多个服务异常,最终不得不用A/B部署+流量切换的方式临时修复。
三、容器化与编排的引入
3.1 痛点催生改变
微服务拆分完成后,我们迎来了新的挑战:
- 服务数量暴涨,手工部署和监控难度陡增
- 环境差异依然存在(开发、测试、生产)配置不一致
- 扩缩容依然不够灵活,尤其是突发流量场景
这些问题促使我们开始引入Docker和Kubernetes。
3.2 实践中的关键技术点
容器镜像标准化
我们制定了严格的Dockerfile编写规范:
FROM openjdk:8-jdk-alpine
COPY *.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
并建立了CI/CD流水线,使用Jenkins自动构建镜像、推送至Harbor仓库。
Kubernetes部署优化
起初我们只是简单把服务跑在K8s上,但随着使用深入,陆续做了以下几件事:
- 水平伸缩:基于HPA自动扩缩副本数,设置合适的资源Limit和Request
- 滚动更新:设置maxUnavailable、maxSurge参数避免服务中断
- 健康检查:为每个服务实现合理的Liveness和Readiness探针
比如针对一个敏感的服务,我们这样设置探针:
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 15
periodSeconds: 5
这样的探针逻辑帮助我们减少了因偶发抖动导致的误杀问题。
四、迈向云原生:拥抱基础设施现代化
4.1 架构再进化
当我们完成K8s迁移到阿里云ACK后,才真正体会到什么是“云原生”。
我们做了几个关键调整:
- 使用SLB代替Zuul做负载均衡
- 使用OSS替代本地文件存储
- 使用SLS统一日志收集(之前是ELK)
- 使用Arms做全链路监控
- 将MySQL迁移至PolarDB,Redis迁移至云数据库
在这个过程中,最深的感受就是:不要重复造轮子,云厂商提供的产品已经足够强大。
比如我们之前自己搭了一个Prometheus+AlertManager做监控告警,但迁移到云原生之后,直接对接SLS+Arms,省时省力不说,报警准确性、响应及时性都远超自建方案。
4.2 数据库与缓存的再次演化
为了应对更大的数据量,我们做了如下优化:
- 分库分表:使用ShardingSphere对订单表进行水平拆分
- 索引优化:通过慢查询日志分析,重建不合理索引
- 写分离:读取请求走只读实例,减轻主库压力
- 缓存穿透防护:布隆过滤器 + Redis空值缓存策略
特别值得一提的是,我们曾经遇到过一个热点Key问题:某个爆款商品详情页频繁刷新,导致Redis QPS飙到几十万。解决方案是:
- 对热Key做二级缓存:Nginx层做LocalCache
- 设置随机TTL:避免大量缓存同时失效
- 前置限流:通过Sentinel对特定接口做流量控制
五、开发体验提升:从工具链到协作模式
5.1 全链路压测与观测
我们实现了真正的全链路压测:
- 用JMeter模拟真实场景
- 使用Jaeger做链路追踪
- 压测数据注入线上链路(加特殊标识)
例如,在某次双十一压测中,我们发现了订单支付链路中的瓶颈环节:优惠券验证服务存在同步阻塞行为,最终将其改为异步批量校验,提升了整体吞吐量。
5.2 敏捷协作的转变
微服务带来了更细粒度的职责划分,但也增加了沟通成本。我们采取了两个措施:
- 接口文档自动化生成:使用Swagger + SpringDoc,配合GitBook生成完整文档
- 跨服务Mock测试:使用WireMock模拟外部依赖,方便本地联调
另外,我们开始推动DevOps理念,让开发同学更多参与部署与监控环节,打破了“研发-运维”的壁垒。
六、那些年踩过的坑与教训

6.1 微服务不是银弹
- 拆分初期我们过度追求“微”,反而造成服务间调用关系混乱
- 忘记服务治理,导致雪崩效应频发
- 没有建立良好的服务治理平台,排查问题耗时极长
6.2 容器与虚拟机的区别
- 最初把容器当虚拟机用,随便装各种组件,镜像臃肿
- 忽略Pod生命周期管理,出现InitContainer反复失败却不知晓
- 日志输出不规范,无法集中采集分析
6.3 云原生认知误区
- 认为上了K8s就是云原生,忽略了Operator、Service Mesh等进阶能力
- 盲目使用Serverless,结果运行时延迟不达标
- 过度依赖云平台功能,造成供应商绑定严重
七、成果与收获
如今我们的系统已经稳定运行两年多,经历过几次大促考验。回顾这段旅程,取得了以下成果:
| 方向 | 改进前 | 改进后 |
|---|---|---|
| 部署效率 | 每次上线需30分钟+ | CI/CD全流程约5分钟 |
| 服务可用性 | 平均每月1次故障 | 99.9% SLA达成 |
| 接口响应时间 | P99>500ms | P99<200ms |
| 运维复杂度 | 多人专职维护 | 半个人兼职即可 |
| 新业务上线周期 | 4~6周 | 1~2周 |
最重要的是,整个团队的技术视野拓宽了,大家不再局限于编码本身,而是开始思考如何构建一个高可用、可扩展、易运维的系统。
八、给读者的建议
如果你正在面对类似的架构转型或升级挑战,下面几点或许能帮你少走弯路:
- 先明确目标再做架构:不是为微服务而微服务,一切以解决现有问题为导向
- 循序渐进,小步快跑:可以从领域模型入手,逐步拆分,而不是一刀切
- 关注可观测性:日志、监控、链路跟踪尽早接入,后期补救代价巨大
- 做好容量评估与演练:压测不仅是测试接口性能,更是检验整个链路的能力
- 重视团队赋能:新技术落地的背后是人,培训、知识沉淀、文化适应缺一不可
最后想说一句:好的架构不是设计出来的,是在一次次试错、总结中生长出来的。
作者简介:
我在互联网行业从业9年,目前担任某电商平台后端架构负责人。经历过从单机到微服务、从物理机到云原生的完整技术演进,主导过多个亿级流量系统的改造与优化。平时喜欢写代码、研究开源项目,也热爱分享。欢迎交流技术问题或探讨架构思路。

评论 0