单体架构升级微服务,一步步带你设计演进路线
【洛水石】 | 架构进阶 | Java架构师必读
图1:单体架构到微服务的四阶段演进路线图
很多团队在业务快速增长后,突然发现原来的单体系统撑不住了。但盲目拆微服务不是银弹,拆错了反而让系统更脆弱。本文从真实案例出发,系统梳理单体→微服务的演进路径、拆分策略、技术选型和常见踩坑。
一、先搞清楚:你真的需要微服务吗?
1.1 微服务不是终点,而是手段
微服务解决的核心问题:
| 问题 | 微服务如何解决 |
|------|---------------|
| 单体部署风险高(改一处全挂) | 独立部署,故障隔离 |
| 团队协作瓶颈(多人改同一仓库) | 按服务拆分,团队独立迭代 |
| 局部扩容困难(只有一个Pod) | 按服务独立扩缩容 |
| 技术栈绑死(全是Java) | 多语言混用 |
1.2 但微服务也带来了新问题
- **网络延迟**:原来是本地调用,现在变成RPC
- **分布式事务**:跨服务的数据一致性保证
- **服务治理**:注册、发现、熔断、限流一套都要搞
- **运维复杂度**:从1个应用变成10+个服务,日志链路追踪
- **开发成本上升**:本地开发时需要跑多个服务
1.3 什么时候不应该上微服务
Martin Fowler:"微服务首先要解决的第一个问题,是如何识别微服务的边界。"
以下情况不建议:
- 团队规模 < 10人
- 业务还不稳定,边界模糊
- 日活用户 < 1万,没有明显性能瓶颈
- 团队没有K8s/Docker运维经验
结论:先把单体做好,再谈拆分。
二、演进路线图:四个阶段
架构演进不应该一步到位,应该按业务增长节奏分阶段推进:
阶段一:单体 + 垂直扩展(10万日活以内)
↓ 瓶颈:数据库连接数、CPU/内存、模块耦合
阶段二:单体 + 分层优化(引入缓存、读写分离)
↓ 瓶颈:发布风险、团队协作、局部扩容
阶段三:模块化单体 → 服务化(业务边界清晰化)
↓ 瓶颈:跨模块调用变多,独立部署需求强
阶段四:微服务体系(完整治理栈)
三、第一阶段:优化单体,把地基打牢
3.1 数据库优化

在系统规模不大时,很多问题可以靠数据库优化解决:
-- 1. 合理建立索引
ALTER TABLE orders ADD INDEX idx_user_status(user_id, status);
ALTER TABLE orders ADD INDEX idx_created_at(created_at);
-- 2. 慢查询监控
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 1; -- 超过1s记录慢日志
-- 3. 读写分离(通过代理层)
-- 主库:写操作
-- 从库:读操作(配合 MyBatis-Plus @DS 注解)
3.2 引入缓存层
// Redis缓存典型场景
@Service
public class ProductService {
@Cacheable(value = "product", key = "#productId",
unless = "#result == null")
public ProductVO getProduct(Long productId) {
return productMapper.selectById(productId);
}
@CacheEvict(value = "product", key = "#product.id")
public void updateProduct(Product product) {
productMapper.updateById(product);
}
}
3.3 应用层垂直扩展
# docker-compose.yml - 单体应用多实例 + Nginx负载均衡
version: '3'
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
app1:
image: myapp:latest
environment:
- SERVER_PORT=8080
app2:
image: myapp:latest
environment:
- SERVER_PORT=8081
四、第二阶段:模块化单体,为拆分做准备
4.1 为什么要先做模块化
直接把耦合的单体拆成微服务,100%翻车。正确姿势是:
先在单体内部做好模块边界,再分离部署。
4.2 DDD战略设计:识别限界上下文

以电商系统为例,用DDD方法识别业务域:
核心域(核心竞争力):
└── 订单域:下单、支付、退款、售后
└── 商品域:SKU管理、库存、定价
支撑域(业务支撑):
└── 用户域:注册、登录、权限
└── 营销域:优惠券、满减、积分
通用域(可复用):
└── 消息通知:短信、邮件、推送
└── 文件服务:图片、文件上传
4.3 包结构按模块划分
com.company.ecommerce/
├── order/ # 订单模块
│ ├── domain/ # 领域模型
│ │ ├── Order.java
│ │ └── OrderItem.java
│ ├── service/
│ │ └── OrderService.java
│ ├── repository/ # 数据访问(只访问本模块的表)
│ └── api/ # 对外接口(仅通过接口调用)
│ └── OrderApi.java
├── product/ # 商品模块
│ ├── domain/
│ ├── service/
│ └── api/
│ └── ProductApi.java
└── user/ # 用户模块
└── api/
└── UserApi.java
关键约定:
- 模块间**只允许通过 api 层接口调用**,禁止直接访问其他模块的 Service 或 Mapper
- 每个模块**只访问自己的数据库表**,禁止跨模块 JOIN
4.4 用门面模式隔离内部实现
// 订单模块对外暴露的接口(api层)
public interface OrderApi {
OrderDTO createOrder(CreateOrderCommand cmd);
OrderDTO getOrder(Long orderId);
void cancelOrder(Long orderId, String reason);
}
// 商品模块查询接口
public interface ProductApi {
ProductDTO getProduct(Long productId);
boolean checkAndDeductStock(Long skuId, Integer count);
}
// 订单服务通过接口调用商品,不直接依赖ProductMapper
@Service
public class OrderServiceImpl implements OrderService {
private final ProductApi productApi; // 接口注入
public OrderDTO createOrder(CreateOrderCommand cmd) {
// 通过接口查询商品,将来改为RPC调用只需换实现
ProductDTO product = productApi.getProduct(cmd.getProductId());
boolean deducted = productApi.checkAndDeductStock(
cmd.getSkuId(), cmd.getCount());
// ...
}
}
五、第三阶段:拆出第一个微服务
5.1 拆分顺序:先拆通用服务,后拆核心服务
| 拆分顺序 | 原因 |
|---------|------|
| **第一批:无状态通用服务**(文件、短信) | 边界清晰,不涉及事务 |
| **第二批:读多写少的查询服务**(商品展示) | 可缓存,容错简单 |
| **第三批:相对独立的业务模块**(用户中心) | 边界清晰,依赖少 |
| **最后:核心交易链路**(订单、支付) | 最复杂,留到团队有经验后再拆 |
5.2 数据库如何拆
一个服务一个库,这是铁律:
-- 拆分前(单库)
CREATE DATABASE ecommerce;
USE ecommerce;
-- user, product, order, inventory 表全在一起
-- 拆分后(分库)
CREATE DATABASE ecommerce_user; -- 用户库
CREATE DATABASE ecommerce_product; -- 商品库
CREATE DATABASE ecommerce_order; -- 订单库
CREATE DATABASE ecommerce_inventory; -- 库存库
拆库迁移三步法:
步骤1:双写阶段
新代码同时写旧库和新库,以旧库为主
步骤2:验证阶段
对比新旧库数据一致性,修复差异
步骤3:切换阶段
新库读写,旧库废弃
保留旧库一段时间作为兜底
5.3 服务间通信选型
| 方式 | 框架 | 适用场景 |
|------|------|---------|
| **REST HTTP** | OpenFeign | 简单调用,对延迟要求不高 |
| **RPC** | Dubbo/gRPC | 高频调用,性能优先 |
| **消息队列** | RocketMQ/Kafka | 异步解耦,最终一致性 |
// OpenFeign调用示例
@FeignClient(name = "product-service", fallbackFactory = ProductFallback.class)
public interface ProductFeignClient {
@GetMapping("/internal/product/{id}")
ProductDTO getProduct(@PathVariable Long id);
@PostMapping("/internal/stock/deduct")
Boolean deductStock(@RequestBody DeductStockRequest request);
}
// 降级处理
@Component
public class ProductFallback implements FallbackFactory<ProductFeignClient> {
@Override
public ProductFeignClient create(Throwable cause) {
return new ProductFeignClient() {
@Override
public ProductDTO getProduct(Long id) {
log.error("商品服务降级,id={}", id, cause);
return null; // 或返回缓存数据
}
@Override
public Boolean deductStock(DeductStockRequest request) {
log.error("库存扣减降级,走MQ异步补偿", cause);
return false;
}
};
}
}
六、第四阶段:完整微服务治理体系
6.1 服务注册与发现
# application.yml - Nacos配置
spring:
cloud:
nacos:
discovery:
server-addr: nacos:8848
namespace: prod
group: DEFAULT_GROUP
config:
server-addr: nacos:8848
file-extension: yaml
shared-configs:
- dataId: common-config.yaml
refresh: true
6.2 网关统一入口

# Gateway路由配置
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/order/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100
redis-rate-limiter.burstCapacity: 200
- id: product-service
uri: lb://product-service
predicates:
- Path=/api/product/**
filters:
- StripPrefix=1
6.3 Sentinel 熔断限流
@RestController
public class OrderController {
@GetMapping("/order/{id}")
@SentinelResource(
value = "getOrder",
blockHandler = "getOrderBlocked", // 触发限流/熔断时
fallback = "getOrderFallback" // 抛出异常时
)
public Result<OrderVO> getOrder(@PathVariable Long id) {
return Result.ok(orderService.getOrder(id));
}
// 限流降级方法
public Result<OrderVO> getOrderBlocked(Long id, BlockException ex) {
return Result.fail(429, "系统繁忙,请稍后重试");
}
// 异常降级方法
public Result<OrderVO> getOrderFallback(Long id, Throwable t) {
log.error("订单查询异常,id={}", id, t);
return Result.fail(500, "服务暂时不可用");
}
}
6.4 链路追踪:SkyWalking
<!-- pom.xml -->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<version>9.2.0</version>
</dependency>
// 自定义埋点
@Service
public class OrderService {
@Trace(operationName = "createOrder")
public OrderDTO createOrder(CreateOrderCommand cmd) {
ActiveSpan.tag("userId", cmd.getUserId().toString());
ActiveSpan.tag("productId", cmd.getProductId().toString());
// 业务逻辑...
}
}
七、跨服务事务:分布式事务解决方案
微服务最大的难题是分布式事务。详见专题文章,这里给出选型建议:
场景一:强一致性要求(金融转账)
→ Seata AT 模式(自动补偿,适合简单CRUD)
→ Seata TCC 模式(手动补偿,适合高并发)
场景二:最终一致性(订单创建→积分发放)
→ 本地消息表 + RocketMQ 事务消息
→ 幂等性保证:唯一消息ID + 数据库唯一索引
场景三:长流程业务(物流、退款)
→ Saga 模式(状态机驱动,每步记录)
八、常见踩坑汇总
坑1:服务拆太细,反成"分布式单体"
拆了20个服务,每个服务只有2个接口,调用链深达10层。
原则:按业务能力(Business Capability)拆,不是按技术层拆。
坑2:共享数据库
❌ order_service 和 product_service 都连同一个 ecommerce 库
✅ 每个服务有自己的数据库,通过API调用获取其他服务数据
坑3:忽略服务启动顺序
# docker-compose 依赖设置
services:
order-service:
depends_on:
nacos:
condition: service_healthy
mysql:
condition: service_healthy
坑4:没有熔断,雪崩效应
一个服务变慢 → 调用方线程积压 → 调用方也变慢 → 整个系统崩溃
// 必须配置超时 + 熔断
feign:
client:
config:
default:
connectTimeout: 1000 # 1s连接超时
readTimeout: 3000 # 3s读超时
circuitbreaker:
enabled: true # 开启熔断
坑5:日志没有TraceId,排查困难
// MDC传递TraceId
public class TraceIdFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ...) {
String traceId = ((HttpServletRequest)req).getHeader("X-Trace-Id");
if (StringUtils.isBlank(traceId)) {
traceId = UUID.randomUUID().toString().replace("-","");
}
MDC.put("traceId", traceId);
// 传递给下游
chain.doFilter(req, resp);
MDC.clear();
}
}
九、演进检查清单
判断是否该拆分
- [ ] 团队规模超过10人,且有独立负责不同业务模块的子团队
- [ ] 部署频率高,一处改动影响全局发布
- [ ] 某个模块(如商品搜索)有独立的扩容需求
- [ ] 已完成模块化单体改造,模块边界清晰
拆分前准备
- [ ] 建立分布式链路追踪(SkyWalking/Zipkin)
- [ ] 建立统一日志中心(ELK)
- [ ] 制定API接口规范
- [ ] 建立服务注册中心(Nacos)
- [ ] 制定回滚方案
拆分中注意
- [ ] 先拆通用服务,再拆核心服务
- [ ] 数据库随服务一起拆,不共享数据库
- [ ] 服务间通信使用接口,便于切换
- [ ] 每个服务单独部署,有独立CI/CD流水线
十、总结
| 阶段 | 关键动作 | 技术栈 |
|------|---------|--------|
| 单体优化 | 索引、缓存、读写分离 | MySQL、Redis、Nginx |
| 模块化单体 | DDD分模块、接口隔离 | Spring Boot模块化 |
| 服务拆分 | 按业务域拆、数据库独立 | Spring Cloud、Feign |
| 完整微服务 | 注册/发现/熔断/链路追踪 | Nacos、Sentinel、SkyWalking |
架构演进没有银弹,按业务阶段走,每一步都要有充分的理由。别因为"微服务流行"就拆服务,别因为"单体OUT了"就贬低单体。
更多推荐
所有评论(0)