Socket.io扩展问题。

8

我在一台拥有2个CPU、2GB内存和Ubuntu 20.04操作系统的GCP计算引擎实例上运行了3个Node.js应用,并使用Nginx反向代理。其中之一是一个Socket.IO聊天服务器。Socket.IO应用程序使用@socket.io/cluster-adapter来利用所有可用的CPU核心。 我按照此教程更新了Linux设置以获得最大连接数。这是ulimit命令的输出:

core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 7856
max locked memory       (kbytes, -l) 65536
max memory size         (kbytes, -m) unlimited
open files                      (-n) 500000
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 7856
virtual memory          (kbytes, -v) unlimited

cat /proc/sys/fs/file-max
2097152

/etc/nginx/nginx.conf

user www-data;
worker_processes auto;
worker_rlimit_nofile 65535;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
        worker_connections 30000;
        # multi_accept on;
}
...

/etc/nginx/sites-available/default

...
//socket.io part
location /socket.io/ {
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_set_header Host $http_host;
         proxy_set_header X-NginX-Proxy false;
         proxy_pass http://localhost:3001/socket.io/;
         proxy_redirect off;
         proxy_http_version 1.1;
         proxy_set_header Upgrade $http_upgrade;
         proxy_set_header Connection "upgrade";
        }
...

我的聊天服务器代码,

const os = require("os");
const cluster = require("cluster");
const http = require("http");
const { Server } = require("socket.io");
const { setupMaster, setupWorker } = require("@socket.io/sticky");
const { createAdapter, setupPrimary } = require("@socket.io/cluster-adapter");
const { response } = require("express");
const PORT = process.env.PORT || 3001;
const numberOfCPUs = os.cpus().length || 2;

if (cluster.isPrimary) {
  const httpServer = http.createServer();
  // setup sticky sessions
  setupMaster(httpServer, {
    loadBalancingMethod: "least-connection", // either "random", "round-robin" or "least-connection"
  });

  // setup connections between the workers
  setupPrimary();
  cluster.setupPrimary({
    serialization: "advanced",
  });

  httpServer.listen(PORT);

  for (let i = 0; i < numberOfCPUs; i++) {
    cluster.fork();
  }

  cluster.on("exit", (worker) => {
    console.log(`Worker ${worker.process.pid} died`);
    cluster.fork();
  });
} 
//worker process
else {
  const express = require("express");
  const app = express();
  const Chat = require("./models/chat");
  const mongoose = require("mongoose");
  const request = require("request"); //todo remove
  var admin = require("firebase-admin");
  var serviceAccount = require("./serviceAccountKey.json");
  const httpServer = http.createServer(app);
  const io = require("socket.io")(httpServer, {
    cors: {
      origin: "*",
      methods: ["GET", "POST"],
    },
    transports: "websocket",
  });

  mongoose.connect(process.env.DB_URL, {
    authSource: "admin",
    user: process.env.DB_USERNAME,
    pass: process.env.DB_PASSWORD,
  });

  app.use(express.json());

  app.get("/", (req, res) => {
    res
      .status(200)
      .json({ status: "success", message: "Hello, I'm your chat server.." });
  });

  // use the cluster adapter
  io.adapter(createAdapter());
  // setup connection with the primary process
  setupWorker(io);

  io.on("connection", (socket) => {
    activityLog(
      "Num of connected users: " + io.engine.clientsCount + " (per CPU)"
    );
    ...
    //chat implementations
  });
}

加载测试客户端代码,

const { io } = require("socket.io-client");

const URL = //"https://myserver.com/"; 
const MAX_CLIENTS = 6000;
const CLIENT_CREATION_INTERVAL_IN_MS = 100;
const EMIT_INTERVAL_IN_MS = 300; //1000;

let clientCount = 0;
let lastReport = new Date().getTime();
let packetsSinceLastReport = 0;

const createClient = () => {
  const transports = ["websocket"];

  const socket = io(URL, {
    transports,
  });

  setInterval(() => {
    socket.emit("chat_event", {});
  }, EMIT_INTERVAL_IN_MS);

  socket.on("chat_event", (e) => {
    packetsSinceLastReport++;
  });

  socket.on("disconnect", (reason) => {
    console.log(`disconnect due to ${reason}`);
  });

  if (++clientCount < MAX_CLIENTS) {
    setTimeout(createClient, CLIENT_CREATION_INTERVAL_IN_MS);
  }
};

createClient();

const printReport = () => {
  const now = new Date().getTime();
  const durationSinceLastReport = (now - lastReport) / 1000;
  const packetsPerSeconds = (
    packetsSinceLastReport / durationSinceLastReport
  ).toFixed(2);

  console.log(
    `client count: ${clientCount} ; average packets received per second: ${packetsPerSeconds}`
  );

  packetsSinceLastReport = 0;
  lastReport = now;
};

setInterval(printReport, 5000);


如您所见,从此代码中可以看出,我只使用websocket来传输。因此,按照这个StackOverflow答案的说法,应该能够服务最多8000个连接。但是当我运行负载测试时,服务器在1600个连接后变得不稳定。CPU使用率升高到90%,内存使用率升高到70%。在Nginx错误日志中找不到任何问题。我该如何将连接数增加至至少8000个?我应该升级实例还是更改任何Linux设置?任何帮助都将不胜感激。
更新: 我删除了与群集相关的所有内容,并将其重新作为普通的单线程nodejs应用程序运行。这次,结果稍微好一些,稳定连接数量为2800个(CPU使用率40%,内存使用率50%)。请注意,在测试期间,我没有执行任何磁盘I/O操作。

这可能与你的问题无关,但是为什么你需要粘性会话?我记得我曾经很难让它工作,而且只有使用ws,你就不需要它。在这里检查我的问题https://dev59.com/_6bja4cB1Zd3GeqPcSwB - Qiulang
1
如果我移除粘性会话,只使用WebSocket,工作进程之间是否能够相互通信?这是一个聊天应用程序,用户可能连接到不同的工作进程。这就是为什么我使用粘性会话,以便用户A可以向连接在不同工作进程上的用户B发送消息。 - Sujith S Manjavana
@Qiulang,我认为我的当前设置与你提供的示例几乎相似。 - Sujith S Manjavana
@AloisoJunior 我移除了所有与集群相关的内容,并将其作为常规的单线程nodejs应用程序再次运行。这一次,结果稍微好了一些,稳定连接数达到了2800个(CPU使用率40%,内存使用率50%)。 - Sujith S Manjavana
你能展示一下你的客户端连接代码吗?你是如何与服务器建立socket连接的? - Rahul Sharma
显示剩余9条评论
1个回答

0

您正在使用集群适配器,该适配器不适用于粘性会话。您应该改用Redis适配器。每个工作进程将连接到Redis,并能够相互通信。您也可以在粘性会话中使用Redis适配器,但您还需要在主进程上使用Redis适配器。

回答您的另一个问题:

“如果我删除粘性会话,仅使用websocket,工作进程是否能够相互通信?”

是的,工作进程将能够相互通信。我认为在聊天应用程序中使用粘性会话不是一个好主意。您应该使用发布/订阅系统,如Redis或NATS,在工作进程之间进行通信。例如,您可以使用Redis向频道发布消息,其他工作进程将接收消息并将其发送给客户端。

当您使用粘性会话时,每个工作进程将连接到单个客户端。因此,如果您有4个工作进程,则可以同时为4个客户提供服务。如果您使用集群适配器,则每个工作进程将连接到所有客户端。因此,如果您有4个工作进程,则可以同时为4 * 4个客户提供服务。因此,使用集群适配器可以为更多客户提供服务。

使用Redis适配器的示例:

const { createAdapter } = require("socket.io-redis");
const io = require("socket.io")(httpServer, {
  cors: {
    origin: "*",
    methods: ["GET", "POST"],
  },
  transports: "websocket",
});

io.adapter(createAdapter("redis://localhost:6379")); 

使用NATS适配器的示例:

const { createAdapter } = require("socket.io-nats");
const io = require("socket.io")(httpServer, {
  cors: {
    origin: "*",
    methods: ["GET", "POST"],
  },
  transports: "websocket",
});

io.adapter(createAdapter("nats://localhost:4222"));

尝试两个选项,看看哪个对你最有效。


1
请问,为什么使用粘性会话与集群适配器是个不好的想法呢?我正在遵循这份文档,它也使用了粘性会话。此外,我尝试将其作为常规单线程应用程序运行而不使用集群,但我只得到了2800个稳定连接。因此,我的代码可能存在其他问题。 - Sujith S Manjavana
我编辑了答案以回答你的问题。 - Tibic4
这可能与Linux有关吗?即使删除了集群模块,我仍然无法获得超过3000个连接。 - Sujith S Manjavana
我认为这不是Linux问题。您可以尝试使用Redis适配器,看看是否可以获得更多连接。您也可以尝试使用NATS适配器。 - Tibic4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接