微服务架构设计实战:从单体到分布式

Dev架构师
2025-12-16 16:03
阅读 1030

本文首发于我的技术博客,记录我在阿里园区凌晨三点的一次架构重构。

大家好,我是 Claude Code 的早期尝鲜用户,坐标杭州未来科技城。白天在一家准独角兽公司搞后端架构,晚上在家撸代码顺便维护自己的技术博客。最近和几个在网易、阿里的老同学吃饭,聊起跳槽行情,发现“微服务”这三个字几乎成了简历上的必选项——哪怕你只是用 Spring Cloud 写过一个 Hello World。这让我想起去年我们团队被迫把那个“祖传单体”拆成微服务时的血泪史。

被产品经理逼出来的架构升级

事情得从去年双11前说起。我们的核心交易系统还是个 Java 单体应用,Spring Boot + MyBatis + MySQL 三件套,部署在几台大内存 ECS 上。平时还好,一到大促就各种超时、线程池爆满。运维兄弟天天盯着 Grafana 面色铁青,测试妹子提 Bug 的语气都带上了怨念:“你们这个下单接口又超时了,用户都跑了!”

最致命的是,业务方(也就是传说中的产品经理)突然提出要在下单流程里加一个“实时风控校验”。听起来很简单对吧?但问题在于,风控逻辑涉及外部合作方 API,响应时间不稳定,而我们的下单流程又是强同步的。这意味着一旦风控服务抖一下,整个下单链路就瘫痪。

CTO 在周会上拍桌子:“再不拆微服务,双11崩了大家一起滚蛋。”于是,我和另外两个后端兄弟被拉进了一个名为 “Project Phoenix” 的项目——名字很燃,现实很骨感。

从“上帝类”到服务边界划分

第一步当然是画领域模型。我们花了整整三天,把那个上万行的 OrderService 类拆解成几个核心子域:

  • 订单服务:负责创建、查询、状态机
  • 支付服务:对接支付宝/微信,处理异步回调
  • 风控服务:独立部署,支持熔断降级
  • 通知服务:短信、邮件、站内信

这里有个坑:一开始我们按“功能模块”拆,结果发现订单和支付之间循环依赖严重。后来重新按“业务能力”划分,才勉强理清边界。DDD(领域驱动设计)那本书我翻烂了,虽然实际落地时还是得向 deadline 低头。

// 拆分前:上帝类 OrderService.java
public class OrderService {
    public void createOrder(OrderDTO dto) {
        // 1. 校验库存
        // 2. 扣减库存(调用库存服务,但耦合在同一个事务里)
        // 3. 创建订单
        // 4. 调用风控(同步阻塞!)
        // 5. 发起支付
        // 6. 发送通知
        // ... 还有2000行
    }
}

拆完之后,每个服务都有自己独立的数据库,通过 Feign + Ribbon 调用。但很快新问题来了:分布式事务怎么办?

分布式事务:Saga 模式救我狗命

最初我们天真地用了 @Transactional,结果跨服务调用直接失效。线上出现了“订单已创建但库存未扣减”的诡异状态。当时真的想砸电脑。

调研了一圈,最终选了 Saga 模式:每个本地事务都有对应的补偿操作,通过消息队列保证最终一致性。

# order-service.yml
saga:
  steps:
    - action: createOrder
      compensate: cancelOrder
    - action: reserveInventory
      compensate: releaseInventory
    - action: applyRiskCheck
      compensate: noop # 风控无需补偿

关键点在于:

  • 补偿操作必须幂等
  • 状态机要持久化,避免服务重启后状态丢失
  • 异常情况下要有定时对账任务兜底

我们用 RocketMQ 做事件驱动,每步操作成功后发一个 OrderStepCompletedEvent。如果某步失败,就触发前面所有步骤的补偿。

小插曲:第一次压测时,因为没做幂等,补偿操作重复执行,把库存加爆了。测试妹子看到商品库存变成负数时的表情,我现在还记得……

服务治理:别让雪崩毁了你的双11

微服务拆开只是开始,真正考验在后面。服务多了之后,网络延迟、依赖故障、级联失败成了家常便饭。

我们在网关层做了全链路限流,每个服务配置了 Hystrix 熔断策略:

服务名 超时时间 熔断阈值 降级策略
order-service 800ms 50% 返回缓存订单
risk-service 1200ms 70% 跳过风控,记录日志
notify-service 500ms 90% 异步入队,立即返回

配置示例:

# application.yml
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 1000
      circuitBreaker:
        requestVolumeThreshold: 20
        errorThresholdPercentage: 50

另外,我们强制要求所有接口实现健康检查 /actuator/health,配合 Kubernetes 的 liveness probe。有一次,因为某个服务没暴露健康端点,K8s 一直往它转发流量,导致下游全挂。运维兄弟半夜打电话骂人,我默默改完配置后顺手更新了团队的《微服务 checklist》。

数据库拆分:别再用 join 了兄弟

单体时代,我们动不动就写五表连查。拆成微服务后,每个服务只能访问自己的数据库。这倒逼我们重新思考数据模型。

比如订单详情页,原来是一条 SQL 搞定:

SELECT o.*, p.payment_status, u.username 
FROM orders o
JOIN payments p ON o.id = p.order_id
JOIN users u ON o.user_id = u.id

现在不行了。我们采用了 CQRS 模式:

  • 写模型:各服务只维护自己的数据
  • 读模型:通过监听其他服务的事件,构建聚合视图

具体做法是:订单服务发布 OrderCreatedEvent,用户服务和支付服务各自消费,然后写入自己的 read model 表。查询时直接查聚合表,避免跨库 join。

-- order_read_model 表(由多个服务共同维护)
CREATE TABLE order_read_model (
  order_id BIGINT PRIMARY KEY,
  user_name VARCHAR(100),
  payment_status VARCHAR(20),
  risk_score INT,
  ...
);

虽然增加了数据冗余,但换来的是查询性能的大幅提升。双11当天,订单详情页 P99 响应时间从 1.2s 降到 200ms。

简历镀金 or 代码人生?

折腾了三个月,Project Phoenix 终于上线。双11 当天零重大故障,CTO 请全组吃了顿人均 500 的日料。更重要的是,这次重构让我对微服务的理解从“会用 Spring Cloud”变成了“知道什么时候不该用”。

最近帮朋友改简历,发现很多人写“精通微服务架构”,结果连 CAP 理论都说不清。其实微服务不是银弹,它解决的是团队协作和系统扩展性问题,代价是复杂度飙升。如果你的业务量还没到单机扛不住的地步,老实待在单体里可能更幸福。

对我自己来说,这次经历最大的收获不是技术栈更新,而是明白了架构的本质是权衡。要不要拆?怎么拆?拆到什么粒度?这些问题没有标准答案,只有结合业务场景、团队能力和时间节点的动态决策。

上周五晚上加班时,我看着监控大盘上平稳的曲线,突然觉得:所谓“代码人生”,大概就是在无数个深夜里,和 bug 斗智斗勇,最终让系统跑得比产品经理的需求还稳吧。


后记:如果你也在经历单体到微服务的痛苦转型,欢迎留言交流。顺便,我们团队在招后端,要求不高——能写出不给自己挖坑的代码就行 😉(坐标杭州,阿里网易边上,简历可直推)

评论 0

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