从单体到云原生:一个后端工程师的成长之旅

写码不秃头
2025-06-13 06:49
阅读 480

引言

引言

作为一名有五年工作经验的后端工程师,我经历过不少技术演进的过程。记得五年前刚加入第一家公司时,项目还是典型的单体架构,所有代码都在一个工程里,部署在一台物理服务器上。那时的我们,对“服务拆分”、“微服务”这些词都停留在理论层面。直到公司业务增长、用户量激增,系统的瓶颈才一步步显现。

这篇文章想通过我参与的一个真实项目,分享一下我这几年亲历的系统从单体架构 → 微服务架构 → 云原生架构的整体演进过程,以及我在其中踩过的坑和收获的经验。希望对正在面临或准备转型的你有所帮助。


背景介绍:老项目的烦恼

背景介绍:老项目的烦恼

项目是做电商平台的后台系统,最初是一个 Spring Boot 单体应用,包含了商品管理、订单处理、支付结算、用户中心等模块。一开始只有十几万用户,性能问题并不明显。但随着营销活动频繁上线,用户暴涨到百万级别,系统逐渐暴露出以下问题:

  • 部署困难:一次发布需要重启整个应用,导致所有模块停摆;
  • 性能瓶颈:某个模块(比如商品详情)并发高,拖累其他模块;
  • 扩展性差:无法针对某个功能快速扩容;
  • 维护复杂:多人协作开发冲突频发,代码臃肿难读。

当时我们团队尝试过纵向拆分,将商品和订单做成两个 WAR 包部署在同一台 Tomcat 上,但本质上仍属于“伪微服务”,没有解决核心问题。

那时候我就意识到,真正的架构演进不是靠口号喊出来的,而是被需求倒逼出来的。


架构演进第一步:向微服务迈出试探的脚步

架构演进第一步:向微服务迈出试探的脚步

技术选型与目标

我们决定使用 Spring Cloud Alibaba + Nacos + Feign + Sentinel + Gateway 来搭建一套微服务架构。初期目标:

  • 拆分商品、订单、用户、支付为独立服务;
  • 支持服务注册发现与负载均衡;
  • 接入服务熔断机制提升容错能力;
  • 使用网关统一处理鉴权、路由逻辑。

实施细节

1. 服务拆分

我们按照业务领域划分了四个基础服务,并各自作为一个 Spring Boot 项目:

  • product-service
  • order-service
  • user-service
  • payment-service

每个服务之间通过 OpenFeign 进行远程调用,Nacos 做服务注册中心。

// 示例:order-service 调用 product-service 获取商品信息
@FeignClient(name = "product-service")
public interface ProductClient {
    @GetMapping("/products/{id}")
    Product getProductById(@PathVariable("id") Long id);
}

2. 网关集成

使用 Spring Cloud Gateway 搭建统一网关,负责请求转发和权限控制:

spring:
  cloud:
    gateway:
      routes:
        - id: product
          uri: lb://product-service
          predicates:
            - Path=/api/product/**
          filters:
            - StripPrefix=1

3. 服务治理初探

引入 Sentinel 对接口进行限流降级,防止雪崩效应。例如,我们在商品查询接口上加了 QPS 控制:

@GetMapping("/products/{id}")
@SentinelResource(value = "getProduct", blockHandler = "handleBlock")
public Product getProduct(@PathVariable Long id) {
    return productService.getProduct(id);
}

public Product handleBlock(Long id, BlockException ex) {
    return new Product().setName("当前访问人数过多,请稍后再试");
}

踩的坑

  • Feign 调用超时:默认超时时间太短,需要手动配置 Ribbon 的 timeout 参数;
  • Nacos 启动慢导致服务注册失败:调整启动顺序或者引入健康检查;
  • 服务依赖混乱:多个服务之间存在循环调用,后来通过事件驱动方式解耦(如 RabbitMQ);
  • 事务一致性问题:跨服务更新状态时容易出错,后续引入了分布式事务框架 Seata 来处理关键路径。

第二阶段:拥抱容器化和 DevOps 自动化

随着微服务数量增加,运维压力陡然上升。每次部署都要登录到服务器,手动执行脚本,效率低还容易出错。于是我们开始引入 Docker + Kubernetes + Jenkins 的组合,开启容器化之路。

Docker 化改造

为每个服务编写 Dockerfile,构建镜像并推送到私有仓库:

FROM openjdk:8-jdk-alpine
COPY *.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

Jenkins Pipeline 实现自动打包推送:

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package'
            }
        }
        stage('Docker Build and Push') {
            steps {
                sh 'docker build -t harbor.example.com/project/order-service:latest .'
                sh 'docker push harbor.example.com/project/order-service:latest'
            }
        }
    }
}

Kubernetes 编排部署

Kubernetes 的 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: harbor.example.com/project/order-service:latest
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080

同时我们将 MySQL、Redis、RabbitMQ 等中间件也容器化部署,并使用 Helm 进行版本管理。

成果与挑战

  • 部署效率大幅提升:从小时级缩短到分钟级;
  • 环境一致性更好:本地、测试、生产环境一致性强;
  • 资源利用率更高:可以动态调度 Pod,避免资源浪费;

但也有一些新问题:

  • 日志收集变得复杂:引入 ELK Stack 统一收集;
  • 服务间通信延迟变高:优化网络策略和服务粒度;
  • K8s 学习曲线陡峭:安排专项培训,逐步掌握核心概念。

第三阶段:走向真正的云原生

当我们的服务全部运行在 Kubernetes 上之后,下一步自然就是深入利用云原生的优势:弹性伸缩、自动恢复、监控告警、服务网格等。

引入 Prometheus + Grafana 监控体系

Prometheus 定期采集各服务的 /actuator/metrics 数据,Grafana 展示各种指标:

  • JVM 内存 & GC 情况
  • HTTP 请求成功率/延时
  • DB 连接数/慢 SQL 数量
  • Redis 缓存命中率等

这为我们排查线上问题提供了极大帮助。

弹性伸缩实践

通过 HPA(HorizontalPodAutoscaler)设置根据 CPU 使用率自动扩缩容:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60

这样在促销高峰期,系统能自动扩容应对流量洪峰,节省成本的同时保障用户体验。

引入 Service Mesh 提升可观测性

我们最终采用了 Istio,将服务治理从代码中抽象出来,实现了:

  • 零信任安全模型
  • 分布式追踪(Jaeger)
  • 流量管理(金丝雀发布、AB 测试)
  • 更细粒度的熔断和重试规则

比如我们可以对订单服务设置如下虚拟服务:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: order-vs
spec:
  hosts:
    - order-service
  http:
    - route:
        - destination:
            host: order-service
            subset: stable
      weight: 90
    - route:
        - destination:
            host: order-service
            subset: canary
      weight: 10

这样就能实现平滑的灰度上线流程。


效果总结与经验沉淀

经过三年多的努力,这个平台已经稳定运行在公有云上,支撑着每天千万级 PV 和上亿订单量。回顾这次旅程,我想分享几点心得体会:

收获与成果

  • 稳定性大幅提高:通过 Kubernetes 和 Istio 实现自动恢复;
  • 研发效率提升:CI/CD 全自动化部署;
  • 成本显著降低:按需扩缩容 + 云厂商弹性计算;
  • 运维更轻松:日志、监控、告警一站式管理;
  • 可拓展性强:新业务可快速接入已有基础设施。

我的几个建议

  1. 不要为了拆分而拆分:微服务不是银弹,前期评估好是否真的需要拆;
  2. 先做好基础设施建设:日志、监控、配置中心一定要同步跟上;
  3. 选择适合团队的技术栈:不要盲目追新,适合自己最重要;
  4. 注重文档和交接机制:否则你会在某一天被自己写的服务困扰;
  5. 多动手少空谈:很多问题是只有真正落地之后才会暴露出来的。

结语

从最初的单体架构一路走到今天的云原生架构,这条路看似光鲜亮丽,其实每一步都走得不容易。每一次技术升级的背后,都是无数次踩坑、重构、争论和学习。

作为后端工程师,我们要做的不仅是写出漂亮的代码,更是要站在更高的角度去思考如何让系统更健壮、更高效、更容易维护。架构的演变,其实是对我们综合能力的考验。

如果你现在正处在一个小团队、小项目中,也不要觉得微服务离你很遥远。不妨从一个小模块做起,试试用 Docker 封装一下你的服务,试着用 Grafana 监控一下你的接口性能。

技术的成长,从来都不是一蹴而就的,它藏在每一个真实的项目里,也在每一个深夜加班调试的 bug 中。

愿你在自己的技术道路上,越走越远。


文章作者:张远
GitHub:zhangyuan
微信公众号:后端修炼之道

评论 0

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