微服务架构设计实战:我的单体拆分之路

模型接口玩家
2025-06-13 14:48
阅读 590

我是一个在互联网公司做后端开发的程序员,入职前三年一直维护着一个超大的 PHP 单体项目。随着业务量快速增长,部署、发版、调试都变得异常困难,系统经常出问题却难以定位。最头疼的一次是修改一处登录逻辑,直接导致整个站点无法访问,用户投诉如潮涌来。

那一次让我下定决心:我们必须把这套单体应用拆成微服务架构。今天我就想分享一下这次“从零开始”的经历,包括我们遇到的问题、踩过的坑,以及最后落地后的收获。

背景介绍:为什么我们要拆?

背景介绍:为什么我们要拆?

我们的核心平台是一个面向企业用户的 SaaS 系统,最初采用的是经典的 MVC 架构,前端 + 后端(PHP)+ MySQL 的组合。所有的功能模块都在一个代码库中,包括:

  • 用户认证
  • 订单管理
  • 商品信息
  • 库存控制
  • 通知中心
  • 运营报表

随着系统功能越来越多,团队也从最初的3个人扩展到10人以上,大家逐渐意识到以下几个痛点:

  1. 发布风险高:一个小改动也可能影响整个系统。
  2. 技术栈不灵活:想要用新语言写个模块?基本不可能。
  3. 性能瓶颈:某些接口慢得让人崩溃,但又不好独立扩容。
  4. 测试成本大:每次上线都要全回归一遍。
  5. 协作效率低:多个小组频繁冲突,Git Merge 时噩梦连连。

于是,我们决定尝试将这个庞大的系统拆分成多个微服务。

第一步:识别服务边界

系统架构设计图-1

第一步:识别服务边界

这是我认为最难也是最重要的部分。我们做了几个动作:

1. 梳理现有模块关系图谱

通过分析日志和数据库表结构,画出了各个模块之间的调用依赖。结果发现其实有些看似强相关的模块之间联系并不紧密,例如库存和订单虽然是上下游,但实际上只有少量同步交互,其余大多是读操作。

2. 按照业务域划分服务

最终我们将原系统划分为如下几个主要服务:

原模块 拆分后服务
用户注册/登录 auth-service(认证中心)
商品资料管理 product-service
库存相关逻辑 inventory-service
订单下单与状态变更 order-service
报表及统计 report-service

这里有个小插曲:我们在讨论是否要将商品详情和库存服务合并的时候争论了很久。后来发现两者虽然相关,但一个是强读场景,一个是写多读少,分开后便于各自独立优化。

3. 明确每个服务的核心职责

举个例子:auth-service 主要处理 JWT 颁发和验证;而用户基础资料保存放在 user-service 里更合适。一开始我们就搞混了这两个服务的界限,导致后面重构花了好些时间。

技术选型:我们用了什么?

技术选型:我们用了什么?

由于团队对 Java 和 Spring Boot 比较熟悉,同时希望引入一些现代框架提升开发效率,所以我们选择了如下技术栈:

  • 语言框架:Spring Boot + Kotlin
  • 通信协议:RESTful API + FeignClient
  • 配置管理:Spring Cloud Config + Apollo
  • 注册中心:Nacos
  • 网关:自研简化版 + OpenResty
  • 持久化:MySQL + Redis
  • 监控告警:Prometheus + Grafana + ELK
  • 部署方式:Docker + K8s

值得一提的是,我们没有采用非常复杂的方案,比如 gRPC 或者 Service Mesh。因为当时团队规模有限,不想引入太大复杂度。

接口设计实践:如何让服务之间更好地沟通?

接口设计实践:如何让服务之间更好地沟通?

接口设计是个特别容易被忽视但又极其关键的环节。我在第一个版本里犯了个错误——定义了一个“通用请求体”,所有服务都统一使用一种 JSON 格式传参。结果当其中一个服务需要上传文件时彻底炸锅,不得不停工返工。

最后我们吸取教训,制定了以下原则:

  • 尽可能使用标准 HTTP 方法语义,GET 查询、POST 创建等
  • 请求体按业务实际需要定制,不强行复用
  • 使用 OpenAPI 规范文档化每个接口,并生成 SDK 包供其他服务调用
  • 对外暴露的服务加一层 Gateway,避免客户端直连业务服务

下面是 order-service 中创建订单的一个接口示例:

@RestController
@RequestMapping("/orders")
public class OrderController {

    @PostMapping
    public ResponseEntity<OrderDTO> createOrder(@RequestBody CreateOrderRequest request) {
        // 内部校验参数并调用 service 层
        return ResponseEntity.ok(orderService.create(request));
    }
}

为了保证接口稳定性,我们为每个服务生成了对应的 Swagger 文档,并通过 Jenkins Pipeline 强制检查接口变动是否兼容。

踩过的坑和解决方法

1. 数据一致性怎么破?

单体架构里可以很方便地使用数据库事务来保证数据一致性,比如“扣库存减1”和“创建订单”放在一起提交。但在分布式环境下这么做就麻烦了。

我们尝试过使用 TCC 补偿事务模型,比如:

  1. 下单前先调用库存服务预占库存
  2. 创建订单成功后调用 confirm,失败则 cancel
  3. 加入重试机制确保执行到底

但这种模式太复杂,尤其要考虑幂等性、网络超时等问题。最后我们妥协了——允许最终一致性的存在,采用异步消息队列做对账补偿。

经验建议:除非有严格的金融级要求,否则优先考虑 Eventual Consistency 模型,避免过度设计。

2. 本地事务 vs 分布式事务

这个问题我们在支付服务上吃了不少亏。当时支付流水必须和订单状态变更同时完成,否则系统就乱套了。早期尝试使用 Seata 的 AT 模式,结果性能下降明显,而且锁竞争严重。

后来改成了基于 Kafka 的事件驱动架构:

  1. 订单服务发送 "订单支付成功" 事件
  2. 支付服务消费该事件,更新自身记录
  3. 失败后重试直到最终成功

虽然不能保证实时一致,但我们加入了一个后台扫描任务来兜底补漏。

3. 日志追踪怎么办?

拆了服务之后,排查问题变得非常痛苦。你根本不知道一个请求经过了多少个服务节点。我们一开始只能靠人工串联各个系统的日志,效率极低。

后来引入了 Sleuth + Zipkin 来实现分布式链路追踪:

spring:
  sleuth:
    sampler:
      probability: 1.0  # 采样率设为100%
  zipkin:
    base-url: http://zipkin-server:9411

有了链路 ID 之后,我们可以清晰看到一条请求从 gateway 到 order,再到 inventory 的完整路径,排查速度提升了好几个数量级。

服务治理的初步建设

API接口文档-2

除了拆分本身,服务治理也是不可忽略的一部分:

  • 服务注册发现:Nacos 提供了健康检查和自动剔除故障节点的能力
  • 客户端负载均衡:FeignClient 默认支持 Ribbon,能做轮询、权重选择
  • 断路熔断机制:集成 Hystrix,防止雪崩效应
  • 限流降级策略:在入口网关加上 Rate Limiting

比如我们设置了一个基础限流规则,在高峰期避免某个服务被压垮:

hystrix:
  threadpool:
    default:
      coreSize: 20
      maximumSize: 30
      keepAliveTimeMinutes: 1
      maxQueueSize: 100

实际收益

拆分完成后,我们看到了明显的好处:

  1. 部署更快了:每个服务独立构建部署,CI/CD 时间减少一半
  2. 问题隔离更好:库存服务挂掉不会影响登录流程
  3. 弹性伸缩更容易:订单服务高峰扩容时不影响其他模块
  4. 新技术试点可行:我们用 Go 编写了新的营销引擎,通过 HTTP 对接老系统
  5. 开发协作顺畅:各组专注自己负责的服务,冲突大大减少

不过,我们也牺牲了一些东西:比如原本简单的 JOIN 查询变成了跨服务调用,有时候为了获取一个聚合视图,不得不多次调用多个服务。为此我们也引入了 CQRS 架构中的 Read Side 来做查询优化。

总结 & 建议

如果你也打算从单体转向微服务,这里是我总结的一些心得:

不要盲目追求微服务,适合自己才是最好的

尤其是小型团队,微服务会带来可观的运维复杂度和学习曲线。如果你的日活不足万级,不妨先尝试模块化+垂直切分。

重视服务边界的设计

前期花足够时间梳理领域模型和边界,宁可在纸上画烂几个版本也不要仓促编码。

逐步演进而不是推倒重来

我们采取了边拆边保留旧接口的方式,确保过渡期平稳。新功能一律走微服务架构,老功能逐步迁移。

配套基础设施要跟上

日志、监控、链路追踪这些工具一旦缺失,后期查问题会非常痛苦。

最后送大家一句话:微服务不是银弹,它只是帮助我们在复杂系统面前保持灵活性的一种手段。

如果你现在正面临类似的困境,希望这篇文章能给你一些启发。欢迎留言交流你的经历,一起成长 💪


本文所述案例均为真实项目经验改编,文中涉及系统细节已脱敏处理。如有雷同,纯属巧合。

评论 0

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