简单实现一个系统升级过程中的数据平滑迁移的场景实例

在系统升级过程中,数据的平滑迁移是关键的一部分,目的是在新旧系统同时运行的过程中,确保数据一致性和可用性。以下是一个实际场景案例及相应的实例代码。

实际场景案例

场景:电商平台订单服务升级

假设一个电商平台需要升级订单服务的数据库结构,将 order 表中的 customer_name 字段拆分为 first_namelast_name 两个字段,同时新系统引入了一个新的服务端点来操作订单。

数据平滑迁移的步骤

  1. 添加冗余字段(兼容阶段)
    • 在现有数据库中添加新的字段 first_namelast_name
    • 继续支持旧系统 customer_name 的读写。
  2. 双写阶段
    • 修改业务逻辑,在更新 customer_name 时,同时更新 first_namelast_name
    • 允许新字段参与数据查询。
  3. 切换阶段
    • 逐步切换到使用 first_namelast_name 代替 customer_name
  4. 清理阶段
    • 在验证所有业务无误后,移除旧字段 customer_name
  • 旧架构Order 服务存储 customerName
  • 新架构Order 服务存储 firstNamelastName,并通过 Customer 服务实时获取客户信息。
  • 目标:
    1. 实现旧数据迁移。
    2. 支持双写逻辑。
    3. 灰度切换到新系统。

详细实现代码

数据库表结构

Order 表

sql 复制代码
CREATE TABLE orders (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    customer_name VARCHAR(100), -- 旧字段
    first_name VARCHAR(50),     -- 新字段
    last_name VARCHAR(50),      -- 新字段
    customer_id BIGINT          -- 外键关联 Customer
);

Customer 表

sql 复制代码
CREATE TABLE customers (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    first_name VARCHAR(50),
    last_name VARCHAR(50)
);

Order 服务

实体类
java 复制代码
@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String customerName; // 旧字段

    private String firstName; // 新字段
    private String lastName;  // 新字段

    private Long customerId; // 关联 Customer

    // Getters and setters
}
Repository 接口
java 复制代码
public interface OrderRepository extends JpaRepository<Order, Long> {
    List<Order> findByFirstNameAndLastName(String firstName, String lastName);
}
服务层
java 复制代码
@Service
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private CustomerClient customerClient; // Feign 客户端与 Customer 服务交互

    // 创建订单,支持新旧逻辑
    @Transactional
    public Order createOrder(String customerName) {
        // 调用 Customer 服务获取信息
        CustomerResponse customer = customerClient.getCustomerByName(customerName);

        Order order = new Order();
        order.setCustomerName(customerName); // 旧字段

        // 填充新字段
        order.setFirstName(customer.getFirstName());
        order.setLastName(customer.getLastName());
        order.setCustomerId(customer.getId());

        return orderRepository.save(order);
    }

    // 查询订单
    public Order getOrder(Long id) {
        return orderRepository.findById(id).orElseThrow(() -> new RuntimeException("Order not found"));
    }

    // 更新订单
    @Transactional
    public Order updateOrder(Long id, String customerName) {
        CustomerResponse customer = customerClient.getCustomerByName(customerName);

        Order order = getOrder(id);
        order.setCustomerName(customerName); // 更新旧字段
        order.setFirstName(customer.getFirstName()); // 同步新字段
        order.setLastName(customer.getLastName());
        order.setCustomerId(customer.getId());

        return orderRepository.save(order);
    }

    // 批量迁移旧数据
    @Transactional
    public void migrateOldData() {
        List<Order> orders = orderRepository.findAll();

        for (Order order : orders) {
            if (order.getFirstName() == null || order.getLastName() == null) {
                // 调用 Customer 服务补全信息
                CustomerResponse customer = customerClient.getCustomerByName(order.getCustomerName());

                order.setFirstName(customer.getFirstName());
                order.setLastName(customer.getLastName());
                order.setCustomerId(customer.getId());
                orderRepository.save(order);
            }
        }
    }
}

Customer 服务

实体类
java 复制代码
@Entity
@Table(name = "customers")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String firstName;
    private String lastName;

    // Getters and setters
}
Repository 接口
java 复制代码
public interface CustomerRepository extends JpaRepository<Customer, Long> {
    Customer findByFirstNameAndLastName(String firstName, String lastName);
}
服务层
java 复制代码
@Service
public class CustomerService {

    @Autowired
    private CustomerRepository customerRepository;

    public Customer getCustomer(Long id) {
        return customerRepository.findById(id).orElseThrow(() -> new RuntimeException("Customer not found"));
    }

    public Customer createCustomer(String firstName, String lastName) {
        Customer customer = new Customer();
        customer.setFirstName(firstName);
        customer.setLastName(lastName);
        return customerRepository.save(customer);
    }
}

Feign 客户端(Order 服务调用 Customer 服务)

java 复制代码
@FeignClient(name = "customer-service")
public interface CustomerClient {

    @GetMapping("/customers/name")
    CustomerResponse getCustomerByName(@RequestParam String name);
}
Feign 响应对象
java 复制代码
public class CustomerResponse {
    private Long id;
    private String firstName;
    private String lastName;

    // Getters and setters
}

批量迁移工具

在启动时执行批量数据迁移。

java 复制代码
@Component
public class DataMigrationTask implements CommandLineRunner {

    @Autowired
    private OrderService orderService;

    @Override
    public void run(String... args) throws Exception {
        orderService.migrateOldData();
        System.out.println("Data migration completed.");
    }
}

控制器层

Order 控制器

java 复制代码
@RestController
@RequestMapping("/orders")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @PostMapping
    public ResponseEntity<Order> createOrder(@RequestParam String customerName) {
        Order order = orderService.createOrder(customerName);
        return ResponseEntity.ok(order);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Order> updateOrder(@PathVariable Long id, @RequestParam String customerName) {
        Order order = orderService.updateOrder(id, customerName);
        return ResponseEntity.ok(order);
    }
}

Customer 控制器

java 复制代码
@RestController
@RequestMapping("/customers")
public class CustomerController {

    @Autowired
    private CustomerService customerService;

    @PostMapping
    public ResponseEntity<Customer> createCustomer(@RequestParam String firstName, @RequestParam String lastName) {
        Customer customer = customerService.createCustomer(firstName, lastName);
        return ResponseEntity.ok(customer);
    }
}

灰度发布策略

  1. 启动阶段
    • Order 服务启用双写逻辑。
    • Customer 服务接收新数据写入。
  2. 切换阶段
    • 对旧数据进行批量迁移。
    • 验证新系统接口的稳定性。
  3. 清理阶段
    • 停止使用 customerName 字段。
    • 更新所有业务逻辑到新字段。

通过引入跨服务交互和批量迁移的逻辑,这个案例模拟了真实环境中的平滑迁移场景。

相关推荐
杨云龙UP42 分钟前
MySQL 8.0.x InnoDB 写入链路优化:Redo Log 与 Buffer Pool 扩容与缓冲区调优实战记录-20251029
linux·运维·数据库·sql·mysql
黄俊懿1 小时前
【深入理解SpringCloud微服务】Seata(AT模式)源码解析——开启全局事务
java·数据库·spring·spring cloud·微服务·架构·架构师
我命由我123452 小时前
python-dotenv - python-dotenv 快速上手
服务器·开发语言·数据库·后端·python·学习·学习方法
繁星蓝雨2 小时前
Qt优雅的组织项目结构三(使用CMakeLists进行模块化配置)——————附带详细示例代码
开发语言·数据库·qt
Jerry.张蒙3 小时前
SAP业财一体化实现的“隐形桥梁”-价值串
大数据·数据库·人工智能·学习·区块链·aigc·运维开发
无名修道院3 小时前
DVWA 靶场搭建:Windows11(phpstudy 搭建)(步骤 + 截图 + 常见问题)
数据库·网络安全·渗透测试·靶场·php·dvwa·phpstudy
Dwzun4 小时前
基于SpringBoot+Vue的二手书籍交易平台系统【附源码+文档+部署视频+讲解)
java·vue.js·spring boot·后端·spring·计算机毕业设计
期待のcode4 小时前
Wrapper体系中的condition参数
java·spring boot·后端·mybatis
CodeAmaz5 小时前
MySQL 事务隔离级别详解
数据库·mysql·事务隔离级别
千寻技术帮5 小时前
10398_基于SSM的教学评价管理系统
数据库·mysql·毕业设计·ssm·教学评价