← 返回首页
🔧

从单体到微服务迁移

📂 architecture ⏱ 4 min 761 words

从单体到微服务迁移

迁移策略概述

将单体应用迁移到微服务架构是一个渐进的过程,需要仔细规划和执行。以下是几种常见的迁移策略:

绞杀者模式(Strangler Fig Pattern)

绞杀者模式是通过逐步将功能从单体应用中剥离,用新的微服务替代,直到单体应用完全被新系统取代。

实现步骤

  1. 识别功能边界:分析单体应用,识别可以独立拆分的功能模块
  2. 创建新服务:为每个识别的功能创建新的微服务
  3. 路由请求:使用反向代理或API网关将请求路由到新服务或单体应用
  4. 迁移数据:逐步将数据从单体数据库迁移到新服务的数据库
  5. 移除旧代码:当所有功能都迁移完成后,移除单体应用中的相关代码
// API网关配置 - 路由到新服务或单体应用
@Configuration
public class GatewayConfig {
    
    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder) {
        return builder.routes()
            // 新的用户服务
            .route("user-service", r -> r
                .path("/api/users/**")
                .filters(f -> f.rewritePath("/api/users/(?<segment>.*)", "/api/users/${segment}"))
                .uri("lb://user-service"))
            
            // 订单功能仍在单体应用中
            .route("monolith-orders", r -> r
                .path("/api/orders/**")
                .filters(f -> f.rewritePath("/api/orders/(?<segment>.*)", "/api/orders/${segment}"))
                .uri("lb://monolith"))
            
            // 新的订单服务(迁移完成后启用)
            .route("order-service", r -> r
                .path("/api/v2/orders/**")
                .filters(f -> f.rewritePath("/api/v2/orders/(?<segment>.*)", "/api/orders/${segment}"))
                .uri("lb://order-service"))
            
            .build();
    }
}

// 功能开关 - 控制使用新服务还是单体应用
@Component
public class FeatureToggle {
    
    @Value("${feature.toggle.user-service:false}")
    private boolean useUserService;
    
    @Value("${feature.toggle.order-service:false}")
    private boolean useOrderService;
    
    public boolean shouldUseUserService() {
        return useUserService;
    }
    
    public boolean shouldUseOrderService() {
        return useOrderService;
    }
}

// 控制器中的功能切换
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    private final UserService userService;
    private final UserClient userClient;
    private final FeatureToggle featureToggle;
    
    @GetMapping("/{id}")
    public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
        if (featureToggle.shouldUseUserService()) {
            // 使用新的用户服务
            return ResponseEntity.ok(userClient.getUser(id));
        } else {
            // 使用单体应用中的用户服务
            return ResponseEntity.ok(userService.getUser(id));
        }
    }
}

分支抽象模式(Branch by Abstraction)

分支抽象模式通过在单体应用内部引入抽象层,使得新旧实现可以共存,然后逐步切换到新的实现。

实现步骤

  1. 识别要替换的组件:确定要替换的单体应用组件
  2. 创建抽象接口:为该组件定义抽象接口
  3. 实现新版本:基于新架构实现该接口
  4. 逐步切换:通过配置或特性开关逐步切换到新实现
  5. 移除旧实现:当新实现稳定后,移除旧的实现
// 1. 识别要替换的组件:用户服务
// 原始的用户服务实现
@Service
public class MonolithUserService {
    
    private final UserRepository userRepository;
    
    public User getUser(Long id) {
        return userRepository.findById(id)
                .orElseThrow(() -> new UserNotFoundException(id));
    }
    
    public User createUser(CreateUserRequest request) {
        User user = new User(request.getName(), request.getEmail());
        return userRepository.save(user);
    }
}

// 2. 创建抽象接口
public interface UserService {
    User getUser(Long id);
    User createUser(CreateUserRequest request);
}

// 3. 实现新版本
@Service
@ConditionalOnProperty(name = "user.service.impl", havingValue = "microservice")
public class MicroserviceUserService implements UserService {
    
    private final UserClient userClient;
    
    @Override
    public User getUser(Long id) {
        return userClient.getUser(id);
    }
    
    @Override
    public User createUser(CreateUserRequest request) {
        return userClient.createUser(request);
    }
}

// 旧的单体实现
@Service
@ConditionalOnProperty(name = "user.service.impl", havingValue = "monolith", matchIfMissing = true)
public class MonolithUserServiceAdapter implements UserService {
    
    private final MonolithUserService monolithUserService;
    
    @Override
    public User getUser(Long id) {
        return monolithUserService.getUser(id);
    }
    
    @Override
    public User createUser(CreateUserRequest request) {
        return monolithUserService.createUser(request);
    }
}

// 4. 配置切换
# application.yml
user:
  service:
    impl: monolith  # 切换到 microservice 启用新服务

数据库迁移策略

每个服务独立数据库

-- 创建独立的用户服务数据库
CREATE DATABASE user_service_db;

-- 创建独立的订单服务数据库
CREATE DATABASE order_service_db;

-- 用户表
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- 订单表(只存储用户ID,不存储用户详细信息)
CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,  -- 引用用户服务的用户ID
    status VARCHAR(50) NOT NULL,
    total_amount DECIMAL(10, 2),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

数据同步机制

// 使用CDC(Change Data Capture)同步数据
@Component
public class UserDataSynchronizer {
    
    private final UserEventPublisher eventPublisher;
    
    @PostConstruct
    public void init() {
        // 监听用户数据变化
        // 实际实现可能使用Debezium、Canal等CDC工具
    }
    
    public void onUserCreated(User user) {
        // 发布用户创建事件
        UserCreatedEvent event = new UserCreatedEvent(
            user.getId(),
            user.getName(),
            user.getEmail()
        );
        eventPublisher.publish(event);
    }
    
    public void onUserUpdated(User user) {
        // 发布用户更新事件
        UserUpdatedEvent event = new UserUpdatedEvent(
            user.getId(),
            user.getName(),
            user.getEmail()
        );
        eventPublisher.publish(event);
    }
}

// 订单服务消费用户事件
@Component
public class UserEventConsumer {
    
    private final UserCacheRepository userCacheRepository;
    
    @KafkaListener(topics = "user-events")
    public void handleUserEvent(String message) {
        UserEvent event = objectMapper.readValue(message, UserEvent.class);
        
        switch (event.getType()) {
            case "USER_CREATED":
                handleUserCreated((UserCreatedEvent) event);
                break;
            case "USER_UPDATED":
                handleUserUpdated((UserUpdatedEvent) event);
                break;
        }
    }
    
    private void handleUserCreated(UserCreatedEvent event) {
        // 将用户信息缓存到本地
        UserCache userCache = new UserCache(
            event.getUserId(),
            event.getName(),
            event.getEmail()
        );
        userCacheRepository.save(userCache);
    }
}

逐步迁移的步骤

步骤1:准备阶段

// 1. 分析单体应用,识别服务边界
// 使用领域驱动设计进行限界上下文分析
@Component
public class ServiceBoundaryAnalyzer {
    
    public List<ServiceBoundary> analyze(MonolithApplication app) {
        List<ServiceBoundary> boundaries = new ArrayList<>();
        
        // 分析用户相关功能
        boundaries.add(analyzeUserBoundary(app));
        
        // 分析订单相关功能
        boundaries.add(analyzeOrderBoundary(app));
        
        // 分析商品相关功能
        boundaries.add(analyzeProductBoundary(app));
        
        return boundaries;
    }
    
    private ServiceBoundary analyzeUserBoundary(MonolithApplication app) {
        return new ServiceBoundary(
            "user-service",
            Arrays.asList("User", "Role", "Permission"),
            Arrays.asList("UserService", "RoleService"),
            Arrays.asList("/api/users/**", "/api/roles/**")
        );
    }
}

步骤2:创建新服务

// 创建新的用户服务
@SpringBootApplication
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

// 定义API
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    private final UserService userService;
    
    @GetMapping("/{id}")
    public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
        return ResponseEntity.ok(userService.getUser(id));
    }
    
    @PostMapping
    public ResponseEntity<UserDto> createUser(@RequestBody CreateUserRequest request) {
        return ResponseEntity.ok(userService.createUser(request));
    }
}

步骤3:设置路由

// 配置API网关路由
@Configuration
public class GatewayConfig {
    
    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("user-service", r -> r
                .path("/api/users/**")
                .filters(f -> f.rewritePath("/api/users/(?<segment>.*)", "/api/users/${segment}"))
                .uri("lb://user-service"))
            .build();
    }
}

步骤4:迁移数据

// 数据迁移脚本
@Component
public class DataMigration {
    
    private final JdbcTemplate monolithJdbcTemplate;
    private final UserServiceClient userClient;
    
    @Transactional
    public void migrateUsers() {
        // 从单体数据库读取用户数据
        List<User> users = monolithJdbcTemplate.query(
            "SELECT * FROM users",
            (rs, rowNum) -> mapToUser(rs)
        );
        
        // 迁移到新的用户服务
        for (User user : users) {
            CreateUserRequest request = new CreateUserRequest(
                user.getName(),
                user.getEmail()
            );
            userClient.createUser(request);
        }
    }
}

步骤5:切换流量

// 使用特性开关控制流量
@Component
public class TrafficSwitch {
    
    @Value("${traffic.switch.user-service:0}")
    private int userServicePercentage;
    
    public boolean shouldRouteToNewService() {
        // 使用百分比控制流量切换
        return Random.nextInt(100) < userServicePercentage;
    }
}

// 动态配置更新
@RefreshScope
@RestController
@RequestMapping("/admin")
public class AdminController {
    
    @PostMapping("/traffic-switch")
    public ResponseEntity<Void> updateTrafficSwitch(
            @RequestBody TrafficSwitchConfig config) {
        // 更新配置,控制流量切换
        return ResponseEntity.ok().build();
    }
}

迁移检查清单

  1. 服务拆分:确认服务边界清晰,职责单一
  2. API设计:定义清晰、稳定的API契约
  3. 数据管理:确保每个服务有自己的数据存储
  4. 服务发现:配置服务注册和发现机制
  5. 负载均衡:实现客户端或服务端负载均衡
  6. 熔断器:实现熔断器模式,防止级联故障
  7. 分布式追踪:实现分布式追踪,便于调试
  8. 集中化日志:实现集中化日志管理
  9. 监控告警:建立监控和告警体系
  10. 自动化部署:建立自动化CI/CD流水线