从单体到云原生:我们的后端架构演进之路

502守望者
2025-06-24 07:50
阅读 797

初衷与背景:为什么要做这件事?

初衷与背景:为什么要做这件事?

写这篇文章,源于我亲身经历的一次技术架构转型。那时候我们团队接手了一个已有三四年的项目——一个为中小型企业提供在线订单管理的服务平台。系统最初是用Spring Boot写的单体应用,部署在一台8核16G的ECS上,初期表现稳定、维护简单,开发效率也高。

可随着用户数量增长和功能模块扩展,这个“小而美”的单体架构开始变得沉重起来。频繁上线、数据库瓶颈、部署冲突、微服务拆分需求……这些问题像滚雪球一样扑面而来。

作为一个技术负责人,我深刻体会到:技术架构不是一成不变的,它必须服务于业务发展。于是我们开启了一段从单体架构走向微服务 + 云原生的艰难探索之旅。


遇到的问题:单体架构的天花板在哪?

遇到的问题:单体架构的天花板在哪?

1. 发布风险大

每次上线,哪怕是前端改个按钮颜色,后端也可能要整体打包重新部署。有一次上线因为某个新接口导致老逻辑出错,直接影响了核心下单流程,用户投诉量陡增。

某天上线前我盯着Jenkins控制台输出手心都冒汗:“这要是炸了,老板得亲自拎着我去找运维背锅。”

2. 性能瓶颈突出

MySQL数据库成了瓶颈,读写压力集中在一张order表上。即使加了缓存,高并发场景下还是会出现慢查询甚至死锁。

3. 扩容不灵活

服务器CPU经常飙到90%以上,但只有一两块功能模块(如报表分析)吃资源,其他模块空转。没法做到按需水平扩容。

4. 开发效率下降

随着代码量增加,Spring Boot项目的编译、启动时间越来越长,本地调试成本高。不同功能模块之间耦合严重,修改一处可能牵动全身。

5. 难以对接新能力

公司想接入消息队列做异步处理、引入Kafka实现数据同步,但在单体架构中嵌入这些组件非常麻烦,而且改动点分散。


我们的选择:如何一步步转向云原生?

我们的选择:如何一步步转向云原生?

服务器部署方案-2

我们的目标很明确:

  • 提升系统的可维护性和可伸缩性
  • 实现按需部署和弹性伸缩
  • 支持快速迭代和自动化运维
  • 降低上线风险,提升线上稳定性

于是我们规划了一个为期半年的架构迁移计划,大致可以分为以下几个阶段:


第一阶段:拆分业务边界,向微服务迈进

我们首先对原有系统进行了服务化改造。将原本单一的Spring Boot项目按照业务边界拆分成如下几个独立服务:

  • 用户服务(user-service)
  • 订单服务(order-service)
  • 商品服务(product-service)
  • 报表服务(reporting-service)
  • 系统中心服务(system-service)

每个服务基于Spring Cloud搭建,使用Feign进行通信,Redis做共享缓存,Nacos作为配置中心和服务注册中心。

# bootstrap.yml 示例
spring:
  application:
    name: order-service
  cloud:
    nacos:
      config:
        server-addr: nacos-server:8848
        extension-configs:
          - data-id: application-common.yaml
            group: DEFAULT_GROUP
            refresh: true

API接口文档-1

在这个过程中,我们遇到了两个大问题:

数据库拆分难题

我们原本使用的是单库多表结构,现在要拆成多个服务各自拥有一套数据库。关键挑战是如何处理跨服务的数据一致性问题。

解决方式是采用最终一致性设计:

  • 使用RabbitMQ进行跨服务事件广播
  • 订单创建成功后,通知商品服务更新库存快照
  • 各服务监听事件完成本地事务更新

这种方式虽然牺牲了一部分强一致性,但带来了极高的可用性,也降低了系统间的耦合度。


第二阶段:容器化 + 编排,走向云原生第一步

所有微服务完成后,下一步是把它们运行在容器环境中。我们选择了Docker + Kubernetes(K8s)方案,配合阿里云ACK(阿里云Kubernetes服务)进行部署。

我们将每个微服务打成Docker镜像,上传至阿里云ACR仓库,并编写相应的Deployment和Service配置文件。

# 示例:order-service 的 deployment 文件
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
      - name: order-service
        image: registry.cn-hangzhou.aliyuncs.com/my-org/order-service:latest
        ports:
        - containerPort: 8080
        envFrom:
        - configMapRef:
            name: order-service-config

这里我们也踩了不少坑:

环境差异导致的配置问题

本地开发环境和K8s集群环境存在差异,特别是数据库连接、Redis地址等配置容易搞错。

解决方案:

  • 所有环境相关的配置通过ConfigMap注入容器
  • 使用Helm统一管理发布模板,避免手动编辑YAML带来的错误

容器健康检查设置不当

一开始我们没给服务设置就绪探针(readinessProbe),导致K8s认为服务已就绪,但实际上应用还没完全启动,请求进来会返回异常。

修复方法:

readinessProbe:
  httpGet:
    path: /actuator/health
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5

加上之后,Pod只有在应用真正Ready后才被加入Endpoints列表,有效减少了启动过程中的5xx错误。


第三阶段:引入DevOps工具链,提升交付效率

我们逐步建立起CI/CD流水线,用Jenkins + GitLab CI做构建和部署,打通整个研发流程。

  • 开发提交代码 → 自动触发CI任务构建镜像
  • 构建成功后自动部署至测试环境
  • 测试通过后由运维审批上线至生产环境

这样做的好处是上线流程规范化、版本可控,也能快速回滚。

我们也集成了一些监控工具,比如Prometheus+Grafana做性能指标收集,ELK做日志分析,Sentry做错误追踪。


第四阶段:引入Service Mesh(可选进阶)

到了后期,我们尝试用Istio来管理服务之间的通信,实现智能路由、熔断降级等功能。

虽然效果不错,但也带来额外复杂度。建议中小型项目不要过早引入,先做好基本的服务治理即可。


踩过的坑和经验总结

踩过的坑和经验总结

坑1:服务间调用链太深,埋点和追踪难搞

刚开始没有统一接入分布式追踪,定位问题时只能靠日志拼凑请求路径,效率很低。

补救方法:

  • 引入SkyWalking做分布式链路追踪
  • 统一上下文传递traceId,方便排查

坑2:数据库连接池配置不合理,压测时出现连接等待

我们之前每台服务的数据库连接池都是默认最大10个连接,后来发现并发上来后出现大量等待。

优化策略:

  • 使用HikariCP替代原来的Tomcat JDBC Pool
  • 根据QPS估算合理连接池大小
  • 设置waitTimeout,避免长时间阻塞
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 3000

坑3:服务启动顺序依赖混乱,导致初始化失败

某些服务需要先等注册中心启动、数据库准备就绪,否则报错。

解决办法:

  • 编写initContainers检测依赖状态
  • 或者在Spring Boot中引入HealthCheck再启动主流程

最终效果和收益

经过这几个月的折腾,我们终于把架构迁移到了一个具备一定弹性和可观测性的云原生体系中。具体收益如下:

方面 单体时代 微服务云原生
发布频率 每周一次,紧张上线 每日多次,灰度发布
故障隔离 一个挂全站瘫痪 服务级别故障不影响全局
弹性扩容 手动升级配置,耗时长 自动扩缩容响应流量变化
性能监控 日志为主 实时可视化大盘
新功能开发 代码耦合严重,易冲突 职责清晰,独立迭代

上线后一个月内,我们经历了双十一活动的大促考验,高峰期QPS达到8k,系统整体平稳度过,几乎没有人为介入。


给读者的一些忠告

如果你也在考虑从单体迈向微服务或云原生,以下几点是我血泪总结的经验,希望能帮到你:

1. 别一开始就追求完美架构

很多同学看到别人用了K8s、Istio、Envoy就觉得我们也必须全部用上。其实不然。架构应服务于当前业务规模和团队能力

你可以先拆服务、做容器化,后面再引入更复杂的工具。一步到位反而容易失控。

2. 数据库拆分要慎重

微服务强调数据自治,但这意味着你需要面对更多的数据一致性挑战。不要为了“看起来整洁”而强行拆库。先做垂直拆分、再按业务拆表,最后才是真正的跨库拆分。

3. 要有一定的自动化能力

手工运维不适合微服务。哪怕你现在人少,也要尽早建立CI/CD流程、引入基础的监控体系。

4. 不要忽视文档和技术沟通

服务越多,越容易陷入“谁写的谁知道”的困境。定期开架构评审会议,保持文档更新,是团队健康发展的关键。


写在最后

技术架构从来不是一个静态的过程,而是随着业务成长不断演化的有机体。从最初的单体架构,到如今我们这套相对成熟的云原生体系,中间踩了很多坑、走了不少弯路,但也积累了许多宝贵经验。

回头看,那些深夜加班改配置、翻日志的日子依然记忆犹新。但正是这些真实项目中的历练,让我深刻理解了什么是“工程上的平衡之道”。

如果你也在做类似的尝试,希望这篇文章能让你少走一点弯路,多一份信心。毕竟,在通往更好架构的路上,我们都是同行人。


(完)

评论 0

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