第十三章:API网关与服务治理

说到API网关和服务治理,我脑子里第一个蹦出来的画面,是几年前一次线上事故。

那天下午,我们的做市系统突然响应变慢,交易员在群里疯狂@我。查了半天才发现,是某个底层服务挂了,结果所有请求都堵在网关层,最后把整个系统拖垮了。嗯,从那以后,我对API网关和服务治理的重视程度,直接拉满了。

13.1 RESTful API设计:别小看接口规范

RESTful API,说白了就是一套约定。你按规矩来,大家都省心。我在项目中见过太多"自由发挥"的接口设计,结果维护成本高得吓人。

核心原则:

  • 资源路径用名词,别用动词。比如 /orders 而不是 /getOrders
  • HTTP动词对应操作:GET查、POST增、PUT全量改、PATCH部分改、DELETE删
  • 状态码要准确:200成功、201创建、400参数错误、404不存在、500服务器挂了

举个例子,我们做市系统的订单接口:

# 好的设计
GET    /api/v1/orders          # 查询订单列表
POST   /api/v1/orders          # 创建新订单
GET    /api/v1/orders/{id}     # 查询单个订单
PUT    /api/v1/orders/{id}     # 全量更新订单
PATCH  /api/v1/orders/{id}     # 部分更新订单
DELETE /api/v1/orders/{id}     # 删除订单

# 坏的设计(我见过有人这么写)
GET    /api/v1/getOrderList
POST   /api/v1/createOrder
POST   /api/v1/deleteOrder?id=123

避坑指南:我曾经接手过一个项目,接口路径里混着中英文和下划线,比如 /get_订单List。嗯,你想想看,这种接口谁敢用?所以从一开始就定好规范,比事后重构省心一百倍。

13.2 gRPC通信:高性能的另一种选择

RESTful API虽然好,但有些场景它确实不够快。比如做市系统里,行情数据推送、订单状态同步,这些对延迟极度敏感。这时候,gRPC就派上用场了。

gRPC基于HTTP/2,用Protobuf做序列化。我个人的习惯是:对外暴露的接口用RESTful,内部服务间通信用gRPC。这样既保证了外部兼容性,又兼顾了内部性能。

一个典型的gRPC服务定义:

syntax = "proto3";

package marketmaker;

service OrderService {
  rpc CreateOrder (CreateOrderRequest) returns (OrderResponse);
  rpc GetOrder (GetOrderRequest) returns (OrderResponse);
  rpc SubscribeOrderStatus (SubscribeRequest) returns (stream OrderStatus);
}

message CreateOrderRequest {
  string symbol = 1;
  double price = 2;
  double quantity = 3;
  string side = 4;  // buy or sell
}

message OrderResponse {
  string order_id = 1;
  string status = 2;
  double filled_quantity = 3;
}

为什么选gRPC?

  • 序列化快:Protobuf比JSON小3-10倍
  • 支持双向流:适合行情推送这种场景
  • 强类型:接口定义即文档,不会出现"这个字段是啥意思"的尴尬

我在项目中遇到过一个问题:gRPC的负载均衡不像HTTP那么简单。因为gRPC长连接的特性,普通的轮询负载均衡反而会导致连接分布不均。后来我们用了客户端负载均衡,配合服务发现,才解决了这个问题。

13.3 服务注册发现:让服务找到彼此

微服务架构里,服务实例的IP和端口是动态变化的。你不能把地址写死在配置文件里,对吧?服务注册发现就是解决这个问题的。

常见的方案有Consul、Etcd、Zookeeper。我个人偏爱Consul,因为它自带健康检查和DNS接口,用起来比较顺手。

服务注册发现的工作流程:

  1. 服务启动时,向注册中心注册自己的地址和端口
  2. 注册中心定期检查服务健康状态
  3. 客户端从注册中心获取可用服务列表
  4. 客户端根据策略选择一个实例发起调用

注意:我曾经踩过一个坑——服务注册成功了,但健康检查配置得太宽松。结果一个服务已经半死不活了,注册中心还认为它活着,导致大量请求超时。健康检查的间隔和超时时间,一定要根据业务场景仔细调。

下面这张图展示了服务注册发现的核心逻辑:

注册中心 Consul / Etcd 服务提供者 A 服务提供者 B 服务消费者 ① 注册 ① 注册 ② 发现 ③ 调用 ① 服务提供者启动时向注册中心注册 ② 服务消费者从注册中心获取可用服务列表 ③ 服务消费者根据策略调用服务提供者

13.4 限流熔断:保护系统不被冲垮

做市系统最怕什么?怕流量洪峰。行情剧烈波动时,订单量可能是平时的几十倍。如果没有限流熔断,系统分分钟被冲垮。

限流和熔断是两个概念,但经常一起用:

  • 限流:控制请求速率,防止系统过载。比如每秒最多处理1000个订单请求。
  • 熔断:当某个服务故障率过高时,暂时切断对该服务的调用,避免故障扩散。

常用的限流算法:

算法 原理 适用场景
令牌桶 以固定速率往桶里放令牌,请求来了拿令牌 允许突发流量,适合做市系统
漏桶 请求先进桶,以固定速率流出 平滑流量,适合消息队列
滑动窗口 统计时间窗口内的请求数 实现简单,精度可控

我个人习惯用令牌桶算法。为什么呢?因为做市系统有时候需要处理突发订单,比如大单拆分后同时涌入。令牌桶允许一定程度的突发,正好满足这个需求。

熔断器的状态机:

状态:CLOSED → OPEN → HALF_OPEN → CLOSED

CLOSED(关闭):正常状态,请求正常通过
OPEN(打开):故障率超阈值,直接拒绝请求
HALF_OPEN(半开):过一段时间后,放少量请求试探

避坑指南:我曾经把熔断阈值设得太低,结果一次小波动就触发了熔断,导致大量正常请求被拒绝。后来我学乖了——阈值要根据历史数据动态调整,不能拍脑袋定死。

13.5 网关层的统一处理

API网关不只是做路由转发。它还可以统一处理很多事情:

  • 认证鉴权:在网关层统一校验Token,不用每个服务都写一遍
  • 日志记录:记录所有请求的入参、出参、耗时
  • 协议转换:对外暴露RESTful,内部转成gRPC
  • 灰度发布:根据Header或Cookie,把请求路由到不同版本的服务

我们用的网关是自研的,基于Netty做了一层封装。为什么不用现成的?因为做市系统对延迟要求太高,现成网关的某些功能我们用不上,反而增加了延迟。

我的建议:网关层尽量做"薄"一点。只做路由、认证、限流这些通用功能。业务逻辑不要放在网关里,否则网关会变得越来越重,最后变成"上帝服务",谁都依赖它,谁都改不动它。

好了,API网关和服务治理这块,核心就是这些。从接口设计到通信协议,从服务发现到限流熔断,每一环都关系到系统的稳定性和性能。做市系统对稳定性的要求极高,这些治理手段不是锦上添花,而是保命用的。


交易系统化学习资料 微信Strategy888888