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

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

实际场景案例

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

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

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

相关推荐
BestandW1shEs26 分钟前
谈谈Mysql的常见基础问题
数据库·mysql
重生之Java开发工程师29 分钟前
MySQL中的CAST类型转换函数
数据库·sql·mysql
教练、我想打篮球31 分钟前
66 mysql 的 表自增长锁
数据库·mysql
Ljw...32 分钟前
表的操作(MySQL)
数据库·mysql·表的操作
哥谭居民000133 分钟前
MySQL的权限管理机制--授权表
数据库
2401_8576100334 分钟前
SpringBoot社团管理:安全与维护
spring boot·后端·安全
wqq_99225027742 分钟前
ssm旅游推荐系统的设计与开发
数据库·旅游
凌冰_1 小时前
IDEA2023 SpringBoot整合MyBatis(三)
spring boot·后端·mybatis
难以触及的高度1 小时前
mysql中between and怎么用
数据库·mysql