后端架构演进:从单体到云原生 —— 一次真实踩坑与重构的旅程

赵勇_云计算
2025-06-15 13:35
阅读 696

引言

引言

刚入行那会儿,我参与了一个内部系统开发项目。当时我们团队规模不大,产品需求也相对简单,于是采用了最熟悉的“单体架构”搭了个Java Spring Boot应用,配上MySQL做数据持久化。一切看起来都很顺利,直到系统上线后开始出现各种问题。

随着业务的增长,这个最初跑得还挺溜的服务逐渐暴露出一堆“中年危机”。比如响应变慢、部署复杂、维护困难,甚至在高峰期还经常挂掉。后来我们决定做一次彻底的技术重构,把整个后端从单体架构一步步拆解、改造成了现在的云原生架构。

这篇文章就来聊聊我们是怎么走过这段“阵痛期”,又从中踩出了哪些坑,以及最终获得的收益。


第一阶段:单体架构的甜蜜与苦涩

第一阶段:单体架构的甜蜜与苦涩

项目背景

项目是一个面向企业内部使用的OA(办公自动化)系统,包括审批流程、考勤打卡、文档管理等模块。一开始需求不复杂,团队规模小,大家一致决定采用Spring Boot搭建一个单一应用,所有代码都放在一个Maven工程里,数据库用的MySQL。

听起来没问题对吧?确实,在初期一切都运行良好。但好景不长……

遇到的问题

  1. 部署复杂
    每次修改一个小功能都要打包重启整个服务,稍有不慎就会引入新Bug,影响其他模块。

  2. 性能瓶颈
    所有接口都在同一个JVM里面跑,某个模块处理慢了,整个应用都会受到影响。尤其是一些报表类请求特别耗资源,直接拖垮整台机器。

  3. 扩展性差
    新增模块时代码耦合严重,改动牵一发动全身。比如加个权限控制逻辑,可能需要修改多个地方。

  4. 运维成本高
    线上环境一旦出问题,排查起来特别麻烦。日志多得像大海捞针,监控也没统一。

  5. 技术栈难以升级
    升级Spring Boot版本或者换个ORM框架都必须全局替换,风险极高。

这些痛点让我们意识到不能再这么搞下去了——我们必须重构架构,寻找更适合当前发展阶段的技术方案。


第二阶段:迈向微服务架构的第一步

技术选型与思路

我们决定先从小处着手,尝试将原来的单体应用拆分成几个独立的服务模块:

  • 用户中心(User)
  • 审批服务(Approval)
  • 考勤服务(Attendance)
  • 文档中心(Document)

每个服务之间通过HTTP API调用通信,使用Spring Cloud提供的Eureka做服务注册与发现,Feign做远程调用,Ribbon做负载均衡。

同时,我们也引入了Spring Cloud Config做配置管理,Zuul作为网关进行统一鉴权和路由。

实践中的挑战

  1. 服务划分不合理 最初我们只是机械地按照模块来拆分,没考虑清楚服务的边界在哪里。结果某些接口频繁跨服务调用,导致响应延迟飙升,反而比原来的单体还要慢。

    小插曲:有一次上线审批服务新版本,由于依赖文档服务接口未更新,直接导致审批列表加载失败,用户集体投诉……

  2. 分布式带来的复杂度 数据一致性变成了难题,两个服务之间的事务怎么保证?后来我们上了Seata做分布式事务管理,但这套方案在生产环境调试起来并不轻松,一度让我们怀疑人生。

  3. 监控和链路追踪缺失 微服务多了之后,日志分布在不同的服务器上,查问题的时候就像玩拼图一样痛苦。后来我们接入了SkyWalking做了链路追踪,效果还不错,但前期集成和配置也花了不小功夫。

初步成果

虽然过程中踩了不少坑,但还是收获了很多经验:

  • 服务粒度要合理,避免过度拆分
  • 接口设计要考虑幂等性和兼容性
  • 日志收集 + 链路追踪是微服务的基础设施
  • 分布式事务不是万能钥匙,慎用!

第三阶段:拥抱云原生,构建弹性基础设施

转折点:一次线上事故

某天晚上,我们的其中一个服务因内存泄露导致Pod不断重启,但由于Kubernetes默认的自动恢复机制设置得过于宽松,未能及时扩容,最终整个系统几乎陷入瘫痪。

这次事故让我们意识到,仅仅拆成微服务还不够,真正的稳定性和扩展性来自于基础设施的现代化

于是我们开始向云原生全面转型。

我们做了什么?

  1. 容器化 & Kubernetes 编排 使用Docker封装各个微服务,并部署到阿里云ACK集群上。每个服务都以Deployment+HPA方式运行,具备自动扩缩容能力。

  2. 服务网格初步探索 引入Istio管理服务间通信,做流量控制、熔断限流和灰度发布。虽然 Istio 的学习曲线陡峭,但它的确让我们的运维变得更加灵活可控。

  3. 统一配置中心 & Secret 管理 改用ConfigMap和Secret管理敏感信息,结合Vault做密钥存储,避免硬编码密码。

  4. CI/CD 流水线搭建 使用GitLab CI配合K8s Job构建持续集成流程,实现自动构建镜像并推送到私有仓库,再由ArgoCD做自动部署。

  5. 监控告警体系升级 Prometheus抓取各服务指标,Grafana展示大盘,AlertManager发送预警。再加上之前的SkyWalking链路追踪,排查问题效率提升了不止一个档次。

成果展示

  • 弹性伸缩:节假日高峰时服务自动扩容,平时自动收缩,节省了不少计算资源。
  • 故障隔离增强:某个服务宕机不会导致整体雪崩,熔断机制可以及时切断故障传播。
  • 上线效率提升:从提交代码到上线不到十分钟,极大提升了交付速度。
  • 运维更轻松:有了完善的监控和告警系统,再也不怕半夜被叫醒了 😅

第四阶段:优化与沉淀 —— 架构设计与细节打磨

到了这个阶段,我们开始关注一些底层架构的优化和长期维护问题。

数据库设计调整

起初我们为每个微服务配了一个独立的MySQL数据库,初衷是为了隔离。但实际中发现很多聚合查询或报表生成非常不方便。

后来我们做了如下调整:

  • 读写分离:针对读多写少的服务,增加只读副本,使用ShardingSphere做路由。
  • 数据聚合层建设:新增一个BI模块,定时同步关键数据到ClickHouse,用于复杂报表分析。
  • 数据库迁移工具:使用Liquibase管理Schema变更,确保每次发版都能安全升级结构。
  • 分表策略完善:用户行为日志类数据采用按月分表策略,避免大表扫描造成性能下降。

接口设计规范

为了减少服务间的协作成本,我们制定了一套内部API规范:

  • 统一错误码格式:定义全局的错误码规范,便于客户端统一处理
  • OpenAPI + Swagger:所有接口自动生成文档,并提供Mock测试
  • 版本控制机制:使用URL路径带版本号 /api/v1/user,支持平滑过渡旧接口
  • 异步回调机制:对于耗时操作,返回任务ID并支持回调通知,提升用户体验

生产环境运维经验

  • 金丝雀发布:优先将新版本发布给少量用户,确认无误后再全量推广
  • 日志标准化:统一采用JSON格式输出日志,并打上上下文Trace ID,方便ELK检索
  • 健康检查机制:K8s探针定期检查存活状态,异常节点快速剔除
  • 限流降级:使用Sentinel在网关层和业务层做熔断保护,防止突发流量压垮系统

总结与建议

回望这几年走过的架构演进之路,真的是一边踩坑一边成长的过程。总结下来有几点我想送给正在经历类似阶段的同学:

1. 架构没有银弹,适合自己的才是最好的

不要盲目追求“高大上”的技术名词,要根据团队能力和业务阶段选择合适的技术栈。有时候,简单的方案反而是最优解。

2. 提前设计好服务边界

服务拆分不是越细越好,一定要从业务模型出发,明确每个服务的职责范围。否则只会带来更多的通信成本和维护负担。

3. 基础设施先行

微服务落地的前提是有良好的监控、日志、配置、流水线等基础设施保障,不然你会被各种“看不见摸不着”的问题折磨疯。

4. 小步快跑,逐步演进

不要想着一蹴而就就把整个系统重构一遍。可以先从一个核心模块入手,验证可行性后逐步扩大范围。

5. 多做自动化,别让运维手工操作

自动化部署、自动化测试、自动化监控,越早建立这套机制,后面就越省心。


结语

现在再看当年那个小小的Spring Boot项目,已经变成了覆盖十几个微服务、部署在云平台上的成熟系统。这中间经历了无数次踩坑、修复、重试,也留下了许多深夜加班的回忆。

但我始终坚信一点:好的架构不是设计出来的,是不断迭代试错出来的。

希望这篇基于我真实项目经历的分享,能对你们有所启发。如果你也在经历架构演进的迷茫期,欢迎留言交流,我们一起踩坑、一起成长 💪

By the way,下次如果让我再选一次,我会一开始就用云原生起步,而不是从单体慢慢拆……真的太折腾了 😂

评论 0

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