You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用多实例EventSources进行SSE数据流时应用冻结问题

问题背景

我正在使用ExpressJS和HTML开发一款仪表盘,采用Server-Sent Events(SSE)实现数据流。每次打开页面都会创建一个新的EventSource对象,但发现当EventSource实例数量达到6个时,应用会出现冻结情况。

简化客户端代码

const eventSource = new EventSource("/api/data/stream/last");
eventSource.onmessage = function (event) {
    const type = event.type;
    const data = JSON.parse(event.data);
    console.log("Received ", type, ": ", data);
};

eventSource.onerror = function (error) {
    console.error("EventSource failed:", error);
};

简化服务端代码

router.get("/all", function (req, res) {

    /* Set response headers for Server-Sent Events */
    res.setHeader("Content-Type", "text/event-stream");
    res.setHeader("Cache-Control", "no-cache");
    res.setHeader("Connection", "keep-alive");

    /* Send all data */
    sendData(err, res, data);

    /* Handle client connection close event */
    req.on("close", () => {
        res.end();
    });
});

问题现象

当我在不同浏览器标签或窗口中打开多个仪表盘实例时,一切正常,直到打开第6个实例,此时应用会冻结。

推测原因

我怀疑该问题与EventSource对象发起新HTTP会话并打开新TCP/IP套接字的方式有关。由于SSE会保持连接持续从服务器推送数据,每个套接字都会处于活跃状态。根据RFC 2616,大多数浏览器对同一服务器的同时活跃HTTP/1连接有上限,通常在4到6个之间,这可能是达到第6个连接时应用冻结的原因。

咨询问题

  1. 如何更高效地管理多个SSE连接以防止应用冻结?
  2. 是否可以增加现代浏览器的同时连接数上限?
  3. 在该场景下,是否有替代方案实现实时数据流而不触及这些限制?

解决方案与建议

1. 高效管理SSE连接的方案

(1)复用浏览器内的连接

同一浏览器的不同标签页可共享单个SSE连接,借助localStorage或Broadcast Channel API实现跨标签通信:

  • 首个打开的标签页创建SSE连接,后续标签页监听跨标签通信事件接收数据
  • 所有标签页关闭时再销毁SSE连接
  • 客户端示例思路:
const channel = new BroadcastChannel('dashboard-stream');
if (!localStorage.getItem('sse-connected')) {
    const eventSource = new EventSource("/api/data/stream/last");
    eventSource.onmessage = function (event) {
        const data = JSON.parse(event.data);
        // 推送数据到其他标签页
        channel.postMessage(data);
        // 当前标签页处理数据
        handleData(data);
    };
    localStorage.setItem('sse-connected', 'true');
    // 监听页面关闭,清理资源
    window.addEventListener('beforeunload', () => {
        localStorage.removeItem('sse-connected');
        eventSource.close();
    });
} else {
    // 接收主连接标签页的数据
    channel.addEventListener('message', (e) => {
        handleData(e.data);
    });
}

function handleData(data) {
    // 业务逻辑处理
    console.log("Received data:", data);
}

(2)服务端实现连接分组与复用

  • 让客户端订阅特定数据主题,服务端仅推送对应主题的更新,避免全量数据推送
  • 合并相同数据流的连接,多个客户端订阅同一主题时,服务端维护一份推送逻辑,降低资源占用

(3)及时清理无效连接

除了监听close事件,还要监听abort事件,确保客户端断开时及时释放服务端资源:

req.on("close", () => {
    res.end();
    // 移除该连接的订阅或清理相关资源
});
req.on("abort", () => {
    res.end();
    // 同步清理资源
});

2. 关于浏览器同时连接数上限

现代浏览器的HTTP/1.1并发连接上限确实为4-6个,无法通过前端代码或常规配置修改——这是浏览器内置的安全与性能限制,防止单个服务器被过度请求压垮。

如果服务端支持HTTP/2,浏览器对同一域名的并发连接限制会大幅提升(通常数十到上百个),因为HTTP/2支持多路复用,多个请求可通过同一TCP连接传输。升级到HTTP/2是解决该限制的有效方式,Express可配合spdy模块或Node.js 8+原生HTTP/2 API实现。

3. 实时数据流的替代方案

(1)WebSocket

WebSocket是双向通信协议,同一连接可双向传输数据,浏览器对其并发连接限制远宽松于HTTP/1.1:

  • 客户端示例:
const socket = new WebSocket('ws://your-server/api/data/stream');
socket.onmessage = function(event) {
    const data = JSON.parse(event.data);
    handleData(data);
};
  • 服务端示例(使用ws模块):
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
    // 定时推送数据
    const interval = setInterval(() => {
        ws.send(JSON.stringify(getLatestData()));
    }, 1000);
    // 客户端断开时清理定时器
    ws.on('close', () => {
        clearInterval(interval);
    });
});

(2)轮询(短轮询/长轮询)

短轮询是客户端定期请求服务器获取数据,长轮询是服务器保持连接直至有数据更新再返回。虽然实时性不如SSE或WebSocket,但可通过控制请求频率降低并发连接数,适合实时性要求不高的场景。

(3)消息队列中间件

若应用规模较大,可引入Redis Pub/Sub或MQTT等中间件,客户端通过MQTT协议连接(支持多路复用),服务端将数据发布到队列,客户端订阅对应主题,大幅减少服务端直接连接数。


内容的提问来源于stack exchange,提问作者VictoRPiles

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 13:24:59