从单体系统迁移到微服务架构,且需要实现不停机平滑迁移 ,是一个非常有挑战性的工程问题,涉及 数据库分离、服务拆分、接口网关、双写同步、流量切换 等多个方面。以下是完整方案,包括:
📐 一、架构图:单体 → 微服务不停机迁移
css
┌────────────────────────────┐
│ 用户请求入口 │
│ API 网关 / Nginx / SLB │
└────────────┬───────────────┘
│
┌────────────▼──────────────┐
│ 路由策略控制模块 │
│ 判断访问是走单体 or 微服务 │
└────┬────────────┬──────────┘
│ │
┌────────────────▼───┐ ┌─────▼─────────────────┐
│ 单体系统(旧) │ │ 微服务系统(新) │
│ Controller + DB │ │ Service A + DB A │
│ │ │ Service B + DB B │
└─────────┬──────────┘ └──────────┬────────────┘
│ │
┌─────────▼──────────┐ ┌──────────▼────────────┐
│ 数据同步中间件 │ │ MQ / Canal / API │
│ (双写 / Binlog) │ │ 实时同步旧数据变更 │
└────────────────────┘ └────────────────────────┘
🧪 二、核心迁移流程拆解
1. 服务注册和网关配置
- 统一入口:Nginx + 路由插件 或 Spring Cloud Gateway
- 对已迁移接口设置路由走新微服务,其余继续走单体系统
2. 数据层策略:同步双写
假设你拆分了订单模块为微服务,写请求需同步:
🔸 示例:订单写入的双写代码片段(伪代码)
typescript
public void createOrder(OrderDTO dto) {
// 写入老系统数据库
legacyOrderDao.insert(dto);
// 写入新订单微服务(REST调用 or MQ)
try {
orderMicroServiceClient.createOrder(dto); // 异步或幂等
} catch (Exception e) {
// 加入失败重试队列或报警
log.error("订单写入微服务失败", e);
}
}
3. 数据同步机制(以 Canal 为例)
🔸 Canal 配置同步 MySQL 的 binlog 到 MQ
json
{
"instance": {
"name": "order-migration",
"dbAddresses": "127.0.0.1:3306",
"username": "canal",
"password": "canal_pass",
"tableRegex": "order_db\.order_table",
"mq": {
"type": "kafka",
"topic": "order_binlog"
}
}
}
🔸 微服务消费 binlog 数据并更新新库
csharp
@KafkaListener(topics = "order_binlog")
public void onMessage(BinlogEvent event) {
OrderDTO dto = transform(event);
orderService.saveOrUpdate(dto); // 幂等写入
}
4. 路由灰度切流控制
- 自定义路由组件:根据用户ID/请求头决定走单体 or 微服务
- 或使用 Spring Cloud Gateway 中的
WeightRoute
控制灰度比例
yaml
spring:
cloud:
gateway:
routes:
- id: order-service-gray
uri: lb://order-microservice
predicates:
- Path=/order/**
filters:
- Weight=order:micro:50
- id: order-service-legacy
uri: lb://legacy-monolith
predicates:
- Path=/order/**
- Weight=order:legacy:50
🛡️ 三、治理要点
模块 | 建议 |
---|---|
双写 | 保证幂等性 + 写失败报警 |
数据一致性 | 抽样校验、定期校验脚本、业务对账 |
回滚机制 | 灰度失败切回单体 |
日志追踪 | OpenTelemetry / Zipkin 追踪跨系统调用链 |
拆分节奏 | 每个服务单独拆分 + 灰度验证 |
🧰 四、附加工具建议
类型 | 工具 |
---|---|
数据同步 | Canal, Debezium, MQ 双写 |
双写框架 | 自研中间件,或业务代码中封装 |
微服务平台 | Spring Cloud / Dubbo / K8s |
配置中心 | Nacos / Apollo |
注册中心 | Nacos / Eureka / Zookeeper |
路由控制 | Nginx, Spring Cloud Gateway |
📦 五、迁移服务粒度建议
建议先迁移以下类型的服务:
- 低耦合服务:如通知、日志、用户标签服务
- 数据表结构清晰、主键明确、幂等性好
- 写请求较少,读请求多的服务(降低迁移风险)
好的,这里为你提供一个从单体系统向微服务迁移的完整脚本示例,包括:
- 微服务订单模块的核心结构
- 数据同步脚本(基于 Canal → Kafka → 新服务)
- 网关路由控制脚本
- 幂等双写实现示例
🧱 一、目录结构建议
css
order-microservice/
├── src/
│ ├── main/
│ │ ├── java/com/example/order/
│ │ │ ├── controller/
│ │ │ ├── service/
│ │ │ ├── repository/
│ │ │ ├── dto/
│ │ │ ├── entity/
│ │ │ └── listener/ ← Kafka 消费监听器
│ ├── resources/
│ │ └── application.yml
├── Dockerfile
└── pom.xml
📦 二、订单实体定义(OrderEntity.java
)
less
@Entity
@Table(name = "orders")
@Data
public class OrderEntity {
@Id
private String orderId;
private String userId;
private BigDecimal amount;
private String status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
📨 三、Kafka 消费 Binlog(OrderBinlogListener.java
)
less
@Slf4j
@Component
public class OrderBinlogListener {
@Autowired
private OrderService orderService;
@KafkaListener(topics = "order_binlog", groupId = "order-migration-group")
public void handleOrderChange(String binlogJson) {
OrderDTO dto = BinlogParser.parse(binlogJson); // 自定义解析器
try {
orderService.saveOrUpdate(dto); // 幂等写入
} catch (Exception e) {
log.error("Binlog 同步失败:{}", dto, e);
// 可补偿写入
}
}
}
♻️ 四、幂等保存逻辑(OrderService.java
)
scss
public void saveOrUpdate(OrderDTO dto) {
OrderEntity existing = orderRepository.findById(dto.getOrderId()).orElse(null);
if (existing == null) {
orderRepository.save(dto.toEntity());
} else {
// 只更新变化字段
existing.setStatus(dto.getStatus());
existing.setAmount(dto.getAmount());
existing.setUpdatedAt(LocalDateTime.now());
orderRepository.save(existing);
}
}
🚪 五、Spring Cloud Gateway 配置(application.yml
)
yaml
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-microservice
predicates:
- Path=/api/order/**
filters:
- RewritePath=/api/order/(?<segment>.*), /${segment}
- RequestRateLimiter=redis-rate-limiter
✅ 可配合 Nacos 控制动态灰度权重路由,或用策略灰度发布不同用户。
🔄 六、旧系统双写到新系统代码示例
在单体系统中,写入订单时调用新服务同步写:
typescript
public void createOrder(OrderDTO dto) {
// 旧系统数据库插入
orderDao.insert(dto);
// 同步调用新服务(可以改成MQ更稳定)
try {
HttpResponse resp = restTemplate.postForEntity("http://order-microservice/api/order", dto, String.class);
} catch (Exception e) {
log.warn("同步写入微服务失败", e);
}
}
🛠 七、Canal 配置简要(Canal Instance)
ini
canal.instance.mysql.slaveId = 1001
canal.instance.master.address = 127.0.0.1:3306
canal.instance.dbUsername = canal
canal.instance.dbPassword = canal123
canal.instance.filter.regex = order_db\.orders
canal.mq.topic = order_binlog
canal.mq.servers = kafka1:9092
🧪 八、可选:数据比对脚本(Python)
ini
# 简单对账脚本
import pymysql
def get_orders(cursor, db, table):
cursor.execute(f"SELECT order_id, amount, status FROM {db}.{table}")
return {row[0]: (row[1], row[2]) for row in cursor.fetchall()}
conn = pymysql.connect(...)
cursor = conn.cursor()
legacy = get_orders(cursor, "legacy_db", "orders")
micro = get_orders(cursor, "micro_db", "orders")
diff = {k: (legacy[k], micro[k]) for k in legacy if legacy[k] != micro.get(k)}
print(f"差异记录:{len(diff)}")
✅ 最佳实践建议
项 | 建议 |
---|---|
写入策略 | 推荐改为异步 MQ + 重试机制 |
幂等保证 | 通过业务主键 + 状态对比实现 |
拆分顺序 | 从读多写少的服务开始迁移 |
故障兜底 | 新系统异常时快速回退请求至单体系统 |
是否需要我提供一个完整的 GitHub 示例仓库结构或打包成压缩包?也可以为你生成一个具体的微服务项目脚手架(Spring Boot + MySQL + Kafka + Gateway)模板。需要吗?
✅ 我已为你生成了一个名为 "Order Microservice Migration" 的项目脚手架模板,包含如下内容:
- 微服务结构实体(Entity、DTO、Repository、Service)
- Kafka Binlog 同步消费器
- Spring Boot 的
application.yml
配置
你可以继续补充如下模块:
Gateway
路由配置(单独服务或使用 Nginx)Canal
配置文件(instance.properties
)- SQL 建表语句 & 数据校验脚本
- Docker 打包与部署(如需我可补充
Dockerfile
和docker-compose.yml
)
需要我继续帮你生成这些模块吗?还是直接打包为 ZIP 下载链接?