惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Recent Commits to openclaw:main
Recent Commits to openclaw:main
L
LangChain Blog
月光博客
月光博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 【当耐特】
宝玉的分享
宝玉的分享
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
博客园_首页
T
Tailwind CSS Blog
P
Proofpoint News Feed
雷峰网
雷峰网
D
Darknet – Hacking Tools, Hacker News & Cyber Security
IT之家
IT之家
V
Vulnerabilities – Threatpost
阮一峰的网络日志
阮一峰的网络日志
C
CERT Recently Published Vulnerability Notes
Attack and Defense Labs
Attack and Defense Labs
S
Schneier on Security
Security Archives - TechRepublic
Security Archives - TechRepublic
L
Lohrmann on Cybersecurity
V
Visual Studio Blog
云风的 BLOG
云风的 BLOG
WordPress大学
WordPress大学
The Register - Security
The Register - Security
N
Netflix TechBlog - Medium
Hugging Face - Blog
Hugging Face - Blog
Project Zero
Project Zero
博客园 - 叶小钗
F
Full Disclosure
大猫的无限游戏
大猫的无限游戏
Latest news
Latest news
S
SegmentFault 最新的问题
C
Cyber Attacks, Cyber Crime and Cyber Security
Google Online Security Blog
Google Online Security Blog
Recorded Future
Recorded Future
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Hacker News - Newest:
Hacker News - Newest: "LLM"
腾讯CDC
L
LINUX DO - 最新话题
Google DeepMind News
Google DeepMind News
P
Privacy International News Feed
I
InfoQ
F
Fortinet All Blogs
Vercel News
Vercel News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
Threatpost
T
Tenable Blog
B
Blog RSS Feed

Posts on Changkun's Blog

Why High-Output Systems Are Often the First to Stop Growing Dark Forest Theory: A Formal Derivation Agents (or Humans) in Goal-Directed and Goalless Environments: On Pipelines, Priors, and the Rhythm Between Exploration and Exploitation At the Boundary of Self-Reference: From Stable Structures in Artificial Intelligence to the Self as a Recursive Model in an Open Dissipative System 2023 Reading List 2022 Reading List 2021 Reading List Are PSS/USS and RSS Actually the Same Thing? Performance Differences from Page Faults vs. Prefetching 2020 Year-End Review Migration with Zero Downtime 2020 Reading List The All in Go Stack Pointers Might Not Be Ideal for Parameters Eliminating A Source of Measurement Errors in Benchmarks Setup Wordpress in 10 Minutes 我为什么不再写博客了? 2019 年终总结 2018-2019 读书清单 Ten years of blogging Rethinking the Reflections on Communications and Trusts 2018 年终总结 Go source code study is open source Go source study: unsafe Pattern Go source study: sync.Pool Go runtime programming A Million WebSocket and Go 分布式杂谈01:CAP 理论的误解 Issues of Human-Bot Interaction 压缩法与深度网络的泛化性 Go in 1 Hour UMSLT04: The Past and Present of SGD UMSLT03: A Gentle Start of Learning Theory UMSLT02: A Breif History of Neural Networks UMSLT01: A Breif History of Regularization 不笑不足以为道 论文笔记:Generalization in Deep Learning 2017 年终总结 2017 读书清单 深度学习的泛化理论简介 删除 GitHub 上已经提交的敏感信息 硕士生涯的第一年就这样告一段落了 人肉计算(10): 系统参与激励 人肉计算(9): 陷阱的解法 别聊,一聊你就暴露 人肉计算(8): 人肉计算与数据科学中的陷阱 人肉计算(7): 社会行为分析 Hexo + GitHub + Travis CI + VPS 自动部署 人肉计算(6): 预测市场 人肉计算(5): 信用风险评级模型 读书与回报 瞎扯: 对现代企业理论与当下IT企业的商业模式和信息产业链的规律性的思考 人肉计算(4): 输入数据聚合与PageRank 又一次打整了一下博客 人肉计算(3): 输入数据聚合与链路预测 人肉计算(2): 意图博弈 GWAPs 人肉计算(1): 众包与群众智慧 对后辈同学在计算机专业上的答疑与解惑 在德国的医疗及住院体验 这可能不是一个技术博客了 实验楼楼赛第3期-Python-题解 迅速更换了 DISQUS Electron 深度实践总结 良好的编码体验的三个方面 2016 年终总结 2016 读书清单 最近在着手写的文章 微信小程序文档极致总结 谈谈过去三个月在实验楼的实习经历 Built a Desktop Client for My Blog Guacamole 源码分析与 VNC 中 RFB 协议的坑 《高速上手 C++11/14》正式发布 Docker 极速入门教程02 - 镜像与容器管理 Docker 极速入门教程01 - 基本概念和操作 阶段性沉默 ELK+Redis 最佳实践 终于全面启用了 HTTPS 苹果开源了LZFSE无损压缩 Hash 碰撞的一种思路 记一次完整的 Kaldi-TIMIT 示例运行 Kaldi 上的 TIMIT 例子 Kaldi 安装与部署 从科研写作谈起 Swift API 设计指南 有趣的人类 所以其实论文并没有什么鬼用 Githug 通关记录及指南 小结一下这学期的收获 2015 读书清单 2015 年终总结 负能量爆表 转眼就快两个月了 博客迁移记录 大三总结 这个世界,终究不会是我们的。 Linux 内核分析 之六:Linux 内核创建进程的过程 小说「泽缘」 Linux 内核分析 之五:system_call中断处理过程的简要分析 大创项目的标题真是每年都在考验同学们的想象力啊 Linux 内核分析 之四:使用库函数API和嵌入汇编两种方式使用同一个系统调用
Designing Asynchronous RESTful APIs
Changkun Ou · 2018-07-02 · via Posts on Changkun's Blog

Published at发布于:   |   PV/UV: /   |   Reading阅读: 1 min

RESTful API 已经不是什么新奇玩意儿了。HTTP 的无状态性让我们在处理交互式 CS 应用中不得不使用诸如 Session、Cookie 等技术。那么我们的问题产生于:如果客户端发出的请求的在服务端需要较长的时间处理,我们应该如何设计我们的 RESTful API?这次就来聊一聊异步 RESTful API 的设计。

异步 RESTful 的场景

一个普通的 Web 应用通常不会遇到需要异步 RESTful API 的场景,它们的交互逻辑固定为:

  • 客户端请求 –> 服务端程序与数据库交互 –> 响应客户端。

现在假设我们的业务非常的复杂,需要后端进行长时间的处理(比如,五分钟),那么我们应该如何设计 RESTful API 来与客户端进行异步任务交互呢?

我们想象一个异步的事务过程:

  1. 客户端请求 -> 服务端接收到请求 -> 异步启动任务 -> 响应客户端任务已接受
  2. 客户端请求 -> 服务端收到任务进度查询请求 -> 查看任务进度 -> 响应客户端任务的执行状态

异步 RESTful 产生问题

  • 接口幂等性的保证是让客户端生成还是服务端分配?
  • 为什么不选择 WebSocket?
  • 服务端的异步任务如何对任务进度进行记录?
  • 如何让客户端从请求结果中识别异步任务成功还是失败?
  • 如果异步任务失败了,服务端需要对异步任务涉及的资源作何处理?

接口设计

当上面这些问题思考清楚后,我们就能成功的设计出异步 RESTful API 了。先说结论:

  • GET /api/v1/task 返回一个服务端分配的唯一标识
  • POST /api/v1/task/:uuid 当客户端获得唯一标识后,可以通过请求此接口来提交异步任务,并返回 202 Accepted
  • GET /api/v1/task/:uuid 当客户端提交异步任务后,可以通过此接口来查询异步任务的执行结果,未完成时返回进度
  • PUT /api/v1/task/:uuid 当客户端提交异步任务后,可以通过此接口来更新一些异步任务的信息
  • DELETE /api/v1/task/:uuid 在有需要的场景下,可以通过此接口来让客户端主动发起销毁异步任务(包括异步任务产生的资源等数据),当销毁任务执行后 /api/v1/task/:uuid 所有的操作均不能使用,返回无效事务的错误。

首先我们来看第一个问题:接口幂等性的保证让客户端生成而是服务端分配?

通常我们会看到一些接口设计的幂等性支持客户端在 POST 请求 Body 中包含一个保证幂等性的唯一标识。这是一种做法。另一种做法是,通过 先通过一个接口让服务端分配一个异步任务的事务 ID,然后客户端收到此 ID 后凭借此 ID 进行后续操作。这种做法相较于前一种做法的好处在于:服务端完整的掌控的所有的异步事务。 思考这样一个场景:客户端 A 和 客户端 B 同时使用了相同的标识符来做幂等性的保证,那么服务端必须鉴别这两个相同的标识是来自不同的客户端实体。这可以通过在请求中增加额外的身份验证来完成,但这进一步增加了服务端的复杂度以及客户端与服务端之间的交互成本。

**为什么不选择 WebSocket?**这个问题很好回答,我们来思考一些比较极端的情况。首先明确我们的异步事务场景是:**客户端提交一个异步任务,在未来的某个时间段,客户端需要知道这个异步任务的执行状态。**那么 WebSocket 会发生什么问题?假设我们同时接受了百万级别的异步任务,那么服务端必须维护百万级别的长连接从而通知客户端异步任务的执行状态。试想:一百万的长连接和一百万的并发,哪种方式对服务器产生的压力相对较小?使用 WebSocket 后还会引入很多额外的诸如断线重连、主动关闭、主动关闭失败等等错误处理,一方面增加了客户端和服务端之间的交互的开发成本,另一方面还引入了额外的系统复杂性。

好,那么我们现在可以任务客户端已经成功完成了异步任务的提交。那么客户端如何得知所提交异步任务的执行进度,又应该以什么样的策略来主动对服务端发送查询请求,以及如何鉴别一个异步任务执行失败与否?进度如何?

第一个问题:**如何得知提交异步任务的执行进度。**这个问题看似是一个比较难解的问题,因为服务端的任务调度器也不知道这个任务要多久才会执行完毕。事实上这样的场景只会发生在某个任务的第一次执行中,服务端可以对不同的任务进行鉴别,自然也可以对不同任务的执行时间进行统计。例如:客户端发起请求需要服务端执行任务 A ,服务端查询统计记录了解到任务 A 在所有历史记录中平均需要 158 秒,那么服务端可以以此为标准,直接假设任务 A 的执行时间为 158 秒,那么当客户端的查询请求到达后,比较请求提交时间和查询时间,从而推算任务进度,具体计算公式为:任务进度 = (查询请求到达时间 - 创建任务时间)/ 平均任务时间。如果任务进度计算结果大于等于 1,则任务进度为 99%。当异步任务执行完毕后,再将任务进度主动更新为 100%。

第二个问题:**客户端以什么样的策略来主动对服务端发送查询请求。**一个比较「弱智」的做法就是定时轮询。这个做法不应该被采纳的原因是,定时轮询需要消耗大量的客户端发送请求资源和服务端处理资源。我们仍然可以通过第一个问题中提到的统计模块来让服务端主动告知客户端,什么时候再发送查询请求。在 HTTP 请求 Header 中有一个 Retry-After 字段。那么,当一个客户端任务请求到达后,服务端首先检查任务是否有历史记录,如果有则统计平均任务完成时间(或者最长完成时间),并在 Retry-After 中附上此时间,从而客户端可以得知在何时再发送历史请求,而无需进行不必要的定时轮询浪费资源。如果没有,则设定一个默认的轮询时间即可。

第三个问题:**客户端如何得知一个异步任务执行失败。**在我们这套接口中,如果一个异步任务失败,服务端会被动执行销毁程序,那么对应的 uuid 接口会被禁止。这样客户端看似永远无法知道任务的执行状态,因为对它而言这时是处于查询任务执行状态的阶段。这又两个办法,一个是客户端在查询请求中始终遵循服务端 Retry-After 的原则,如果某一次 Retry-After 后请求失败,则可以认为任务是执行失败的;另一个方法是,客户端记录每次 Retry-After 查询到的任务进度,如果接口在进度小于 100% 的情况下失败了,那么也可以认为任务是失败的。

最后,如果异步任务失败了,服务端需要作何处理?这个问题相对上面的几个问题就比较简单了。答案是服务端需要有被动销毁任务。当一个异步任务失败后,我们需要设计一个被动的销毁任务,让异步任务中涉及的相关操作进行回滚或者删除,例如当客户端在申请某个创建资源后,却没有创建成功,则销毁任务应将此资源创建过程中涉及的中间产物一并销毁。

总结

好了,解决了这些问题我们基本上完成了一整套异步 RESTful API 的设计工作。我们来总结一下异步 API 的几个基本要点:

  1. 服务端分配异步事务的唯一标识

  2. 服务端的对客户端请求的异步事务执行进行统计

  3. 服务端需要实现异步事务失败的被动被动销毁任务

  4. 服务端需要在返回请求时一并告知客户端下一次主动与服务端交互的时间