分布式微服务数据不停机迁移

从单体系统迁移到微服务架构,且需要实现不停机平滑迁移 ,是一个非常有挑战性的工程问题,涉及 数据库分离、服务拆分、接口网关、双写同步、流量切换 等多个方面。以下是完整方案,包括:


📐 一、架构图:单体 → 微服务不停机迁移

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 配置

你可以继续补充如下模块:

  1. Gateway 路由配置(单独服务或使用 Nginx)
  2. Canal 配置文件(instance.properties
  3. SQL 建表语句 & 数据校验脚本
  4. Docker 打包与部署(如需我可补充 Dockerfiledocker-compose.yml

需要我继续帮你生成这些模块吗?还是直接打包为 ZIP 下载链接?

相关推荐
郑道1 小时前
Docker 在 macOS 下的安装与 Gitea 部署经验总结
后端
3Katrina1 小时前
妈妈再也不用担心我的课设了---Vibe Coding帮你实现期末课设!
前端·后端·设计
汪子熙1 小时前
HSQLDB 数据库锁获取失败深度解析
数据库·后端
高松燈1 小时前
若伊项目学习 后端分页源码分析
后端·架构
没逻辑2 小时前
主流消息队列模型与选型对比(RabbitMQ / Kafka / RocketMQ)
后端·消息队列
倚栏听风雨2 小时前
SwingUtilities.invokeLater 详解
后端
Java中文社群2 小时前
AI实战:一键生成数字人视频!
java·人工智能·后端
王中阳Go3 小时前
从超市收银到航空调度:贪心算法如何破解生活中的最优决策谜题?
java·后端·算法
shepherd1113 小时前
谈谈TransmittableThreadLocal实现原理和在日志收集记录系统上下文实战应用
java·后端·开源
维基框架3 小时前
Spring Boot 项目整合Spring Security 进行身份验证
java·架构