微服务架构模式:服务拆分、通信与治理

微服务架构,说白了就是把一个大单体应用拆成一堆小服务。每个服务独立部署、独立运行,各管各的一亩三分地。我最早接触微服务是在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微服务架构的核心流程。你看一眼就明白了:

Spring Cloud 微服务架构核心流程 客户端(App/浏览器) Spring Cloud Gateway(网关) 订单服务 库存服务 支付服务 Nacos(注册中心/配置中心) 数据库集群 说明:客户端 → 网关 → 服务 → 注册中心 → 数据库

这张图展示了请求的完整流程:客户端先打到网关,网关根据路由规则转发到具体服务。服务之间可以互相调用,同时所有服务都注册到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