前言:架构不是设计出来的
我在一家电商公司做了五年后端。我们的系统从最初的"一个 Java 项目"变成了 20+ 微服务。这篇文章不是教科书式的"微服务最佳实践",而是真实的踩坑记录和反思。
Year 1:单体(单体不是"不好")
shop-app/
├── src/main/java/com/shop/
│ ├── controller/ # 用户、商品、订单全在这
│ ├── service/
│ ├── dao/
│ └── model/
└── pom.xml
这个阶段的真实情况:
- 日活不到 1000,QPS 个位数
- 部署:mvn package → scp 到服务器 → java -jar
- 数据库:一台 MySQL 搞定所有
- 最关键的事实:单体在这个阶段是最优解
团队只有 3 个人,拆微服务只会增加沟通成本和运维负担。
Year 2:第一次拆分——"因为痛才拆"
导火索:秒杀活动把系统打挂了,排查发现是商品查询和订单创建共享同一个数据库连接池。
拆分策略:把订单模块独立出来。
shop-user (端口 8081) ── 用户库
shop-goods (端口 8082) ── 商品库
shop-order (端口 8083) ── 订单库
踩的坑:
- 用户下单时要查商品信息和用户余额 → 3 个 HTTP 调用 → 延迟翻倍
- 数据库事务变成了分布式事务(当时不懂,直接忽略了)
- 没有服务发现,IP 写死在配置文件里
Year 3:引入基础设施
服务注册:Nacos
配置中心:Nacos
网关:Spring Cloud Gateway
限流:Sentinel
关键决策:没有引入消息队列,而是用数据库轮询 + 状态机来处理跨服务的数据一致性。这个选择在当时省了很多事,但后来...
Year 4:消息队列救场
订单量上来后,数据库轮询的延迟从 100ms 变成了 3s——因为轮询表越来越大。
引入 RocketMQ:
订单创建 → 发送 OrderCreated 事件
├── 库存服务消费 → 扣库存
├── 优惠券服务消费 → 核销优惠券
└── 通知服务消费 → 发短信
问题:消息丢失怎么办?
答案:本地消息表 + 定时补偿
经验:不要过早引入 MQ,但该引入的时候也别犹豫。判断标准是数据一致性的延迟要求。
Year 5:往回拆——合并过度拆分的服务
这是我们犯的最大的错误:为了微服务而微服务。
我们有 20 个服务,但其中 5 个是"一人服务"——只有一个开发者维护、部署频率极低。
合并后的结构:
核心域(拆):
├── 用户服务
├── 商品服务
├── 订单服务
└── 支付服务
支撑域(合):
└── 运营平台(合并了原来的:统计、活动、推送、配置 4 个服务)
五个核心教训
- 单体不是原罪。在用户量、团队规模、业务复杂度没到阈值之前,单体是最好的架构。
- 按业务域拆分,不要按技术层拆分。拆"订单服务"是合理的,拆"Service 层"是荒谬的。
- 先解决通信问题。服务发现、RPC 框架、可观测性,这三样是微服务的前提。
- 分布式事务能避免就避免。用最终一致性 + 补偿机制替代。
- 保持"拆回去"的勇气。过度拆分的微服务比单体更糟糕。
架构的本质是权衡。没有银弹,只有合适的选择。