【ArchSummit架构师峰会】探讨数据与人工智能相互驱动的关系>>> 了解详情
写点什么

有时 /events 比 Webhooks 更好用

acco

  • 2021-07-20
  • 本文字数:2929 字

    阅读完需:约 10 分钟

有时/events比Webhooks更好用

对许多 API 来说,Webhooks 是一种辅助手段。有了 Webhook 系统,系统 B 可以通过注册来接收有关系统 A 某些更改的通知。当系统 A 发生更改时,它通常以发出 HTTP POST 请求的形式将更改推送到系统 B。

 

Webhook 旨在消除或减少不断轮询数据的需求。但根据我的经验,Webhooks 也带来了一些挑战。

 

一般来说,你不能只依靠 Webhooks 来保持两个系统的一致性。我曾参与开发的集成最后都得通过轮询来增强 Webhook,实现一致性。这种情况是由于一些缺陷造成的。

 

首先,系统故障时会存在风险。是的,发送方通常会重试未交付的 Webhook,并做一些指数回退。但这些保证往往是松散或不清晰的。从灾难中恢复后,你的系统可能最后要处理的就是大量备份的 Webhooks。

 

其次,Webhooks 是短周期的。它们太容易处理不当或丢失了。如果你在部署代码更改后意识到你对一个 JSON 字段进行了粗指处理并将 null 插入了到你的数据库中,你是没办法重播 Webhook 的。或者,你可以在管道带外处理这个 Webhook 请求——就像数据库插入一样。但这样你就要冒着失败并失去这个 Webhook 的风险。

 

为了缓解这两个问题,许多开发人员最后将 Webhooks 缓冲到像 Kafka 这样的消息总线系统上,这种妥协方法感觉太繁琐了。

 

考虑两方之间一个复杂的 Webhook 管道架构:

我们有两条消息总线,一条在发送端,一条在接收端。这里的复杂性是显而易见的,可能出错的阶段有很多。例如:在接收端,即使你的系统很稳定,你仍然会遇到发送方可传递性失败的情况。如果发送方的队列开始出现背压,Webhook 事件就会延迟,但你可能很难知道正在发生这种情况。

 

更复杂的是,两者之间的安全层通常是一些 HTTP 请求签名协议,如 HMAC。这些协议很健壮,并且减轻了管理密码的负担。但一般的开发人员并不熟悉这种协议,因此更容易出现混乱和错误。(我认为 HTTP 请求签名和验证就是那种人们很难搞明白,于是永远没法完全记住的任务。)

 

因此,Webhooks 不仅会让你面临最终不一致的情况,而且每个人都需要为此做更多的工作。

 

那么我们还能用什么来保持两个系统的同步呢?

/events 端点

为了让两个数据集保持和谐,我们只需要查看数据库即可。考虑 Postgres 的复制槽:你为每个 follower 数据库创建一个复制槽,follower 订阅这个复制槽以获取更新。

 

这里的两个关键组件分别是:

 

  • 主数据库记录最近更改的所有内容

  • 主数据库保留一个游标,用于跟踪每个 follower 数据库在更改日志中的位置

 

如果 follower 崩溃,当它恢复时,它可以在闲暇时浏览历史。用不着队列,也没有两端的 worker 试图将事件编成一个桶队列来传递。

 

API 也可以遵循这一模型。拿 Stripe 为例,他们有一个/events 端点,其中包含过去 30 天内对 Stripe 帐户的所有创建、更新和删除操作历史。每个事件对象都包含被操作实体的完整负载。以下是一个 subscription 对象的事件示例:

{  "id": "evt_1J7rE6DXGuvRIWUJM7m6q5ds",  "object": "event",  "created": 1625012666,  "data": {    "object": {      "id": "sub_JgFEscIjO0YEHN",      "object": "subscription",      "canceled_at": 1625012666,      "customer": "cus_Jff7uEN4dVIeMQ",      "items": {        "object": "list",        "data": [          // ...        ],        "url": "/v1/subscription_items?subscription=sub_JgFEscIjO0YEHN"      },      "start_date": 1623826800,      "status": "canceled",    }  },  "type": "customer.subscription.deleted"},
复制代码

一些重要的特性:

 

  • 每个事件都有一个 type,它告诉我们这个事件的类型。在上面这个例子中,我们看到一个客户的订阅已被删除。由于完整订阅的负载已经被包含进去了,所以我们可以更新数据库以反映诸如 cancelled_at 之类的字段及其新的 canceled status。

  • 每个嵌入的对象都包含一个 object 字段,因此我们可以轻松地提取和解析它们。

  • 事件对象自由嵌入了子对象,让我们无需轮询 API 即可全面了解发生更改的所有内容。

 

因此,我们可以轮询/events,而不是通过监听 Webhooks 来保持最新状态。我们只需要在本地保留一个游标,并在请求中使用它来向 Stripe 指示我们已经看到了哪些事件。

 

优势:

 

  • 如果我们崩溃,我们不必担心 Webhooks 丢失这样的问题。当我们恢复时,我们可以按照自己的节奏回到正轨。

  • 如果我们部署了一个错误处理事件的错误,不用担心。我们可以部署一个修复程序并将游标倒回/events,后者将重播它们。

  • 我们的端点不需要消息总线。

  • 我们不必担心 Stripe 的 Webhook 发送方延迟交付。速度是我们控制的。在我们和最新数据之间只有 API 层的缓存。

  • 我们使用了一个简单的、基于令牌的身份验证方案。

  • 我们拉取和处理事件的方式看起来与我们处理其他端点的方式是一样的。我们可以重用很多相同的 API 请求/处理代码。

 

在生产者侧,为了支持/events,你需要围绕对创建/更新/删除操作的监控添加和 Webhooks 相同的那些东西。只是这里不需要构建交付管道,你只需要将记录插入到仅附加的数据库表中即可。

 

在消费者侧,你需要设置一些轮询基础设施。这比处理带内所有内容的基础 Webhook 处理端点更麻烦些。但我敢打赌,构建一个不错的轮询系统并不比构建强大的 Webhook 处理系统(例如消息总线)更难。你将获得更好的一致性保证。

让/events 变得更好

/events 端点有一个明显的效率低下之处:为了尽可能保持实时性,你必须非常频繁地轮询。我们每个帐户每 500 毫秒轮询一次 Stripe/events 端点。

 

这些请求是轻量级的,除了最活跃的 Stripe 帐户之外,其他响应通常都是空的。但是作为程序员,我们希望要寻找一种方法来进一步提高效率。

 

Stripe 等 API 平台想出了一个办法:支持长轮询!

 

在长轮询中,客户端发出标准 HTTP 请求。如果服务器没有任何新信息要交付给客户端,则服务器会将请求保持在打开状态,直到有新信息要交付为止。

 

在我们与 Stripe 的集成中,如果我们可以请求/events 并附带一个表明我们想要长轮询的参数,那就太好了。给定我们发送的游标,如果有新事件创建,Stripe 会立即返回这些事件。但如果没有,Stripe 可以将请求保持在打开状态,直到新事件被创建。当请求完成时,我们只需重新打开它并重复循环即可。这不仅意味着我们可以尽快获取事件,而且还可以减少整体的网络流量。

 

长轮询相比 Websockets 的优势是代码重用和简单性。大多数集成无论如何都涉及某种形式的轮询,无论你是回填数据还是重放错误处理的事件。通过单个参数就能从回填切换到实时侦听新事件这样的能力是很大的优势。

我该如何选择?

对于 API 消费者来说,如果你需要在轮询/events 或使用 Webhooks 之间做出选择,那么具体选哪个就取决于你的一致性需求。Webhooks 可以更快地入门,尤其是当你只关心少数 API 对象时。另外对于某些工作流来说,Webhooks 被删除也没有关系,例如你向一个 Slack 频道发布一个“新订阅者”公告。

 

但是,当一个集成变得越来越重要,并且需要确保不丢失任何内容时,我们认为轮询/events 是更好的选择。

 

对于 API 生产者来说,支持/events 不仅是给你的 API 消费者的一大礼物。/events 很容易成为接下来提供 Webhooks 的一种基础。你的 events 表可以作为 Webhook 发送方出站工作的“队列”。事实上,events 可以提供一些人们急需的 Webhook 功能,例如允许你的 Webhook 消费者重播或重置其 Webhook 订阅的位置。

 

原文链接:https://blog.syncinc.so/events-not-Webhooks

2021-07-20 16:513713

评论

发布
暂无评论
发现更多内容

Go 语言学习路线来啦

roseduan

学习 Go 语言

【LeetCode】山脉数组的峰顶索引Java题解

Albert

算法 LeetCode 6月日更

详解Vue八大生命周期钩子函数

华为云开发者联盟

Vue 对象 函数 Vue实例 八大生命周期

文档代码同源

不脱发的程序猿

开发规范 文档代码同源

【融云视角】沉浸式音频与通讯技术未来趋势

融云 RongCloud

100个开箱即用的shell脚本,CV大法好,工作不费脑!

北游学Java

Java Shell

网络安全逐渐成为程序员的必备技能

学神来啦

Linux 程序员 安全 渗透

全新升级IoT Stack 2.0和度能2.0,百度持续加码为产业智能化安全护航

百度大脑

百度智能云

实时音视频开发理论必备:如何省流量?视频高度压缩背后的预测技术

JackJiang

音视频 即时通讯 IM 视频编解码

击破行业痛点,区块链赋能智慧物流高速发展

旺链科技

区块链 智慧物流

Apache Drill 1.19.0 发布集锦

MPP Apache Drill 联邦查询和分析

EasyRecovery用法进阶--高阶设置使用技巧

淋雨

数据恢复 EasyRecovery 文件恢复 硬盘恢复

推荐学Java——应该了解的前端内容

逆锋起笔

Java 大前端 后端 JAVA开发

ARTS 打卡第11周

steve_lee

EMQ映云科技边缘计算里程碑—Kuiper加入LF Edge基金会

EMQ映云科技

开源 云端 边缘流式数据 emq LF Edge

互联网就业系列文(一)

HZFEStudio

互联网 就业

【译】JavaScript 代码整洁之道-概述篇

KooFE

JavaScript 大前端 6月日更 整洁代码

缓存的世界Redis(一)

卢卡多多

redis 缓存 6月日更

Python——元组的使用

在即

6月日更

重启心智解锁,重新获得一份能力精进指南,面对不确定性的未来,我们可以和世界的变化做好友。

叶小鍵

Flink Metric

Alex🐒

flink 翻译 flink1.13

[译] Android 的 Java 9,10,11,12 的支持

Antway

6月日更

ONES CTO 冯斌 | 升级项目管理体系,加速金融行业数字化转型

万事ONES

项目管理 研发管理 数字经济 ONES

敏捷绩效管理三剑客:OKR 、KPI、CFR

CODING DevOps

DevOps OKR 敏捷绩效 绩效管理

vue编码之优化手段

法医

Vue 大前端 6月日更

让宝妈宝爸告别安全顾虑,区块链构建母婴行业新生态

旺链科技

区块链 母婴

云图说|华为云自研云数据库GaussDB NoSQL,兼容多款NoSQL接口的数据库服务

华为云开发者联盟

nosql Influxdb Cassandra mongo GaussDB NoSQL

ONES CTO 冯斌 | 大型软件研发团队如何实践高效项目管理?

万事ONES

团队管理 ONES Project 研发团队

你应该知道的数仓安全

华为云开发者联盟

数据加密 数仓安全 透明加密 SQL函数加密

并发王者课-黄金3:雨露均沾-不要让你的线程在竞争中被“饿死”

MetaThoughts

Java 多线程 并发 并发王者课

什么是Python中的套接字编程?

华为云开发者联盟

Python 编程 socket 网络 套接字

有时/events比Webhooks更好用_语言 & 开发_InfoQ精选文章