微服务架构设计实战:从单体到分布式 —— 我在一次系统重构中的真实经历

雅致生活
2025-06-17 05:11
阅读 367

引言:为什么我决定踏上微服务之路?

引言:为什么我决定踏上微服务之路?

我叫老张,是一名后端开发出身的技术负责人,目前负责某电商平台的系统架构。2021年底,我们遇到了一个典型问题:原本采用单体架构的电商平台,在用户量和业务复杂度逐渐增长之后,系统性能开始下滑,部署成本越来越高,代码维护也越来越困难。

那时候我们的系统是Spring Boot + MyBatis搭建的一个巨石应用,前端Vue.js通过HTTP API与后端交互。最开始一切看起来都挺正常,随着促销活动、新功能不断上线,问题逐步暴露出来:

  • 每次发版都提心吊胆,一个小改动都可能导致整个系统出错;
  • 数据库压力越来越大,多个模块共享同一套数据表结构,锁表、慢查询频繁发生;
  • 团队协作效率下降,不同小组改同一个模块,代码冲突不断;
  • 扩展性差,某些高并发的模块无法单独扩容,必须整体升级服务器。

于是,我们决定启动架构改造计划——把单体应用拆分成基于微服务的分布式系统。今天,我就想结合这段真实的经历,聊一聊我在实际工作中如何做微服务架构设计,以及过程中踩过的坑和收获的经验。


项目背景:从单体到微服务的起点

项目背景:从单体到微服务的起点

项目名称是我们自研的“XX商城”,定位是一个中型B2C电商系统,包括商品管理、订单中心、会员系统、优惠券系统、支付对接等核心模块。

原系统的痛点总结如下:

模块 存在的问题
商品服务 数据量大、读多写少,经常成为瓶颈
订单中心 高并发下单时响应延迟明显
用户中心 和其他模块耦合严重,修改风险高
优惠券系统 规则复杂,逻辑嵌套多,难以维护
系统整体 每次发布都要全量重启,灰度发布几乎不可行

于是我们在22年初成立了一个专项组,目标是在半年内完成向微服务架构的迁移,同时保持线上服务平稳运行。


挑战来了:拆分不是简单的事情

1. 划分服务边界是个难题

一开始,我们尝试按照传统的“垂直划分”方式来拆分服务,比如商品服务、订单服务、用户服务等等。但很快发现一个问题:有些数据或逻辑存在跨服务调用需求,怎么处理?

例如用户在下单的时候,需要检查库存是否足够、验证优惠券是否可用、扣减用户积分等多个操作,这些分别属于不同的服务。如果使用远程调用串联,可能会引入分布式事务一致性问题

举个例子:订单服务调用了库存服务进行库存扣减,结果在后续调用优惠券服务时失败了怎么办?是不是要回滚前面的操作?这在没有合适机制的情况下非常容易出问题。

所以,我们在最初阶段就意识到:服务边界的划分不仅要看业务模块,更要考虑数据归属和一致性要求。

2. 技术选型也头疼

我们要从零构建微服务架构,面临的第一个选择就是框架和组件:

  • 使用 Spring Cloud 还是 Dubbo?
  • 注册中心用 Eureka、Zookeeper 还是 Nacos?
  • 分布式配置中心用 Config 还是 Apollo?
  • 如何实现服务治理(负载均衡、熔断限流、降级)?
  • 是否需要网关统一接入?
  • 如何保证日志追踪、链路监控?

我们当时做了不少技术调研和内部对比,最后选择了以下几个组合:

技术栈 选型理由
Spring Cloud Alibaba 更适合国内场景,对阿里生态支持好
Nacos 注册中心+配置中心一体化,易于集成
Sentinel 流量控制和熔断能力强,中文文档友好
Gateway 统一入口,路由、鉴权集中处理
Sleuth + Zipkin 实现完整的分布式链路追踪
RocketMQ 解耦异步消息队列

这套技术栈虽然成熟,但在实际落地过程中也有不少磕碰,后面我会详细说。


设计思路:从“粗粒度”拆分开始,逐步细化

我们并没有一开始就追求所谓的“完美领域驱动设计”(DDD),而是采用了相对保守的方式:

  1. 先按照主实体划分服务边界

    • 商品服务:负责商品基本信息、库存、SKU等;
    • 订单服务:订单创建、状态流转、退货退款;
    • 用户服务:用户信息、地址管理;
    • 营销服务:优惠券、满减规则、折扣策略;
    • 支付服务:对接第三方支付平台、异步通知处理;
  2. 设计服务之间的接口通信方式

    • 同步调用用 Feign + OpenFeign;
    • 异步解耦用 RocketMQ;
    • 跨域权限认证用 OAuth2 + JWT;
    • 所有请求走 Gateway 做统一认证和流量控制;
  3. 数据库设计方面严格隔离

    • 每个服务拥有独立的数据库实例;
    • 表结构只暴露给内部访问,对外通过接口提供;
    • 对于强一致性的数据,如库存,我们引入了本地事务 + 半消息机制确保最终一致;
  4. 基础设施同步搭建

    • 使用 Docker 容器化部署;
    • 每个微服务对应一个独立的 Jenkins 构建任务;
    • Kubernetes 编排实现滚动发布;
    • 监控方面集成了 Prometheus + Grafana;

代码实践:部分关键代码片段分享

这里展示几个我们在实际开发中常用的代码示例,供参考。

1. Feign 接口定义(用户服务调用商品服务)

@FeignClient(name = "product-service", fallback = ProductServiceFallback.class)
public interface ProductServiceClient {

    @GetMapping("/products/{id}")
    ResponseEntity<ProductDTO> getProductById(@PathVariable("id") Long productId);
}

配合fallback类:

@Component
public class ProductServiceFallback implements ProductServiceClient {
    
    @Override
    public ResponseEntity<ProductDTO> getProductById(Long productId) {
        // 返回默认产品信息或抛出异常
        return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(null);
    }
}

这样即使商品服务暂时不可用,订单服务也能快速失败或降级,避免雪崩效应。

2. 使用 Sentinel 实现限流熔断

我们在Gateway层集成Sentinel限流插件,对每个接口设定QPS阈值,并设置自动降级策略:

spring:
  cloud:
    sentinel:
      datasource:
        ds1:
          nacos:
            server-addr: nacos:8848
            dataId: gateway-rules.json
            groupId: DEFAULT_GROUP
            data-type: json

对应的JSON限流规则如下:

[
  {
    "resource": "/order/create",
    "limitApp": "default",
    "grade": 1,
    "count": 200,
    "strategy": 0,
    "controlBehavior": 0
  }
]

以上表示对/order/create接口设置每秒最多200个请求,超过自动限流。


踩坑经验:那些年我们一起掉进的陷阱

1. 微服务拆得太细反而拖累效率

我们在初期有个误区:认为拆得越细越好,后来才发现很多小服务之间相互依赖严重,反而让接口数量暴增,调用关系混乱,甚至出现“循环调用”的问题。

最终解决方案是:合并一些低耦合的服务,适当放宽边界,优先解决高频调用的痛点。

2. 忽视数据一致性带来的问题

有一次在订单创建流程中,我们漏掉了对优惠券扣减失败的回滚,导致出现了优惠券被错误发放的情况。后来我们引入了一种本地事务+消息确认的方式来兜底:

  • 在优惠券服务中记录“已使用”状态;
  • 发送一条确认消息到MQ;
  • 如果下游失败,根据消息重试或人工补偿。

类似这样的机制帮助我们大幅减少了线上故障率。

3. 日志追踪缺失导致排查困难

刚上线那会儿,我们遇到一个接口报500,但不知道是哪个服务出了问题,只能一个一个查日志。后来我们引入了Sleuth + Zipkin,所有调用链上都有trace id,极大提高了问题定位效率。


改造后的效果和收益

经过几个月的努力,我们完成了核心模块的微服务化重构,并逐步将流量切换过去。效果如下:

指标 改造前 改造后
单节点吞吐能力 600 QPS 提升至 1200 QPS
发布稳定性 30% 的版本有回滚 降至 5% 左右
故障隔离能力 全站受影响 局部故障不影响全局
开发协同效率 多人改同一工程易冲突 各自独立开发提升30%

缓存策略对比-1

更关键的是,我们实现了灵活的弹性扩缩容:高峰时段可以单独扩增订单服务和商品服务,平时节省资源投入。


经验分享:送给正在转型微服务的你

如果你也在考虑微服务架构,以下几点是我的亲身建议:

✅ 1. 不要为了拆而拆

微服务不是万能药,它适用于业务逻辑复杂、团队规模较大、有长期维护预期的项目。如果是中小型项目,或者团队没有相关经验,不要盲目拆分。

✅ 2. 服务边界设计比技术选型更重要

很多时候我们把过多精力放在“用哪个框架”,却忽略了更重要的:服务职责划分是否合理。记住:好的服务边界应该满足“高内聚、低耦合”。

✅ 3. 一定要提前规划运维体系

微服务带来的不仅仅是代码的变化,还有运维、部署、监控、日志收集等一系列挑战。建议一开始就搭建一套自动化CI/CD流水线,否则后期维护成本极高。

✅ 4. 拿捏好“同步”和“异步”的比例

并不是所有调用都需要远程调用。对于非实时性要求高的操作,尽量通过消息队列解耦,既能提高系统稳定性,又能减少网络开销。

✅ 5. 分布式事务是个坎,别硬上

除非你真的需要强一致性,否则推荐使用“最终一致”的方案,如本地事务+补偿、TCC、Saga模式,或者干脆设计成幂等接口+异步对账。


结语:这条路值得走下去吗?

回头看这一年多的时间,确实不容易。我们经历了从焦虑到迷茫,再到信心满满的转变。微服务架构带来了更高的灵活性和可扩展性,但也伴随着更高的学习成本和运维复杂度。

如果你问我:“你现在还会选择微服务吗?”我的答案是:

“如果业务需要,当然会。不过前提是要做好准备,不要低估它的复杂性。”

希望这篇文章能帮你少走些弯路,少踩点坑。如果你有类似经历,也欢迎留言交流,一起成长。


作者简介:老张,十年后端开发老兵,经历过千万级系统架构演进,喜欢研究架构、性能优化和新技术实践。公众号【程序员老张】持续分享一线实战经验。

评论 0

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