后端架构演进:从单体到云原生,我的实战经历

MQ堵车了
2025-06-21 17:11
阅读 730

起因:系统“长大了”,老架构撑不住了

起因:系统“长大了”,老架构撑不住了

我在一家中型电商平台做后端开发已经三年多了。刚入职的时候,我们系统的整体结构还算是标准的“单体架构”——所有功能都部署在一个Spring Boot应用中,代码仓库也只有一个,前端是Vue,数据库用的是MySQL和Redis。

这套架构一开始跑得挺稳,用户量也不算大,日均请求大概几万次,服务响应速度也能满足需求。可随着业务快速发展,问题开始暴露出来:

  • 发布周期越来越长:动不动就要整站重启,哪怕改了一个接口,上线风险很大;
  • 性能瓶颈明显:高并发场景下某些模块(比如订单中心)频繁阻塞主线程;
  • 数据表耦合严重:一张表被多个模块频繁访问,SQL优化成本高;
  • 新人上手困难:整个项目有20多个模块,新人摸不清各个模块之间的关系;
  • 灾备能力差:没有独立的服务隔离,一个bug可能导致整个站点瘫痪;
  • 部署方式落后:还是传统的物理机+人工部署,环境一致性差、回滚麻烦。

当时我们团队也讨论过要不要重构,但考虑到开发资源有限、线上又要持续迭代,一直拖着没动手。直到某年“双十一”促销期间,高峰期出现了订单创建失败率高达30%、支付网关频繁超时的情况。那次事故之后,我们才真正下定决心进行架构升级。


演进路线:从Monolith到微服务,再到云原生

演进路线:从Monolith到微服务,再到云原生

我们的目标很明确:降低模块之间耦合、提升系统弹性和稳定性、提高部署效率。整个过程经历了三个阶段:

  1. 单体拆分:按业务边界拆出关键子系统
  2. 微服务架构:引入服务治理与基础平台
  3. 云原生探索:Kubernetes + Service Mesh初探

下面我会结合实际遇到的问题和解决方案,分享一下我们的演进过程。


第一阶段:单体拆分 —— 基于领域驱动设计的第一次解耦

第一阶段:单体拆分 —— 基于领域驱动设计的第一次解耦

拆分背景

我们先梳理清楚现有系统的核心模块:

模块名 职责
用户服务 登录注册、权限管理、用户信息维护
商品中心 商品展示、分类、库存查询
订单系统 创建订单、处理支付状态、售后流程
营销系统 活动配置、优惠券、秒杀逻辑
日志统计 行为记录、数据分析埋点

很明显,这些模块可以按照业务边界进行拆分。于是我们决定优先将订单系统独立出去。

技术方案选型

  • Spring Cloud Alibaba + Dubbo3:作为 RPC 框架和服务注册发现组件
  • Nacos:服务注册中心与配置中心
  • Sentinel:服务限流降级
  • Seata:分布式事务(虽然我们暂时还没强依赖)

实现思路

第一步是对订单系统进行代码抽离:

原有项目结构:
├── user
├── product
├── order
├── marketing
└── common

我们将order模块单独提取出来,创建新的Maven项目,并通过Dubbo暴露API接口给主应用调用。

主应用在调用订单服务时,不再直接执行本地代码:

// 调用订单服务的远程接口(伪代码)
@Reference
private OrderService orderService;

Order order = orderService.createOrder(orderDTO);

这样做的好处非常明显:

  • 解耦完成之后,订单模块可以独立开发、独立测试、独立部署
  • 主应用无需知道订单具体实现细节,只关心输入输出
  • 错误隔离性增强,订单模块挂掉不至于导致整个站点崩溃

不过在这个过程中也踩了不少坑:


踩坑经验分享:别小看一次拆分

踩坑经验分享:别小看一次拆分

1. 接口设计不合理引发性能问题

刚开始为了图省事,我们把原本的一个本地方法createOrder()直接包装成了远程调用。没想到由于调用链深、参数复杂,导致每次下单都要经历多次网络往返。

解决办法是:

  • 引入领域模型,简化接口入参
  • 提供批量操作接口,减少RPC次数
  • 加入缓存层,预热部分数据
  • 使用异步调用处理非核心流程(如日志埋点)

2. 缺乏服务治理,出现雪崩效应

某个高峰时段,订单服务因为数据库死锁导致响应缓慢,进而引发了连锁反应,上游服务全部堆积线程池,最终整个系统大面积不可用。

这次事件后我们:

  • 引入了Sentinel做熔断降级和限流控制
  • 所有关键外部调用都设置了最大超时时间
  • 增加了健康检查机制和自动隔离策略

3. 数据库拆分滞后,成为新瓶颈

虽然应用层面做了拆分,但由于订单表还在原来的数据库里,导致压力依然集中。后来我们对数据库进行了垂直拆分:

  • 将订单相关的表迁移至独立数据库实例
  • 建立跨库数据同步机制(基于Canal + Kafka)
  • 引入读写分离缓解压力

这一步其实比服务拆分还要复杂,毕竟涉及到大量历史数据的迁移和一致性保障工作。


第二阶段:全面进入微服务时代

经过一段时间的实践,我们逐渐建立了完整的微服务生态体系:

  • 所有核心业务模块都已拆分为独立服务
  • 使用 Nacos 统一管理服务注册与配置
  • Spring Cloud Gateway 作为统一入口
  • ELK 进行日志收集和分析
  • Prometheus + Grafana 做监控报警
  • Jenkins 自动化构建流水线

这个时候我们遇到了一个新的问题:服务数量变多以后,运维变得越来越复杂

我们开始频繁遇到这些问题:

  • 配置文件难以管理,不同环境切换容易出错
  • 服务发布效率低,依赖人工介入步骤多
  • 多副本部署难,负载均衡不均匀
  • 服务间通信混乱,缺乏统一规范
  • 故障排查耗时长,定位困难

这时候我们就意识到,是时候引入容器化与编排系统了。


第三阶段:拥抱云原生 —— Kubernetes 初探

转型原因

我们的服务器规模扩大到了几十台,而手动管理部署方式已经完全跟不上节奏。我们考虑引入Kubernetes来解决以下问题:

  • 自动扩缩容
  • 服务自愈
  • 多环境一致部署
  • 更高效的资源利用率
  • 集中式配置管理

技术栈升级

我们选择的技术组合如下:

  • Docker:容器化打包
  • Kubernetes(简称 K8s):服务编排与调度
  • Helm:包管理工具
  • Istio(后期引入):服务网格,用于更精细化的服务治理
  • Rancher:可视化集群管理界面

实施过程中的几个关键点

1. 容器镜像标准化

我们制定了严格的Docker镜像构建规范:

  • 使用 Alpine Linux 减小体积
  • 所有服务运行在非root用户环境下
  • 配置与代码解耦(通过ConfigMap注入)
  • 支持健康检查接口(/healthz)

例如一个典型的Spring Boot服务Dockerfile:

FROM openjdk:17-jdk-slim as builder
COPY . /app
WORKDIR /app
RUN ./mvnw clean package

FROM gcr.io/distroless/java-debian12:17-debug
COPY --from=builder /app/target/app.jar /app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]

2. YAML 文件规范化

使用 Helm Charts 对部署模板进行抽象:

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "fullname" . }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ include "name" . }}
  template:
    metadata:
      labels:
        app: {{ include "name" . }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          ports:
            - containerPort: 8080
          envFrom:
            - configMapRef:
                name: {{ include "configmap-name" . }}

这样一套Helm模板可以同时适用于测试、预发、生产等不同环境,只需替换values文件即可。

3. 引入 Istio 做精细化治理

虽然K8s解决了很多部署问题,但在服务治理方面仍然显得粗放。我们后来引入Istio实现了:

  • 流量路由(AB测试、蓝绿发布)
  • 自动重试、超时、限流
  • 全链路追踪(配合Jaeger)
  • TLS加密通信
  • 服务身份认证

比如一条简单的虚拟服务配置(VirtualService):

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: order-service
spec:
  hosts:
    - "order.api.example.com"
  gateways:
    - public-gateway
  http:
    - route:
        - destination:
            host: order-service
            port:
              number: 80

微服务架构示意图-1


成果与收获:技术带来了业务增长

架构改造完成后,我们看到了明显的提升:

指标 改造前 改造后
系统可用性 99.0% 99.95%
发布频率 每周1~2次 每天多次
故障恢复时间 数小时 分钟级
服务器资源利用率 40% 75%+
团队协作效率 中心化开发 分散自治

而且,有了良好的架构之后,我们也更容易尝试一些新玩法:

  • 在K8s上快速搭建灰度测试环境
  • 实验性地接入 Serverless 技术处理流量波动大的任务
  • 探索 AI 能力在推荐系统中的整合
  • 将部分报表服务迁移到大数据平台(Flink + Hive)

最让我印象深刻的一次是去年年会期间,突然爆发了一波秒杀流量,我们临时启用了HPA(Horizontal Pod Autoscaler),几分钟内将相关服务自动扩容了3倍,成功抗住压力,而这一切都不需要人工干预。


写给正在转型路上的你

如果你所在的团队也在考虑架构升级,我想分享几点自己的心得:

1. 架构演进一定是“渐进式”的,而不是“革命式”的

不要想着一次性推倒重建,那样很容易陷入“重新造轮子”的陷阱。正确的做法是:

  • 找到最关键的痛点模块先行拆分
  • 保留兼容层,逐步过渡
  • 每次重构都要伴随足够的监控和观察

2. 不要过度追求新技术,适合自己的才是最好的

我曾经也是一个技术尝鲜者,喜欢各种“新玩意”。但经历过几次翻车教训之后明白:

工具是为了解决问题而存在,而不是为了炫技而存在。

评估一项技术是否值得引入,要看它能不能帮你解决实实在在的问题,而不是仅仅因为“大家都在用”。

3. 真正的挑战在于组织能力和工程文化

技术只是表面,真正的难点往往在于:

  • 如何建立统一的服务规范
  • 如何避免重复造轮子
  • 如何推动研发流程变革
  • 如何建立有效的监控体系
  • 如何打造DevOps能力

这需要团队长期投入,不是靠一个人或一次重构就能搞定的。


结语:永远在路上的架构进化

如今再回头看,我觉得我们走过的每一步都很踏实。也许现在看来还有很多不完善的地方,但这正是架构演进的魅力所在——它不是一蹴而就的事,而是一场马拉松式的持续优化。

从最初的单体应用,到现在相对成熟的云原生体系,我们的目标从未改变:更好地服务业务,让技术成为发展的引擎而非负担。

如果你正在面临类似的转型阵痛,不妨试试从小处着手,从一个模块的拆分做起,然后不断积累经验和信心。技术这条路,走得远的都不是天才,而是那些愿意脚踏实地、持续改进的人。

共勉。


📌 附录:文中提到的部分关键技术栈简要对比参考

组件 替代方案 备注
Nacos Eureka/Zookeeper/Consul 注册中心,Nacos更适合国内微服务场景
Dubbo Spring Feign/gRPC 适合Java生态,性能较好
Istio Linkerd/Servo 功能强大但也较重,根据团队能力选择
K8s Docker Swarm 目前事实上的容器编排标准
Sentinel Hystrix/Resilience4j 支持更丰富的规则配置
Seata Saga模式/TCC框架 只在需要强一致性时引入

如果你想获取本文涉及项目的完整示例代码和配置,欢迎留言交流 😊

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝