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

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

实际场景案例

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

假设一个电商平台需要升级订单服务的数据库结构,将 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 字段。
    • 更新所有业务逻辑到新字段。

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

相关推荐
腾渊信息科技公司8 小时前
Spring Boot对接MES实战:视觉检测数据自动同步方案
java·人工智能·spring boot·后端·计算机视觉·ai·软件需求
憧憬成为web高手9 小时前
皮卡丘靶场速通--sql 2
数据库·sql·mybatis
段一凡-华北理工大学9 小时前
向量数据库实战:选型、调优与落地~系列文章12:文本分块策略实战:chunk_size 怎么选?重叠多少?
开发语言·数据库·后端·oracle·rust·工业智能体·高炉智能化
oradh11 小时前
Oracle XTTS实现跨版本迁移和升级(Oracle 11g单库升级至19C RAC集群)
数据库·oracle·11g升级19c·xtts跨版本迁移和升级
程序员cxuan11 小时前
Grok Build 被众人唾骂,结果老马把它开源了
人工智能·后端·程序员
隔窗听雨眠11 小时前
Spring Boot在云原生时代的编程范式革新研究
spring boot·后端·云原生
z1234567898612 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
程序猿DD12 小时前
一个 API Key,统一调用大模型、生图和联网搜索
数据库·网关
java_logo12 小时前
Docker Compose 部署 ClickHouse:快速搭建高性能列式数据库
数据库·clickhouse·docker·列式数据库·轩辕镜像·高性能数据库·clickhouse部署教程
TlSfoward13 小时前
抓包代理链路下的 TLS 指纹变化分析 TLSFOWARD抓包工具
数据库·爬虫·网络协议·搜索引擎·php