告别轮询!服务端推送技术选型(WebSockets/SSE/WebTransport/长轮询)

140 阅读7分钟

Webhooks

概念

Webhook 是一种基于 HTTP 的回调机制,允许一个应用程序在特定事件发生时自动通过 HTTP 请求通知另一个应用程序。与传统的 API 不同,Webhook 是一种“推送”机制,而不是“拉取”机制。

工作原理

Webhooks 的工作流程可以总结为以下几个步骤:

  • 事件触发:当某个应用程序中的特定事件发生时,系统就会触发 Webhook。例如,用户完成了一笔支付,或代码库有新的提交。
  • 发送 HTTP 请求:事件发生后,应用会通过 HTTP 请求(通常是 POST 请求)将事件数据发送到预设的 URL。这个 URL 是接收 Webhook 的端点,通常是另一个应用程序或服务提供的 API。
  • 数据处理:接收方应用接收到 HTTP 请求后,会解析请求中的数据,并根据这些数据进行相应的操作。例如,它可能会更新数据库,发送通知,或触发其他操作。

最佳实践

  • 安全连接 :使用 HTTPS 确保 Webhook 传输的数据是加密和安全的。
  • 验证 Webhook:实施验证方法(例如验证数字签名),以确认传入数据来自受信任的来源。
  • 错误处理 :将系统设计为能够正常处理故障,包括重试和针对不成功的 Webhook 交付发出警报。
  • 日志记录 :维护 Webhook 活动的详细日志,以便于调试并为数据流提供审计跟踪。

使用场景

  • 实时数据更新(如消息通知)

    • 场景:用户收到新消息、订单状态变更时实时刷新页面。
    • 实现:
      • 后端服务接收 Webhooks 事件(如支付成功、新消息)。
      • 后端通过 WebSocket 或 Server-Sent Events (SSE)  将数据推送到前端。
      • 前端监听推送事件,动态更新 UI。
    • 优势:避免前端轮询,减少服务器压力。
  • 第三方服务集成

    • 场景:集成 GitHub、Stripe 等第三方服务状态到前端界面。
      • 示例:显示 GitHub 仓库的实时提交记录。
    • 实现:
      • 后端配置 Webhooks 接收第三方事件(如 GitHub 的 push 事件)。
      • 后端将事件数据转发给前端(通过 WebSocket/SSE)。
      • 前端渲染实时数据。
  • 自动化工作流触发

    • 场景:用户完成支付后自动跳转/展示凭证。
    • 实现:
      • 支付平台(如 Stripe)通过 Webhook 通知后端支付结果。
      • 后端验证后,通过实时通道通知前端更新页面(如展示订单成功)。
  • 开发调试工具

    • 场景:本地开发时捕获 Webhooks 数据。
    • 实现:
      • 使用工具如 ngrok 将本地服务暴露为公网 URL。
      • 将 URL 注册为 Webhook 接收端(如 GitHub Webhook)。
      • 前端本地页面直接显示收到的 Webhook 数据(用于调试)。
  • 低代码平台动态更新

    • 场景:无代码平台中,用户修改配置后实时预览效果。
    • 实现:
      • 配置变更时,后端服务触发 Webhook。
      • 前端监听到变更,重新拉取配置并刷新预览界面。

长轮询

该技术模拟服务器推送通信与普通 XHR 请求。与传统轮询不同,在传统轮询中,客户端定期重复向服务器请求数据,而长轮询会建立与服务器的连接,该连接将保持打开状态,直到有新数据可用。一旦服务器获得新信息,它就会将响应发送到客户端,并关闭连接。收到服务器的响应后,客户端立即启动新请求,并重复该过程。此方法允许更即时的数据更新,并减少不必要的网络流量和服务器负载。但是,它仍然可能在通信中引入延迟,并且效率低于 WebSockets 等其他实时技术。

// long-polling in a JavaScript client
function longPoll() {
    fetch('http://example.com/poll')
        .then(response => response.json())
        .then(data => {
            console.log("Received data:", data);
            longPoll(); // Immediately establish a new long polling request
        })
        .catch(error => {
            /**
             * Errors can appear in normal conditions when a 
             * connection timeout is reached or when the client goes offline.
             * On errors we just restart the polling after some delay.
             */
            setTimeout(longPoll, 10000);
        });
}
longPoll(); // Initiate the long polling

WebSockets

WebSockets 通过客户端和服务器之间的单个长期连接提供全双工通信通道。这项技术使浏览器和服务器能够在没有 HTTP 请求-响应周期开销的情况下交换数据,从而促进实时聊天、游戏或金融交易平台等应用程序的实时数据传输。

虽然 WebSocket API 的基础易于使用,但在生产环境中却显得相当复杂。套接字可能会松动连接,必须相应地重新创建。特别是检测连接是否仍然可用,可能非常棘手。大多数情况下,您会添加 ping-and-pong 检测信号,以确保打开的连接不会关闭。这种复杂性就是为什么大多数人在 WebSockets 上使用像 Socket.IO 这样的库来处理所有这些情况,甚至在需要时提供长轮询的回退。

// WebSocket in a JavaScript client
const socket = new WebSocket('ws://example.com');

socket.onopen = function(event) {
  console.log('Connection established');
  // Sending a message to the server
  socket.send('Hello Server!');
};

socket.onmessage = function(event) {
  console.log('Message from server:', event.data);
};

Server-Sent-Events

服务器发送事件 (SSE) 提供了一种通过 HTTP 将服务器更新推送到客户端的标准方法。与 WebSockets 不同,SSE 专为从服务器到客户端的单向通信而设计,使其非常适合实时新闻源、体育比分等场景,或者任何需要实时更新客户端而无需将数据发送到服务器的情况。

您可以将 Server-Sent-Events 视为单个 HTTP 请求,其中后端不会一次发送整个正文,而是保持连接打开,并通过每次必须将事件发送到客户端时发送一行来涓涓细流地提供答案。

使用 SSE 创建用于接收事件的连接非常简单。在浏览器中的客户端,使用生成事件的服务器端脚本的 URL 初始化 EventSource 实例。

侦听消息涉及将事件处理程序直接附加到 EventSource 实例。API 区分通用消息事件和命名事件,从而允许进行更结构化的通信。

与 WebSockets 不同,EventSource 将在连接丢失时自动重新连接。

在服务器端,您的脚本必须将 Content-Type 标头设置为 text/event-stream,并根据 SSE 规范设置每条消息的格式。这包括指定事件类型、数据负载以及事件 ID 和重试计时等可选字段。

// Connecting to the server-side event stream
const evtSource = new EventSource("https://example.com/events");

// Handling generic message events
evtSource.onmessage = event => {
    console.log('got message: ' + event.data);
};

import express from 'express';
const app = express();
const PORT = process.env.PORT || 3000;

app.get('/events', (req, res) => {
    res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
    });

    const sendEvent = (data) => {
        // all message lines must be prefixed with 'data: '
        const formattedData = `data: ${JSON.stringify(data)}\n\n`;
        res.write(formattedData);
    };

    // Send an event every 2 seconds
    const intervalId = setInterval(() => {
        const message = {
            time: new Date().toTimeString(),
            message: 'Hello from the server!',
        };
        sendEvent(message);
    }, 2000);

    // Clean up when the connection is closed
    req.on('close', () => {
        clearInterval(intervalId);
        res.end();
    });
});
app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));

WebTransport API

WebTransport 是一种尖端 API,旨在实现 Web 客户端和服务器之间的高效、低延迟通信。它利用 HTTP/3 QUIC 协议来启用各种数据传输功能,例如以可靠和不可靠的方式通过多个流发送数据,甚至允许不按顺序发送数据。这使得 WebTransport 成为需要高性能网络的应用程序(如实时游戏、实时流媒体和协作平台)的强大工具。但是,需要注意的是,WebTransport 目前是一个工作草案,尚未得到广泛采用。截至目前(2024 年 3 月),WebTransport 处于工作草案中,尚未得到广泛支持。您尚不能在 Safari 浏览器中使用 WebTransport,Node.js 中也没有本机支持。 这限制了它在不同平台和环境中的可用性。

WebRTC

WebRTC (Web Real-Time Communication) 是一个开源项目和 API 标准,可直接在 Web 浏览器和移动应用程序中实现实时通信 (RTC) 功能,而无需复杂的服务器基础设施或安装额外的插件。它支持点对点连接,用于在浏览器之间流式传输音频、视频和数据交换。WebRTC 旨在通过 NAT 和防火墙工作,利用 ICE、STUN 和 TURN 等协议在对等体之间建立连接。