后端架构演进:从单体到云原生

掘金夜猫子
2025-06-20 13:22
阅读 484

从单体到云原生:一位后端工程师的架构演进实践

从单体到云原生:一位后端工程师的架构演进实践

记得刚入行那会儿,我们团队接手的是一个典型的传统单体应用。Java 写的后台系统,部署在 Tomcat 上,数据库就一张 MySQL。当时觉得这样写代码挺顺手的,功能开发也快,上线更新也不复杂。直到有一天,用户量突然暴涨,服务器扛不住了,整个系统卡得像蜗牛一样慢。

那一刻我才知道,原来“简单”和“稳定”之间,隔着一场灾难的距离。

初遇瓶颈:单体架构的困境

那个项目是一个面向中小企业的 SaaS 管理平台,原本运行在一个单体结构下。前端是 Vue,后端是 Spring Boot,接口统一暴露在 Controller 层里,业务逻辑、数据访问全都混在一起。数据库用的是 MySQL 单实例,Redis 做缓存。

最初服务一切正常,但随着用户数增长(大概到了10万+注册用户),问题开始暴露出来:

  • 部署耦合严重:一个小模块修改,整个应用都要重新部署
  • 资源浪费明显:有些服务 CPU 高,有些服务空闲
  • 稳定性差:一次异常可能让整个系统瘫痪
  • 扩容成本高:只能堆机器,不能有针对性地优化热点模块
  • 运维难度大:日志分散、依赖管理混乱、监控困难

我记得最清楚的一次事故是,在做促销活动的时候,支付模块被高频调用,导致线程池打满,而其他模块也被牵连,出现了大规模的超时。那次我们整整排查了一天一夜,最后得出结论:这种单体架构已经无法支撑未来的业务发展了。

架构重构:服务化 & 微服务化之路

我们决定重构系统,目标是:解耦、可扩展、易维护、高可用

第一步是拆分单体应用。我们采用的方式是基于领域驱动设计(DDD)的思想,把原来的功能模块划分为若干个独立的服务,比如:

  • 用户服务(User)
  • 订单服务(Order)
  • 支付服务(Payment)
  • 消息服务(Message)
  • 文件服务(File)

每个服务有自己的数据库(最终走向多库多表),并通过 REST API 或者 gRPC 相互通信。

初期我们使用 Spring Cloud 来搭建微服务基础设施,包括:

  • Eureka 做服务发现
  • Feign 实现服务间通信
  • Zuul 做网关转发
  • Config Server 管理配置文件
  • Sleuth + Zipkin 做链路追踪

虽然这些技术帮助我们实现了服务间的解耦,但也带来了新的挑战,比如:

  • 服务间通信不稳定
  • 分布式事务不好处理
  • 数据一致性难保障
  • 部署复杂度陡增

为了解决这些问题,我们引入了一些工具和机制:

  • 使用 Ribbon 做客户端负载均衡
  • 通过 Hystrix 做熔断降级
  • 把重要事务封装成事件驱动(Event Driven)
  • 引入 RocketMQ 做异步通知
  • 用 Saga 模式代替两阶段提交处理分布式事务

这里分享一段服务间调用的示例代码:

// OrderService 调用 PaymentService 示例
@FeignClient(name = "payment-service")
public interface PaymentClient {
    @PostMapping("/api/v1/payment/create")
    ResponseEntity<CreatePaymentResponse> createPayment(@RequestBody CreatePaymentRequest request);
}

然后我们在 OrderService 中直接注入这个 client:

数据流转过程-2

@RestController
@RequestMapping("/api/v1/order")
public class OrderController {

    @Autowired
    private PaymentClient paymentClient;

    public ResponseEntity<?> createOrder(OrderRequest request) {
        // 创建订单逻辑...

        // 调用支付服务创建支付
        CreatePaymentResponse response = paymentClient.createPayment(paymentRequest);

        // 继续后续处理...
    }
}

当然,这只是表面,真正的坑在于服务治理、容错、版本兼容等方面。

进阶:拥抱 Kubernetes 和云原生

随着服务越来越多,手动部署、扩缩容、监控越来越吃力。这时我们决定上云,并且全面转向云原生架构。

我们选择阿里云作为主平台,采用 Kubernetes 做容器编排。整个流程大致如下:

  1. 把服务打包成 Docker 镜像
  2. 推送镜像到私有仓库
  3. 编写 Helm Chart 定义服务部署方式
  4. 通过 CI/CD 流水线自动部署到 K8s 集群
  5. 配置 HPA 自动伸缩策略
  6. 引入 Prometheus + Grafana 监控体系
  7. ELK 日志集中管理

这是我们在 K8s 上部署一个服务的 YAML 示例片段:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
      - name: order-service
        image: registry.example.com/order-service:latest
        ports:
        - containerPort: 8080
        envFrom:
        - configMapRef:
            name: order-configmap
        - secretRef:
            name: order-secrets
---
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

数据库设计模型-1

我们还在集群中部署了 Istio 来管理服务网格,实现更细粒度的流量控制和服务治理。

这套体系搭建起来之后,我们的运维效率提升了很多,而且可以实现按需扩容,高峰期不再担心宕机,低峰期节省了不少成本。

踩过的那些坑与经验总结

说到踩坑,太多了,挑几个印象深的说说:

1. 数据库拆分引发的灾难

早期我们按照服务拆分数据库,每个服务都有自己的数据库。看起来没问题,但在实际操作中,很多查询跨服务,关联变得很麻烦。

后来我们采取了两种方案结合:

  • 查询服务单独抽离(CQRS)
  • 使用 ElasticSearch 建立统一视图索引

比如订单详情页需要展示用户信息、商品信息等,我们就建立了一个 OrderDetailView 的聚合模型,定时从各服务同步数据到 ES,这样查询效率大幅提升。

2. 网络延迟影响性能

刚开始我们没意识到,微服务之间的网络调用是有延迟的。比如一个服务要调三个其他服务,平均每次延迟 50ms,合计就是 150ms,用户体验就变差了。

于是我们做了几点优化:

  • 尽量本地缓存
  • 使用异步调用(如 Future / CompletableFuture)
  • 把一些强依赖改为事件订阅模式
  • 必要时合并服务接口(避免多次调用)

3. CI/CD 不稳定

最头疼的是 Jenkins 崩溃,或者构建失败找不到原因。我们后来迁移到 GitLab CI,并结合 ArgoCD 做声明式部署,稳定性提高不少。

项目成效与收益

经过近一年的技术演进,项目的整体表现有了显著提升:

指标 之前 之后
平均响应时间 300ms 120ms
服务故障率 2% < 0.1%
部署效率 手动,耗时30分钟 自动部署,< 5分钟
扩缩容响应 数小时 分钟级
开发协作效率 多人开发冲突频繁 各自负责服务,互不干扰

更重要的是,团队的信心增强了,新来的同学也能快速接入特定服务模块,而不用通读整个代码库。

给后端小伙伴们的建议

如果你也在经历类似的转型过程,这里是我的一些建议:

  • 架构不是银弹,适合自己最重要:别盲目追新技术栈,先从小处试起。
  • 做好监控和报警:没有可观测性的系统,就像盲人骑马。
  • 不要过度设计:能用简单手段解决的问题,就别整复杂玩意。
  • 持续集成必须跟上:自动化部署才能降低风险,否则改几行代码都心惊胆战。
  • 注重团队协同与文档沉淀:架构越复杂,文档越重要。
  • 拥抱变化,但保持冷静:新东西总有它的价值,但也意味着学习成本。

结语

回顾这几年的架构演进历程,从最初的懵懂小白,到参与并主导这一套系统的重构,我收获的不仅是技术上的提升,更是对工程思维、产品意识的理解。如今我们已经进入云原生时代,我相信未来还有更多可能性等待着我们去探索。

希望这篇文章能对你有所帮助,如果你正在或即将面临架构升级的困惑,请记住一句话:没有最好的架构,只有最适合当前阶段的方案。共勉!

评论 0

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