3. API 协议选择:RESTful vs gRPC vs GraphQL

说到特征平台的 Serving API,协议选型是个绕不开的坎。我见过不少团队,一上来就拍脑袋选 REST,结果线上延迟扛不住;也有人盲目追 gRPC,结果调试起来想骂娘。今天咱们就把这三个主流协议掰开揉碎,看看它们到底适合什么场景。

一句话总结:没有银弹。特征平台 Serving 的核心矛盾是「低延迟」和「灵活性」之间的取舍。选哪个,取决于你的特征量级、查询模式和团队运维能力。

3.1 RESTful:老牌劲旅,生态最成熟

RESTful API 说白了就是基于 HTTP 的 JSON 交互。你发一个 GET 或 POST,服务端返回 JSON。简单、直观、谁都能看懂。

优点:

  • 调试友好:用 curl、Postman 就能测,浏览器直接看。我当年排查线上问题,全靠 curl 一把梭。
  • 生态丰富:几乎所有语言都有 HTTP 客户端,SDK 随手就来。
  • 防火墙友好:80/443 端口通行无阻,公司网络策略基本不拦。

缺点:

  • 性能瓶颈:JSON 序列化/反序列化开销大,HTTP 头部冗余。单次请求几百字节的元数据,在高 QPS 下就是灾难。
  • 无强类型约束:接口文档和实际返回可能不一致。我曾经被一个字段名拼写错误坑过,线上跑了三天才发现。
  • 长连接支持弱:HTTP/1.1 的 keep-alive 有限,频繁建连消耗大。

我的建议:如果你的特征平台日均 QPS 低于 10 万,团队以业务开发为主,RESTful 是最稳妥的选择。别为了炫技上复杂协议,维护成本会吃掉你的幸福感。

3.2 gRPC:高性能利器,但门槛不低

gRPC 基于 HTTP/2 和 Protobuf,天生为高性能场景设计。它用二进制编码,支持双向流、多路复用,延迟比 REST 低一个数量级。

优点:

  • 极致性能:Protobuf 序列化比 JSON 快 3-10 倍,体积小 60% 以上。我做过压测,同样 1000 个特征,gRPC 延迟只有 REST 的 1/3。
  • 强类型契约:.proto 文件即接口文档,生成代码保证类型安全。再也不用担心字段名写错。
  • 流式支持:适合批量拉取特征或实时推送更新。

缺点:

  • 调试困难:二进制协议肉眼不可读,必须用 grpcurl 或专用工具。线上问题排查比 REST 麻烦得多。
  • 浏览器支持差:需要 gRPC-Web 转译,多一层代理。
  • 负载均衡复杂:gRPC 的长连接特性让传统 L4 负载均衡失效,需要客户端感知或 L7 代理。

避坑指南:我曾经在一个项目中,团队对 gRPC 不熟,结果 .proto 文件频繁变更,客户端和服务端版本对不上,线上报错查了半天。后来我们强制要求 .proto 文件必须经过 Code Review,并且用 buf 工具做兼容性检查。如果你团队没有 C++/Go 背景的 infra 工程师,慎选 gRPC。

3.3 GraphQL:灵活查询,但别滥用

GraphQL 允许客户端精确指定需要的字段,不多不少。这在特征维度爆炸的场景下很有吸引力——你想想看,一个推荐模型可能要几百个特征,但每次只用到其中几十个。

优点:

  • 按需查询:客户端只取需要的特征,减少网络传输。对于移动端或弱网环境特别友好。
  • 强类型 Schema:所有特征都有类型定义,查询前就能校验。
  • 聚合能力:一次查询可以拉取多个实体的特征,减少请求次数。

缺点:

  • 性能不可控:客户端可以发起深层嵌套查询,服务端可能被拖垮。N+1 问题是老生常谈。
  • 缓存困难:REST 的 URL 级缓存策略在 GraphQL 下基本失效,所有 POST 请求都走同一个端点。
  • 学习曲线陡:Resolver、DataLoader、Subscription……团队需要专门学习。

我的经验:GraphQL 更适合「特征探索」场景——分析师或算法工程师想临时看几个特征的值,用 GraphQL 灵活查询很方便。但线上 Serving 路径,我建议还是用 gRPC 或 REST,因为 GraphQL 的查询解析本身就有额外开销,高并发下扛不住。

3.4 核心对比:一张表说清楚

维度 RESTful gRPC GraphQL
序列化格式 JSON(文本) Protobuf(二进制) JSON(文本)
传输协议 HTTP/1.1 或 HTTP/2 HTTP/2 HTTP/1.1 或 HTTP/2
延迟(P99) 5-15ms 1-5ms 10-30ms
吞吐量 中等 高 低-中等
调试难度 低 高 中
浏览器支持 原生 需 gRPC-Web 原生
流式支持 SSE / WebSocket 原生双向流 Subscription
适用场景 中小规模、快速迭代 高并发、低延迟、微服务 灵活查询、特征探索

3.5 选型决策流程

说了这么多,到底怎么选?我一般按这个流程走:

  1. 先看延迟要求:P99 要求 < 5ms?直接上 gRPC。能接受 10ms+?REST 够用。
  2. 再看查询模式:每次请求固定取哪些特征?用 REST 或 gRPC。每次请求特征集变化大?考虑 GraphQL。
  3. 然后看团队能力:团队有 C++/Go 大佬?gRPC 随便玩。全是 Java/Python 业务开发?REST 更稳。
  4. 最后看运维设施:有没有 Service Mesh(如 Istio)?有的话 gRPC 的负载均衡问题就解决了。没有?REST 更省心。

我的推荐组合:线上 Serving 用 gRPC(性能优先),离线调试和特征探索用 GraphQL(灵活优先),管理面(如特征注册、元数据查询)用 REST(生态成熟)。三个协议各司其职,不冲突。

3.6 架构示意图

下面这张图展示了三种协议在特征平台中的典型位置:

特征平台 Serving API 协议选型架构 客户端层 在线服务 / 模型推理 / 特征探索工具 API 协议层 RESTful (JSON) gRPC (Protobuf) GraphQL 特征服务层 特征聚合 / 实时计算 / 缓存加速 特征存储 (Redis / KV Store / OLAP) 线上路径推荐 gRPC,调试路径推荐 REST,探索路径推荐 GraphQL

3.7 代码示例:三种协议的调用对比

假设我们要查询用户 ID 为 12345 的 3 个特征(age、city、score),看看三种协议分别怎么写:

RESTful 调用:

GET /api/v1/features?user_id=12345&feature_names=age,city,score
Host: feature-platform.example.com

// 响应
{
  "user_id": 12345,
  "features": {
    "age": 28,
    "city": "Beijing",
    "score": 0.95
  },
  "timestamp": 1712345678
}

gRPC 调用(Protobuf 定义):

// feature.proto
service FeatureService {
  rpc GetFeatures(FeatureRequest) returns (FeatureResponse);
}

message FeatureRequest {
  string user_id = 1;
  repeated string feature_names = 2;
}

message FeatureResponse {
  string user_id = 1;
  map<string, FeatureValue> features = 2;
  int64 timestamp = 3;
}

message FeatureValue {
  oneof value {
    int64 int_val = 1;
    double double_val = 2;
    string string_val = 3;
  }
}

GraphQL 调用:

query {
  user(id: "12345") {
    age
    city
    score
  }
}

// 响应
{
  "data": {
    "user": {
      "age": 28,
      "city": "Beijing",
      "score": 0.95
    }
  }
}

小技巧:如果你决定用 gRPC,建议同时暴露一个 RESTful 的「健康检查」端点(比如 /healthz),方便 K8s 探活和运维人员手动排查。我见过太多 gRPC 服务挂了,运维连 curl 都测不了,只能干瞪眼。

3.8 我的最终建议

选协议这事儿,没有标准答案。但我可以给你一个「最小后悔」策略:

  • 起步阶段:先用 RESTful 快速上线,把业务跑通。别在协议上纠结太久。
  • 规模扩张:当 QPS 超过 5 万,延迟开始报警时,把核心 Serving 路径迁移到 gRPC。迁移成本不高,因为业务逻辑可以复用。
  • 灵活需求:如果算法团队频繁要求「帮我查一下这个新特征的值」,给他们搭一个 GraphQL 网关,让他们自己玩去。

记住,协议只是工具,不是目的。你的目标是让特征 Serving 又快又稳,而不是证明自己会用某个协议。