从单体到微服务迁移
从单体到微服务迁移
迁移策略概述
将单体应用迁移到微服务架构是一个渐进的过程,需要仔细规划和执行。以下是几种常见的迁移策略:
绞杀者模式(Strangler Fig Pattern)
绞杀者模式是通过逐步将功能从单体应用中剥离,用新的微服务替代,直到单体应用完全被新系统取代。
实现步骤
- 识别功能边界:分析单体应用,识别可以独立拆分的功能模块
- 创建新服务:为每个识别的功能创建新的微服务
- 路由请求:使用反向代理或API网关将请求路由到新服务或单体应用
- 迁移数据:逐步将数据从单体数据库迁移到新服务的数据库
- 移除旧代码:当所有功能都迁移完成后,移除单体应用中的相关代码
// 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. 识别要替换的组件:用户服务
// 原始的用户服务实现
@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();
}
}
迁移检查清单
- 服务拆分:确认服务边界清晰,职责单一
- API设计:定义清晰、稳定的API契约
- 数据管理:确保每个服务有自己的数据存储
- 服务发现:配置服务注册和发现机制
- 负载均衡:实现客户端或服务端负载均衡
- 熔断器:实现熔断器模式,防止级联故障
- 分布式追踪:实现分布式追踪,便于调试
- 集中化日志:实现集中化日志管理
- 监控告警:建立监控和告警体系
- 自动化部署:建立自动化CI/CD流水线