3. 微服务架构设计:服务拆分策略、通信与网关
聊到高可用交易系统,微服务架构几乎是绕不开的坎。我记得刚入行那会儿,大家还在争论「要不要拆」,现在基本已经达成共识——不拆,你根本扛不住业务复杂度。但怎么拆、拆到什么粒度、拆完怎么通信,这里面的坑,我踩过不少。
3.1 服务拆分策略:按业务域 vs 按功能
服务拆分,说白了就是「切蛋糕」。切得好,系统灵活可扩展;切得不好,分布式带来的复杂度会让你痛不欲生。
3.1.1 按业务域拆分(DDD 驱动)
我个人习惯优先按业务域拆。为什么?因为业务域是相对稳定的。比如交易系统,天然就有「订单」、「支付」、「库存」、「用户」这几个核心域。
我在项目中遇到过这样的场景:一开始把订单和支付放在一起,觉得「反正下单就要支付嘛」。结果后来支付逻辑越来越复杂,每次改支付都要连带部署订单服务,风险极高。拆开之后,两个团队各自迭代,互不干扰。
| 拆分维度 | 优点 | 缺点 |
|---|---|---|
| 按业务域 | 高内聚、低耦合、团队自治 | 跨服务事务难处理 |
| 按功能 | 技术复用、部署灵活 | 容易变成「分布式单体」 |
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 多路复用,性能差距就是这么大。
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 本章知识体系图
这张图基本概括了本章的核心脉络。你想想看,从服务拆分到通信方式,再到网关和注册中心,每一步都环环相扣。拆得不好,通信再快也没用;通信选错,网关再强也扛不住。
公众号:蓝海资料掘金营,微信deep3321