3. 微服务架构设计:服务拆分策略、通信与网关

聊到高可用交易系统,微服务架构几乎是绕不开的坎。我记得刚入行那会儿,大家还在争论「要不要拆」,现在基本已经达成共识——不拆,你根本扛不住业务复杂度。但怎么拆、拆到什么粒度、拆完怎么通信,这里面的坑,我踩过不少。

3.1 服务拆分策略:按业务域 vs 按功能

服务拆分,说白了就是「切蛋糕」。切得好,系统灵活可扩展;切得不好,分布式带来的复杂度会让你痛不欲生。

3.1.1 按业务域拆分(DDD 驱动)

我个人习惯优先按业务域拆。为什么?因为业务域是相对稳定的。比如交易系统,天然就有「订单」、「支付」、「库存」、「用户」这几个核心域。

核心原则:每个微服务只负责一个业务子域,拥有自己的数据库,服务间通过 API 通信。

我在项目中遇到过这样的场景:一开始把订单和支付放在一起,觉得「反正下单就要支付嘛」。结果后来支付逻辑越来越复杂,每次改支付都要连带部署订单服务,风险极高。拆开之后,两个团队各自迭代,互不干扰。

拆分维度 优点 缺点
按业务域 高内聚、低耦合、团队自治 跨服务事务难处理
按功能 技术复用、部署灵活 容易变成「分布式单体」

3.1.2 按功能拆分(技术维度)

按功能拆分,更多是技术层面的考量。比如把「短信发送」、「图片处理」、「日志收集」这些通用能力抽出来。嗯,这里要注意——功能拆分不能替代业务域拆分,它更像是业务域拆分的补充。

我曾经见过一个团队,把「用户服务」拆成了「用户查询服务」和「用户写入服务」。理由是读写分离。结果呢?一个简单的用户注册流程,要跨两个服务协调,反而更慢了。你想想看,过度拆分带来的网络开销,有时候比单机瓶颈更可怕

我的建议:先按业务域拆,再按功能拆。业务域是主维度,功能拆分只针对那些确实需要独立扩展的能力。

3.2 服务间通信:REST、gRPC、消息队列

服务拆完了,怎么让它们说话?通信方式选不对,系统性能直接腰斩。

3.2.1 RESTful API

REST 是最常见的。简单、直观、调试方便。适合对外暴露的接口,或者低频的内部调用。

// 订单服务调用用户服务
GET /api/v1/users/{userId}
// 返回:{ "userId": "123", "name": "张三", "balance": 1000 }

但 REST 有个问题——序列化开销大。JSON 虽然人类可读,但解析慢、体积大。在高频交易场景下,我建议慎用。

3.2.2 gRPC(高性能首选)

gRPC 基于 Protobuf,二进制传输,性能比 REST 高一个数量级。适合服务间高频调用。

// proto 定义
service OrderService {
  rpc CreateOrder (CreateOrderRequest) returns (CreateOrderResponse);
}

message CreateOrderRequest {
  string user_id = 1;
  string product_id = 2;
  int32 quantity = 3;
}

我在项目中遇到过这样的场景:订单服务需要实时查询库存,用 REST 每次要 50ms,换成 gRPC 直接降到 5ms。为什么?二进制协议 + HTTP/2 多路复用,性能差距就是这么大。

注意:gRPC 不适合浏览器端调用。如果前端需要直接调用,还是得用 REST 或 GraphQL。

3.2.3 消息队列(异步解耦)

有些场景不需要同步等待。比如下单成功后发短信、更新积分。这时候用消息队列,削峰填谷、异步解耦

// 订单服务发送消息
orderCreatedEvent = {
  "orderId": "ORD2024001",
  "userId": "U123",
  "amount": 299.00
}
// 发送到 Kafka topic: order_events
producer.send("order_events", orderCreatedEvent);

我曾经踩过一个坑:所有服务都用消息队列通信,结果整个系统变成了「异步黑洞」——你根本不知道一个请求到底处理完了没有。后来我定了个规矩:写操作用消息队列,读操作用 gRPC

通信方式 适用场景 性能 复杂度
REST 对外 API、低频调用 中等
gRPC 内部高频调用
消息队列 异步解耦、削峰填谷

3.3 API 网关设计

服务多了,客户端怎么调用?总不能把几十个服务地址都暴露给前端吧。这时候就需要 API 网关。

API 网关的核心职责:统一入口、路由转发、限流熔断、认证鉴权

// 网关路由配置示例(基于 Spring Cloud Gateway)
spring:
  cloud:
    gateway:
      routes:
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/v1/orders/**
        - id: payment-service
          uri: lb://payment-service
          predicates:
            - Path=/api/v1/payments/**

我个人习惯把网关设计成「薄网关」——只做路由和基础安全,不做业务逻辑。为什么?网关一旦变厚,就成了单点瓶颈。我见过一个项目,网关里塞了各种业务校验,结果网关一挂,全站瘫痪。

避坑指南:我曾经把限流逻辑写在网关层,结果误判了流量,把正常请求也限了。后来我把限流下沉到每个服务内部,网关只做「粗粒度」保护。

3.4 服务注册与发现

服务实例是动态的——扩缩容、故障重启,IP 地址随时在变。怎么让服务找到彼此?注册中心

常见的方案:

  • Consul:支持健康检查、KV 存储,我比较推荐
  • Nacos:阿里出品,支持配置管理和服务发现二合一
  • Eureka:Netflix 老牌,但已进入维护模式
// 服务注册示例(Spring Cloud + Nacos)
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

// application.yml
spring:
  application:
    name: order-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

服务发现的核心机制:心跳检测 + 健康检查。服务启动时向注册中心注册,定期发送心跳。如果心跳超时,注册中心自动摘除该实例。

我记得有一次线上故障,就是因为注册中心挂了,所有服务都找不到对方。后来我做了两件事:注册中心集群部署 + 客户端本地缓存。即使注册中心短暂不可用,服务也能基于缓存继续通信。

重要提醒:不要把注册中心当成「强依赖」。它应该是「辅助工具」,而不是「单点故障」。客户端一定要有本地缓存兜底。

3.5 本章知识体系图

微服务架构设计核心知识体系 微服务架构设计 服务拆分策略 按业务域(DDD驱动) 按功能(技术维度) 服务间通信 RESTful API gRPC(Protobuf) 消息队列(Kafka/RabbitMQ) API网关设计 统一入口、路由转发 限流熔断、认证鉴权 服务注册与发现 Consul / Nacos / Eureka 核心原则:高内聚、低耦合、异步优先、本地缓存兜底

这张图基本概括了本章的核心脉络。你想想看,从服务拆分到通信方式,再到网关和注册中心,每一步都环环相扣。拆得不好,通信再快也没用;通信选错,网关再强也扛不住。

一句话总结:微服务架构不是银弹,但如果你能把握好拆分粒度、选对通信方式、设计好网关和注册中心,它就是你构建高可用交易系统的最强武器。

公众号:蓝海资料掘金营,微信deep3321