微服务架构设计实战:从单体到分布式
本文首发于我的技术博客,记录我在阿里园区凌晨三点的一次架构重构。
大家好,我是 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