API 设计演进:REST、GraphQL、gRPC 混合架构实战

2026-05-26 22:07 API 设计演进:REST、GraphQL、gRPC 混合架构实战已关闭评论

API 设计演进:REST、GraphQL、gRPC 混合架构实战

过去三年我把同一个 API 网关重构了三回。从纯 REST 到 REST + GraphQL 混搭,再到最终引入 gRPC 形成三协议混合架构。如果你正在纠结"选哪个"——成年人不做选择,全都要。但得搞清楚每种协议该放在哪一层。

背景:为什么搞混合架构

2023 年初我接手了一个中台项目,核心业务是内容管理 + 实时协作 + 数据看板。初期选型很单纯:REST 一把梭。

到第 6 个月问题开始冒头:

  • 移动端首页需要聚合 7 个接口的数据,首屏加载 3s+
  • 协作模块需要实时推送,REST 轮询把服务器打崩过一次
  • 数据看板每次查询返回大量冗余字段,前端解析耗时占了一半

这不是 REST 的错,是我用错了场景。于是开始分层改造。

第一层:REST 做资源管理,不动摇

REST 最擅长的是资源 CRUD,这部分我完全没动。用户管理、内容条目、配置信息这些标准资源操作,REST 语义清晰、工具链成熟、调试方便,换掉没有任何好处。

# FastAPI 示例:资源型接口保持 REST
@router.get("/articles/{article_id}")
async def get_article(article_id: str):
    article = await article_service.get(article_id)
    return {"id": article.id, "title": article.title, "content": article.content, "status": article.status}

但 REST 在聚合查询字段裁剪上的短板是硬伤——/articles?fields=id,title 这种伪 GraphQL 方案,维护成本越滚越大。

第二层:GraphQL 做 BFF,解决聚合和裁剪

我在前端和后台之间加了一层 BFF(Backend For Frontend),跑的 GraphQL。核心思路:BFF 层只做编排和裁剪,不做业务逻辑。

# Strawberry GraphQL schema 示例
@strawberry.type
class Article:
    id: str
    title: str
    content: str
    author_id: str
    
    @strawberry.field
    async def author(self) -> Author:
        # 内部调用 REST API 或直查数据库
        return await author_loader.load(self.author_id)

@strawberry.type
class Query:
    @strawberry.field
    async def article(self, id: str) -> Article:
        return await article_service.get(id)
    
    @strawberry.field
    async def dashboard(self, team_id: str) -> Dashboard:
        return await dashboard_service.aggregate(team_id)

踩过的坑:

N+1 问题。 如果不处理,GraphQL 解析器会逐条查询关联数据。用 DataLoader 做批量加载是必须的,上面代码里的 author_loader 就是干这个的。

from strawberry.dataloader import DataLoader

async def load_authors(ids: list[str]) -> list[Author]:
    authors = await author_service.batch_get(ids)
    # 保持顺序一致
    author_map = {a.id: a for a in authors}
    return [author_map[uid] for uid in ids]

author_loader = DataLoader(load_fn=load_authors)

查询深度限制。 不设限的话,恶意查询能把你的数据库打穿。我在网关层限制了最大深度 5 层:

# 限制查询深度
LIMIT_DEPTH = 5

class DepthValidator:
    def __init__(self, max_depth: int = LIMIT_DEPTH):
        self.max_depth = max_depth
    
    def validate(self, query: str) -> bool:
        # 通过解析 GraphQL AST 检查深度
        ...

到这个阶段,架构已经是 REST(资源层)+ GraphQL(BFF 层)。前端首屏从 3s 降到 800ms,接口调用次数减少 60%。

第三层:gRPC 做服务间通信,打通内网

服务间通信原本用的也是 REST。随着微服务拆细,A 调 B、B 调 C 再调 D,单个请求的延迟像滚雪球。

换 gRPC 的原因就两个:

  1. Protobuf 序列化比 JSON 快 3-10 倍
  2. 双向流(streaming)天然适合实时推送
  3. // proto/article.proto
    service ArticleService {
      rpc GetArticle (ArticleRequest) returns (ArticleResponse);
      rpc StreamArticleUpdates (StreamRequest) returns (stream ArticleEvent);
    }
    
    message ArticleRequest {
      string id = 1;
      repeated string fields = 2;  // 支持字段选择
    }
    
    message ArticleResponse {
      string id = 1;
      string title = 2;
      string content = 3;
      int64 version = 4;
    }
    

Python 这边用 grpc.aio 实现,异步调用性能比同步版好很多:

import grpc.aio
from article_pb2 import ArticleRequest
from article_pb2_grpc import ArticleServiceStub

async def get_article_via_grpc(article_id: str):
    async with grpc.aio.insecure_channel("article-service:50051") as channel:
        stub = ArticleServiceStub(channel)
        response = await stub.GetArticle(ArticleRequest(id=article_id))
        return response

踩坑记录:

注意: gRPC 的负载均衡默认是客户端轮询。如果用的是 Kubernetes,建议配 headless service + 客户端侧主动重连,否则会出现连接倾斜。

第四层:三协议混合架构落地

最终架构长这样:

客户端 ──HTTP──▶ 网关层 (REST + GraphQL) ──gRPC──▶ 微服务集群
                        │
                        ├── REST 接口: 对外 API、第三方集成
                        ├── GraphQL 接口: 移动端、前端 SPA
                        └── gRPC 内部: 服务间通信、实时推送

关键决策原则:

场景 协议 原因
外部 API / 第三方集成 REST 生态最好,调试工具最多
前端数据聚合 GraphQL 灵活裁剪,减少网络请求
服务间通信 gRPC 高性能、强类型、流式传输
文件上传 REST gRPC 处理大文件不优雅
实时推送 gRPC stream 双向流,协议天然支持

网关层的路由我用了一个简单的分发器:

from fastapi import FastAPI, Request
from strawberry.asgi import GraphQL

app = FastAPI()

# REST 路由
app.include_router(rest_router, prefix="/api/v1")

# GraphQL 路由
app.mount("/graphql", GraphQL(schema, debug=False))

# gRPC 不经过网关,走内部 DNS

前端调用时,通过一个轻量客户端判断走哪个接口:

// api-client.ts
class ApiClient {
  async getArticle(id: string) {
    // 列表页只关心标题——走 GraphQL
    return this.graphql(`{ article(id: "${id}") { id title } }`);
  }
  
  async createArticle(data: FormData) {
    // 文件上传——走 REST
    return this.rest.post("/api/v1/articles", data);
  }
  
  subscribeArticleUpdates(teamId: string) {
    // 实时推送——走 WebSocket(网关转发到 gRPC stream)
    return this.ws.subscribe(`/ws/articles?team_id=${teamId}`);
  }
}

性能对比数据

同样 1000 并发下的压测结果(单次查询含 3 次服务间调用):

方案 P50 P99 吞吐量
纯 REST(全部 HTTP) 45ms 210ms 2200 req/s
REST + GraphQL BFF 32ms 150ms 3100 req/s
REST + GraphQL + gRPC 内部 18ms 78ms 5800 req/s

gRPC 是内部延迟改善最明显的一步——服务间调用从 HTTP 序列化/反序列化的开销中解放出来。

现在还存在的问题

架构不算完美。有三件事我还在持续优化:

  1. 协议转换损耗。 Gateway 要把 gRPC 的二进制数据转成 JSON 返回给前端,这部分有约 3-5ms 的固定开销。
  2. 调试复杂度。 以前 curl 一把梭,现在要同时维护 REST 文档、GraphQL playground、gRPC reflection 三套调试方案。
  3. 团队学习成本。 新同学上手要同时理解三种协议的语义,前期效率下降明显。

延伸思考

如果你的项目规模还很小(日均请求 <10 万),REST 足够用,别折腾。混合架构的价值在系统复杂度到拐点后才体现。这个拐点我的经验是:

  • 前端有 2 个以上客户端(移动端 / Web / 小程序)
  • 服务数超过 5 个
  • 存在 2 种以上数据交互模式(请求-响应 / 实时推送 / 批量查询)

到不了这个量级,REST 依然是性价比最高的选择。到了,就按层渐进改造——不要一次性推翻重写,从收益最高的那一层开始动。

你可能感兴趣的文章

来源:每日教程每日一例,深入学习实用技术教程,关注公众号TeachCourse
转载请注明出处: https://teachcourse.cn/4176.html ,谢谢支持!

资源分享

分类:Android 标签:
MVP设计模式嵌入百度地图小结 MVP设计模式嵌入百度地图小结
wordpress站点服务器频繁访问GET author=1 wordpress站点服务器频繁访问GE
android-team-coordinator.skill android-team-coordinator.skill
014-一篇文章详细介绍Docker是什么以及如何使用Docker部署项目 014-一篇文章详细介绍Docker是什

评论已关闭!