Node.js:在不耗尽 CPU 的情况下扩展 WebSocket

了解如何在 Node.js 中高效扩展WebSockets,通过集群、负载均衡、Redis 发布/订阅(pub/sub)及实战模式实现,避免 CPU 飙升。

若你曾启动 Node.js WebSocket 服务器并进行数千连接压力测试,定会深有体会:

CPU 负载飙升,内存泄漏频发。转眼间,你的聊天应用仿佛在泥泞地上运行。

WebSockets 看似简单——直到你尝试扩展它。

本文将详细解析如何将 Node.js WebSockets 扩展至数百万并发连接,而​​不会导致 CPU 崩溃。

WebSockets 有何不同?

与 REST API 不同,WebSockets 不是请求/响应模式,而是长连接。这意味着:

  • 每个连接都保持状态。
  • 服务器必须维持心跳机制(ping/pong)。
  • 广播消息可能压垮事件循环。

其扩展性更多取决于架构设计与效率优化,而非单纯计算能力。

朴素的方法

大多数教程是这样开始的:

import WebSocket, { WebSocketServer } from "ws";

const wss = new WebSocketServer({ port: 8080 });
wss.on("connection", (ws) => {
  console.log("Client connected");
  ws.on("message", (msg) => {
    console.log(`Received: ${msg}`);
    // 回显
    ws.send(`You said: ${msg}`);
  });
});

对于 50 个用户运行良好,或许 500 个用户也行。但若扩展到 50,000 个连接时:

  • CPU 因心跳检测而飙升。
  • 单个低效客户端即可阻塞事件循环。
  • 广播变成 O(n) 操作。

扩展模式 #1:集群化

Node.js 是单线程的,要充分利用所有 CPU 核心,需要采用集群化方案。

import cluster from "cluster";
import { cpus } from "os";
import { WebSocketServer } from "ws";
import http from "http";

if (cluster.isPrimary) {
  const numCPUs = cpus().length;
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
} else {
  const server = http.createServer();
  const wss = new WebSocketServer({ server });
  wss.on("connection", (ws) => {
    ws.on("message", (msg) => {
      ws.send(`Worker ${process.pid} got: ${msg}`);
    });
  });
  server.listen(8080, () => {
    console.log(`Worker ${process.pid} listening`);
  });
}

现在每个 CPU 核心处理其专属的连接片段。

问题:worker 不共享状态。这导致……

扩展模式#2:基于 Redis 的 Pub/Sub

要跨工作者(或服务器)进行广播,请使用 Redis 这样的 Pub/Sub 系统。

import { createClient } from "redis";
import { WebSocketServer } from "ws";

const pub = createClient();
const sub = createClient();
await pub.connect();
await sub.connect();
const wss = new WebSocketServer({ port: 8080 });
sub.subscribe("broadcast", (msg) => {
  wss.clients.forEach((client) => {
    if (client.readyState === 1) client.send(msg);
  });
});
wss.on("connection", (ws) => {
  ws.on("message", (msg) => {
    pub.publish("broadcast", msg.toString());
  });
});

现在所有消息都通过 Redis 路由,确保每个工作进程(及每个服务器实例)都能进行广播。

扩展模式 #3:Offload 心跳

WebSocket 心跳检测在大规模环境中会消耗大量 CPU 资源。与其使用 setInterval ping,不如让 nginx 或负载均衡器自动断开 dead sockets。

Nginx 配置片段:

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

upstream websocket {
    ip_hash;
    server 127.0.0.1:8080;
    server 127.0.0.1:8081;
}
server {
    listen 80;
    location /ws {
        proxy_pass http://websocket;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}

让基础架构处理 dead sockets 可降低 Node.js 的开销。

扩展模式 #4:事件驱动广播

向数千客户端广播时,避免使用简单的 for 循环。采用批处理或消息队列机制。

function broadcast(wss, data) {
  const message = JSON.stringify(data);
  for (const client of wss.clients) {
    if (client.readyState === 1) {
      setImmediate(() => client.send(message));
    }
  }
}

使用 setImmediate 可避免在向数千个客户端发送数据时阻塞事件循环。

扩展模式 #5:基于 Sticky Sessions 的水平扩展

负载均衡器通常随机路由客户端,这会破坏 WebSocket 连接。你需要 Sticky Sessions(粘滞会话)机制,确保客户端始终连接到同一服务器。

在 Kubernetes 中:

service:
  spec:
    sessionAffinity: ClientIP

在 Nginx 中:

upstream websocket {
    ip_hash;
    server 127.0.0.1:8080;
    server 127.0.0.1:8081;
}

现在每个连接都保持固定,避免状态丢失。

真实案例研究:聊天应用的扩展实践

在某金融科技公司,我们通过以下方式将实时交易聊天系统扩展至 100 万并发连接:

  • 采用 Node.js 集群充分利用所有CPU核心。
  • 使用 Redis 发布/订阅机制实现跨工作进程通信。
  • 将心跳卸载至负载均衡器
  • 采用 sticky sessions 实现水平扩展
  • 通过Prometheus + Grafana监控CPU使用率

成果:百万活跃用户下 CPU 稳定维持在 ~60% 左右

调试性能瓶颈

常见陷阱:

  • 阻塞式 JSON 解析 → 使用流解析器如 JSONStream
  • 内存泄漏 → 监控 wss.clients.size,清理闲置套接字。
  • GC 压力 → 尽可能复用对象。
  • 大有效载荷 → 使用 permessage-deflate 压缩。

压缩示例:

const wss = new WebSocketServer({
  port: 8080,
  perMessageDeflate: {
    zlibDeflateOptions: { level: 3 },
    clientNoContextTakeover: true,
  },
});

结论

WebSocket 的扩展并非仅仅依靠堆砌硬件,而是需要采用智能模式:集群化、发布/订阅、粘性会话以及工作负载卸载。

通过合理配置,Node.js 能够处理数百万个并发 WebSocket 连接,同时避免 CPU 资源耗尽。

作者:Nikulsinh Rajput
原文:https://medium.com/@hadiyolworld007/node-js-scaling-websockets-without-burning-cpu-e9834b4e65c3

本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/im/61316.html

(0)

相关推荐

发表回复

登录后才能评论