微服务架构模式:服务拆分、通信与治理
微服务架构,说白了就是把一个大单体应用拆成一堆小服务。每个服务独立部署、独立运行,各管各的一亩三分地。我最早接触微服务是在2016年,当时公司要把一个老旧的电商系统重构。那会儿我还在想:拆这么多服务,不是自找麻烦吗?后来真做了才发现——麻烦是麻烦,但好处确实多。
一、服务拆分:到底该怎么切?
服务拆分是微服务的第一步,也是最容易翻车的一步。我见过不少团队,一上来就按功能模块切,结果切完发现服务之间互相调来调去,比原来还乱。
我个人习惯遵循两个原则:
- 业务边界原则:按业务领域拆分,而不是按技术层拆分。比如订单服务、库存服务、支付服务,每个服务都有自己的数据库。
- 数据独立性原则:每个服务拥有自己的数据存储,绝不共享数据库。你想想看,如果两个服务共用一个库,那跟没拆有什么区别?
核心要点:服务拆分的粒度不是越细越好。我见过一个项目拆了50多个服务,结果每次上线要协调十几个团队,反而拖慢了交付速度。合适的粒度是:一个服务可以由一个5-7人的小团队独立维护。
举个例子,一个典型的电商系统可以拆成这样:
// 服务拆分示例(Java + Spring Boot)
// 用户服务
@SpringBootApplication
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
// 订单服务
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
// 库存服务
@SpringBootApplication
public class InventoryServiceApplication {
public static void main(String[] args) {
SpringApplication.run(InventoryServiceApplication.class, args);
}
}
二、服务通信:服务之间怎么说话?
服务拆开了,它们之间怎么通信?这是个大问题。我在项目中遇到过两种主流方式:
2.1 同步通信:REST / gRPC
同步通信最直观,A服务直接调B服务的接口。Spring Cloud里用Feign或者RestTemplate就能搞定。
// 使用Feign进行服务间调用
@FeignClient(name = "inventory-service")
public interface InventoryClient {
@GetMapping("/api/inventory/{productId}")
Inventory getInventory(@PathVariable("productId") Long productId);
}
// 在订单服务中调用
@Service
public class OrderService {
@Autowired
private InventoryClient inventoryClient;
public Order createOrder(OrderRequest request) {
// 同步调用库存服务
Inventory inventory = inventoryClient.getInventory(request.getProductId());
if (inventory.getStock() < request.getQuantity()) {
throw new RuntimeException("库存不足");
}
// 创建订单逻辑...
}
}
注意:同步调用会形成链式依赖。我曾经遇到过一个下单接口,背后调了6个服务,结果其中一个服务慢了几百毫秒,整个接口就超时了。解决方案是:对关键路径做超时控制和熔断保护。
2.2 异步通信:消息队列
异步通信用消息中间件,比如RabbitMQ、Kafka。服务之间通过消息解耦,你发你的,我收我的,互不干扰。
// 发送消息(订单服务)
@Service
public class OrderEventPublisher {
@Autowired
private RabbitTemplate rabbitTemplate;
public void publishOrderCreated(Order order) {
rabbitTemplate.convertAndSend("order.exchange", "order.created", order);
}
}
// 接收消息(库存服务)
@Component
public class InventoryEventConsumer {
@RabbitListener(queues = "inventory.queue")
public void handleOrderCreated(Order order) {
// 扣减库存逻辑
inventoryService.deductStock(order.getProductId(), order.getQuantity());
}
}
我个人更推荐异步通信。为什么呢?因为异步能扛住突发流量。我记得有一次双十一,订单量瞬间暴涨,同步调用直接打垮了库存服务。换成消息队列后,库存服务慢慢消费,系统反而稳住了。
三、服务治理:怎么管好这群服务?
服务多了,治理就成了大问题。你想想看,几十个服务,谁调谁、谁挂了、谁慢了,光靠人工根本管不过来。Spring Cloud全家桶提供了完整的治理方案。
| 组件 | 作用 | 我常用的实现 |
|---|---|---|
| 服务注册与发现 | 服务启动时注册,调用方通过注册中心找到目标服务 | Nacos / Eureka |
| 配置中心 | 集中管理配置,支持动态刷新 | Nacos Config / Apollo |
| 负载均衡 | 将请求分发到多个实例 | Ribbon / LoadBalancer |
| 熔断降级 | 防止服务雪崩,失败时快速返回 | Sentinel / Hystrix |
| 网关 | 统一入口,路由、鉴权、限流 | Spring Cloud Gateway |
| 链路追踪 | 追踪一次请求经过的所有服务 | Sleuth + Zipkin |
避坑指南:我曾经在配置中心上吃过亏。当时用Spring Cloud Config,每次改配置都要重启服务。后来换成Nacos Config,支持配置动态刷新,改完配置秒级生效,再也不用半夜爬起来重启了。
四、Spring Cloud全家桶实战
下面这张图是我总结的Spring Cloud微服务架构的核心流程。你看一眼就明白了:
这张图展示了请求的完整流程:客户端先打到网关,网关根据路由规则转发到具体服务。服务之间可以互相调用,同时所有服务都注册到Nacos。配置也统一从Nacos拉取。
五、一个完整的微服务示例
我拿一个下单场景来演示。假设用户下单,需要同时调用订单服务和库存服务:
// 1. 网关配置(application.yml)
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
- id: inventory-service
uri: lb://inventory-service
predicates:
- Path=/api/inventory/**
// 2. 订单服务调用库存服务(带熔断)
@Service
public class OrderService {
@Autowired
private InventoryClient inventoryClient;
@SentinelResource(value = "createOrder", fallback = "createOrderFallback")
public Order createOrder(OrderRequest request) {
// 调用库存服务
Inventory inventory = inventoryClient.getInventory(request.getProductId());
if (inventory.getStock() < request.getQuantity()) {
throw new BusinessException("库存不足");
}
// 扣减库存
inventoryClient.deductStock(request.getProductId(), request.getQuantity());
// 创建订单
return orderRepository.save(new Order(request));
}
// 熔断降级方法
public Order createOrderFallback(OrderRequest request, Throwable t) {
log.error("创建订单失败,进入降级逻辑", t);
// 返回兜底数据或提示
throw new RuntimeException("服务繁忙,请稍后重试");
}
}
// 3. 配置中心动态刷新
@RefreshScope
@RestController
public class ConfigController {
@Value("${order.timeout:5000}")
private int orderTimeout;
@GetMapping("/config/timeout")
public String getTimeout() {
return "当前订单超时时间:" + orderTimeout + "ms";
}
}
关键点:熔断降级一定要做。我见过太多因为一个服务挂了,导致整个系统雪崩的案例。Sentinel的熔断策略我一般设置:5秒内错误率超过50%,就熔断10秒。这样既能快速失败,又能给下游服务恢复的时间。
六、微服务架构的坑与建议
做了这么多年微服务,我踩过的坑不少。这里列几个最常见的:
- 分布式事务:别想着用两阶段提交,性能太差。我一般用最终一致性方案,配合消息队列和本地消息表。
- 服务间调用超时:每个调用都要设超时时间,默认值往往太长。我习惯设成3秒,超过就降级。
- 日志分散:服务多了,日志散落在各个机器上。必须上ELK或者SkyWalking,不然排查问题能查到崩溃。
- 配置管理:别把配置写死在代码里。用配置中心统一管理,环境隔离要做好。
我的建议:刚开始做微服务,别追求大而全。先拆2-3个核心服务跑起来,把注册中心、网关、配置中心搭好。等团队熟悉了,再逐步拆分其他模块。一口吃不成胖子,微服务也是。
微服务架构不是银弹,但它确实解决了单体应用的一些痛点。服务拆分、通信、治理这三件事做好了,系统就能稳定运行。Spring Cloud全家桶提供了完整的工具链,但工具只是辅助,关键还是设计思路。
公众号:蓝海资料掘金营,微信deep3321