引言
当单体应用逐渐膨胀到数万行代码,每次部署都像在走钢丝——改一行代码可能引发全局故障,CI/CD 流水线越来越慢,团队协作也变得混乱。这时,微服务架构成为必然选择。但拆分不是一拍脑袋的事,错误的拆分会导致分布式灾难。本文将分享一套经过验证的拆分策略,并手把手带你完成一次真实的微服务拆分实战。
第一步:识别业务边界——领域驱动设计(DDD)的应用
微服务拆分的核心是按业务领域划分服务,而不是按技术层(如Controller、Service、DAO)。DDD 中的限界上下文(Bounded Context)是理想的服务边界。
实战案例:电商系统
假设我们有一个电商单体应用,包含用户、商品、订单、支付等功能。我们通过事件风暴(Event Storming)识别出以下限界上下文:
- 用户上下文:注册、登录、用户信息管理
- 商品上下文:商品CRUD、库存管理
- 订单上下文:下单、订单状态流转
- 支付上下文:支付处理、退款
每个上下文对应一个微服务。
第二步:数据库拆分策略
拆分服务时,数据库也必须拆分。基本原则是:每个微服务独享自己的数据库,避免跨服务直接访问数据库。
拆分方式:
- 按表拆分:将不同服务的表分到不同数据库实例。
- 按字段拆分:对于大表,可以按服务所需字段拆分(如用户基础信息库和用户扩展信息库)。
代码示例:Spring Boot + JPA 实现用户服务
// UserService 项目结构
// src/main/java/com/example/user/
// - entity/User.java
// - repository/UserRepository.java
// - service/UserService.java
// - controller/UserController.java
// User.java
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
// getters and setters
}
// UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
// UserService.java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User createUser(User user) {
return userRepository.save(user);
}
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
}
}
// UserController.java
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUserById(id);
}
}
💡 注意:数据库拆分后,原本的单库事务变成分布式事务。建议尽量通过最终一致性(如事件驱动)避免强分布式事务。
第三步:服务间通信——同步 vs 异步
微服务间通信主要有两种方式:
- 同步:REST、gRPC,适合实时查询。
- 异步:消息队列(如RabbitMQ、Kafka),适合事件通知、解耦。
实战:订单服务调用用户服务(同步)
// OrderService 中通过 RestTemplate 调用 UserService
@Service
public class OrderService {
@Autowired
private RestTemplate restTemplate;
public Order createOrder(Order order) {
// 获取用户信息
String url = "http://user-service/api/users/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
if (user == null) {
throw new RuntimeException("User not found");
}
// 继续处理订单...
return orderRepository.save(order);
}
}
实战:订单完成后发送消息(异步)
// OrderService 中发送事件
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
public Order completeOrder(Long orderId) {
Order order = orderRepository.findById(orderId).orElseThrow();
order.setStatus("COMPLETED");
orderRepository.save(order);
// 发送订单完成事件
rabbitTemplate.convertAndSend("order.exchange", "order.completed", order);
return order;
}
}
// 支付服务监听事件
@Component
public class PaymentEventListener {
@RabbitListener(queues = "order.completed.queue")
public void handleOrderCompleted(Order order) {
// 处理支付逻辑
System.out.println("Processing payment for order: " + order.getId());
}
}
💡 注意:同步调用会增加服务间的耦合,建议只在需要实时数据的场景使用。异步消息可以提高系统的弹性,但需要处理消息重复、顺序等问题。
第四步:API 网关与服务发现
微服务架构中,客户端不应直接调用各个服务,而应通过 API 网关统一入口。同时,服务实例动态变化,需要服务发现机制。
实战:Spring Cloud Gateway + Eureka
# application.yml for API Gateway
server:
port: 8080
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
// 启动类
@SpringBootApplication
@EnableEurekaClient
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
第五步:部署与监控
微服务通常使用容器化部署(Docker + Kubernetes)。每个服务独立部署,独立扩展。
Dockerfile 示例
FROM openjdk:11-jre-slim
COPY target/user-service-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8081
ENTRYPOINT ["java", "-jar", "/app.jar"]
监控:使用 Spring Boot Actuator + Prometheus
<!-- pom.xml 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
配置暴露指标:
management:
endpoints:
web:
exposure:
include: health, prometheus
常见坑与最佳实践
坑1:拆分过细
刚拆分时容易走极端,把每个小功能都做成服务,导致服务数量爆炸,运维成本剧增。建议:初期按业务领域拆分,每个服务至少包含3-5个相关功能。
坑2:忽视数据一致性
拆分后,跨服务的数据一致性容易出问题。建议:优先使用最终一致性,通过事件溯源或 Saga 模式处理。
坑3:共享库泛滥
多个服务共享同一个数据库或同一个代码库,导致耦合。建议:每个服务有自己的数据库,通过 API 通信,避免直接依赖。
总结
微服务拆分不是一蹴而就的,需要逐步演进。本文从 DDD 识别边界、数据库拆分、服务间通信、API 网关到部署监控,提供了一套完整的实战指南。关键要点:
- 按业务领域拆分,而非技术层
- 每个服务独享数据库
- 优先异步通信,降低耦合
- 使用 API 网关统一入口
- 容器化部署,独立扩缩容
下一步,你可以深入研究分布式事务(Saga 模式)、服务网格(Istio)等高级主题。
标签
微服务、Spring Boot、DDD、分布式架构、API网关、容器化
分类
后端开发、前端开发