欢迎回来
登录你的知识库账户
忘记密码?
还没有账户?立即注册
创建账户
注册你的专属知识库
已有账户?去登录
找回密码
输入注册邮箱获取验证码
返回登录
请输入图片中的验证码以继续注册
加载中...
取消
新建收藏
手动添加你喜欢的内容
取消
编辑头像与昵称
上传新头像或修改你的显示昵称
支持 JPG/PNG,最大 2MB
取消

问题反馈

notebasewww.notebase.cn
控制台
内容库
动态
管理
账户
U
用户
--
在线
v0.8.7 · 知识库
笔记
KnowledgeBase
网络无边,知识有迹。
0笔记
0工具
30推荐

分类导航

按主题直达

编辑精选

站内用户贡献 · 真实笔记

最新收录

每日更新
继续浏览全部内容 →
>
笔记
0
加载中...
工具
0
此页用于记录用户反馈问题后的每一次改进
笔记用法

“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。

md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。

要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。

工具用法

在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。

要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。

工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。

隐藏笔记

写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。

适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。

数据安全

你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。

技术

全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。

理念

这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。

原则

不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。

更多

产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。

举报

如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。

趋势
// 点击导航加载发现
归档
// 归档为空
最近浏览
// 暂无浏览记录
发布
// 加载中...
用户发布
// 加载中...
用户管理
// 加载中...
访问统计
// 加载中...
内容审核
// 加载中...
个人信息
// 加载中...
返回首页

分布式 API 设计(下):REST

2026/7/7编程开发

前言

接上篇,我们聊完了 gRPC 和 Thrift 这些偏二进制的 RPC 方案,今天来聊聊 REST。说实话,REST 可能是这些年被误解得最深的“架构风格”了——很多人觉得“只要用了 HTTP + JSON 就是 REST”,但事实上,REST 的核心思想跟 HTTP 动词、状态码这些具体实现关系不大,它更关心的是资源的表述和超媒体驱动。

这篇笔记我会从实际开发者的视角出发,把 REST 的设计原则、常见误区、以及怎么在分布式系统里用好它,掰开了揉碎了讲清楚。代码示例会尽量贴近真实场景,不会搞那种“Hello World”级别的玩具代码。


1. REST 到底是什么?先纠正几个常见误解

1.1 REST 不是协议,是架构风格

很多人把 REST 跟 HTTP 绑定得太死了,甚至觉得“RESTful API = HTTP API”。其实 Fielding 博士论文里定义的 REST,核心约束是:

  • 客户端-服务器分离:解耦 UI 和数据存储
  • 无状态:每个请求包含所有必要信息,服务端不存客户端上下文
  • 可缓存:响应隐式或显式标记是否可缓存
  • 分层系统:客户端不需要知道是直接连服务器还是通过中间件
  • 统一接口:这是最关键的,包括资源标识、通过表述操作资源、自描述消息、以及超媒体作为应用状态引擎(HATEOAS)

你看,这里面根本没提 JSON 还是 XML,也没说必须用 GET/POST。HTTP 只是 REST 的一种常见实现载体,但 REST 完全可以运行在其他协议上(比如 CoAP 用于物联网)。

1.2 最常见的错误:把 REST 当成 CRUD 映射

很多团队的“RESTful API”长这样:

GET    /users          -> 查询用户列表
POST   /users          -> 创建用户
GET    /users/{id}     -> 获取单个用户
PUT    /users/{id}     -> 更新用户
DELETE /users/{id}     -> 删除用户

这其实是资源式 RPC,不是 REST。真正的 REST 关注的是资源的状态转移,而不是对数据库的增删改查。比如“下单”这个操作,CRUD 思维会设计成 POST /orders 然后返回订单 ID,但 REST 的思维是:你先获取购物车的表述,然后通过超链接“提交订单”来转移状态。


2. 核心概念深度拆解

2.1 一切皆资源,但资源不是数据表

资源是一种概念映射,可以是实体(用户、订单)、集合(用户列表)、甚至过程(计算器里的加法操作)。每个资源有唯一的标识符(URI),并且可以有多种表述(representation)——比如同一个用户资源,可以用 JSON、XML、HTML 甚至图片格式返回。

关键点:资源的状态由服务端管理,客户端只能通过表述来操作资源。你不能直接修改服务端的内存或数据库,只能通过发送新的表述来请求状态变更。

2.2 自描述消息:让每个请求都“带说明书”

REST 要求消息是自描述的,意思是客户端看到消息就能知道怎么处理,不需要额外文档。具体手段包括:

  • Content-Type:告诉客户端怎么解析 body(application/json、application/xml)
  • Link 头:给出相关资源的链接,比如分页时的 rel="next"、rel="prev"
  • 状态码:比如 201 Created 告诉客户端资源已创建,Location 头给出新资源 URI

举个例子,一个标准的 REST 响应应该长这样:

HTTP/1.1 201 Created
Content-Type: application/json
Location: /orders/12345
Link: </orders/12345/payment>; rel="payment"

{
  "orderId": "12345",
  "status": "pending",
  "total": 29.99,
  "_links": {
    "self": { "href": "/orders/12345" },
    "payment": { "href": "/orders/12345/payment" },
    "cancel": { "href": "/orders/12345/cancel" }
  }
}

注意这里的 _links 字段——这就是 HATEOAS 的体现。客户端不需要事先知道“取消订单”的 URL 是什么,直接从响应里拿就行。

2.3 HATEOAS:被绝大多数人忽略的核心

HATEOAS(Hypermedia As The Engine Of Application State)是 REST 的终极约束,也是实际项目里最容易被砍掉的部分。它的意思是:服务器通过超媒体(链接和表单)告诉客户端接下来能做什么。

想象一下你在浏览网页:你打开一个商品页面,页面上有“加入购物车”按钮和“查看详情”链接,你不需要提前知道这些 URL 是什么,浏览器渲染出来你点就行了。REST API 也应该这样——客户端不应该硬编码任何业务 URL,而是从初始入口(比如 /)开始,跟着链接走。

实际应用场景:

  • 订单状态机:当订单状态是“pending”时,响应里只包含“支付”和“取消”的链接;当状态变成“paid”后,这些链接消失,出现“申请退款”的链接
  • 分页:不返回 page=2 这种参数,而是返回 rel="next" 的链接,客户端直接请求那个链接即可

不过说实话,在移动端和微服务场景下,HATEOAS 的实现成本很高,很多团队选择妥协。但如果你真的想做纯正的 REST,这是绕不过去的。


3. 实际设计中的关键决策

3.1 版本管理:别在 URI 里写版本号

我看到太多 API 长这样:/api/v1/users。这在 REST 哲学里是不推荐的,因为版本应该体现在 Content-Type 或请求头里,而不是 URI。URI 代表的是资源,不是接口版本。

推荐做法:

Accept: application/vnd.mycompany.v1+json

或者用自定义头:

X-API-Version: 1

这样当你升级到 v2 时,旧的客户端仍然可以通过 Accept 头请求 v1 格式,而 URI 保持不变。不过现实是,很多网关和客户端库对自定义 Content-Type 支持不好,所以实际项目中用 URI 版本号也不是不行,只是要知道这不是 RESTful 的做法。

3.2 查询、过滤与分页

REST 里查询参数应该用来过滤、排序、分页,而不是用来定义动作。比如:

GET /orders?status=paid&sort=-created_at&page=2&per_page=20

这里 status 是过滤条件,sort 是排序(负号表示降序),page 和 per_page 是分页。注意 page 不是资源标识符的一部分,所以放在 query string 里没问题。

分页响应应该包含链接:

{
  "data": [...],
  "_links": {
    "self": { "href": "/orders?page=2" },
    "first": { "href": "/orders?page=1" },
    "prev": { "href": "/orders?page=1" },
    "next": { "href": "/orders?page=3" },
    "last": { "href": "/orders?page=10" }
  },
  "meta": {
    "total": 200,
    "per_page": 20,
    "current_page": 2
  }
}

3.3 异步操作的处理

有些操作耗时很长(比如生成报表、处理视频),不能同步返回结果。REST 的做法是:

  1. 客户端发起请求,服务器返回 202 Accepted,并在 Location 头里给一个“状态查询”的 URL
  2. 客户端轮询这个 URL,得到 200 OK 时表示完成,或者 303 See Other 重定向到最终结果

示例:

POST /reports
Content-Type: application/json

{
  "type": "monthly_sales",
  "month": "2024-01"
}

响应:

HTTP/1.1 202 Accepted
Location: /operations/abc123
Content-Type: application/json

{
  "operationId": "abc123",
  "status": "processing",
  "_links": {
    "self": { "href": "/operations/abc123" }
  }
}

客户端轮询 /operations/abc123,得到:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "operationId": "abc123",
  "status": "completed",
  "resultUrl": "/reports/monthly_sales_2024_01.pdf",
  "_links": {
    "result": { "href": "/reports/monthly_sales_2024_01.pdf", "type": "application/pdf" }
  }
}

4. 与 gRPC 的对比:什么时候选 REST?

维度 REST gRPC
数据格式 JSON/XML/HTML 等文本为主 Protocol Buffers 二进制
性能 文本解析慢,Payload 大 二进制高效,Payload 小
浏览器支持 原生支持(fetch/XMLHttpRequest) 需要 gRPC-Web 代理
流式传输 有限(SSE、Chunked) 原生支持四种流模式
工具生态 极其丰富(Postman, curl 等) 需要 protoc 编译,工具较少
学习曲线 低,HTTP 基础即可 需要理解 protobuf、HTTP/2
契约定义 无强制契约(OpenAPI 是文档) 强契约(.proto 文件)
适合场景 对外 API、浏览器客户端、异构系统 内部微服务、高性能场景、流处理

我的经验是:

  • 对外公开的 API:首选 REST,因为客户端多样性太高,JSON 的普适性最好
  • 内部服务间调用:如果对性能要求高(比如每秒上万次调用),或者需要流式传输,gRPC 更合适
  • 混合架构:很多公司用 gRPC 做服务间通信,然后用 REST API Gateway 暴露给外部

5. 实际项目中的坑与经验

5.1 不要滥用 200 OK

很多团队不管成功失败都返回 200,然后在 body 里放一个 code 字段:

{
  "code": 1001,
  "message": "参数错误",
  "data": null
}

这完全违背了 HTTP 语义。正确的做法是:

  • 成功:2xx(200, 201, 204)
  • 客户端错误:4xx(400, 404, 409)
  • 服务端错误:5xx(500, 502, 503)

这样客户端可以根据状态码做初步处理,而不需要解析 body。

5.2 注意幂等性

HTTP 方法有天然的幂等性语义:

  • GET、HEAD、OPTIONS:幂等且安全(不改变资源状态)
  • PUT、DELETE:幂等但不安全(多次调用结果相同)
  • POST:既不幂等也不安全(每次可能创建新资源)

如果你用 POST 做更新操作,需要自己保证幂等性(比如通过幂等键 Idempotency-Key 头)。

5.3 链接管理:别让客户端硬编码

即使不完全实现 HATEOAS,也建议在响应里包含常用链接。比如用户登录后返回:

{
  "userId": 123,
  "name": "张三",
  "_links": {
    "self": "/users/123",
    "orders": "/users/123/orders",
    "profile": "/users/123/profile"
  }
}

这样当以后 /users/123/orders 路径发生变化时,只需要服务端改,客户端不需要更新代码(前提是客户端实现了链接发现逻辑)。


6. 总结:REST 的精髓不在 HTTP,而在架构思想

写到最后,我想说:REST 是一种设计哲学,不是一套技术规范。它的核心价值在于:

  1. 解耦:客户端和服务端通过统一接口交互,各自可以独立演进
  2. 可发现性:超媒体让客户端像浏览网页一样浏览 API
  3. 可伸缩性:无状态和缓存机制让系统可以水平扩展

但现实是,大多数“REST API”其实只是 HTTP API with JSON。这没什么丢人的——如果你的系统不需要 HATEOAS 带来的灵活性,那简化版本完全够用。关键是要清楚自己在做什么选择,以及为什么。

最后给个实用建议:如果团队里没人能说清楚 REST 和 HTTP API 的区别,那就别标榜自己“RESTful”,老老实实叫 HTTP API 就行。少一个被误解的术语,多一份代码的清晰。

编写使用方法
Markdown 格式 · Ctrl+Enter 确定
新建笔记
预览
数据表格
点击单元格编辑 · Tab 移动
A1fx
Sheet1
BIH1H2≡🔗</>
隐私提醒

取消
编辑工具
取消