水平缩放
¥Scaling horizontally
现在我们的应用可以适应临时网络中断,让我们看看如何水平扩展它以便能够支持数千个并发客户端。
¥Now that our application is resilient to temporary network interruptions, let's see how we can horizontally scale it in order to be able to support thousands of concurrent clients.
水平扩展(也称为 "横向扩展")意味着向你的基础架构添加新服务器以应对新需求
¥Horizontal scaling (also known as "scaling out") means adding new servers to your infrastructure to cope with new demands
垂直扩展(也称为 "扩大")意味着向现有基础设施添加更多资源(处理能力、内存、存储……)
¥Vertical scaling (also known as "scaling up") means adding more resources (processing power, memory, storage, ...) to your existing infrastructure
第一步:让我们使用主机的所有可用核心。默认情况下,Node.js 在单线程中运行 Javascript 代码,这意味着即使使用 32 核 CPU,也只会使用一个核心。幸运的是,Node.js cluster
模块 提供了一种为每个核心创建一个工作线程的便捷方法。
¥First step: let's use all the available cores of the host. By default, Node.js runs your Javascript code in a single thread, which means that even with a 32-core CPU, only one core will be used. Fortunately, the Node.js cluster
module provides a convenient way to create one worker thread per core.
我们还需要一种在 Socket.IO 服务器之间转发事件的方法。我们将此组件称为 "适配器"。
¥We will also need a way to forward events between the Socket.IO servers. We call this component an "Adapter".
让我们安装集群适配器:
¥So let's install the cluster adapter:
- NPM
- Yarn
- pnpm
npm install @socket.io/cluster-adapter
yarn add @socket.io/cluster-adapter
pnpm add @socket.io/cluster-adapter
现在我们将其插入:
¥Now we plug it in:
- CommonJS
- ES modules
const express = require('express');
const { createServer } = require('node:http');
const { join } = require('node:path');
const { Server } = require('socket.io');
const sqlite3 = require('sqlite3');
const { open } = require('sqlite');
const { availableParallelism } = require('node:os');
const cluster = require('node:cluster');
const { createAdapter, setupPrimary } = require('@socket.io/cluster-adapter');
if (cluster.isPrimary) {
const numCPUs = availableParallelism();
// create one worker per available core
for (let i = 0; i < numCPUs; i++) {
cluster.fork({
PORT: 3000 + i
});
}
// set up the adapter on the primary thread
return setupPrimary();
}
async function main() {
const app = express();
const server = createServer(app);
const io = new Server(server, {
connectionStateRecovery: {},
// set up the adapter on each worker thread
adapter: createAdapter()
});
// [...]
// each worker will listen on a distinct port
const port = process.env.PORT;
server.listen(port, () => {
console.log(`server running at http://localhost:${port}`);
});
}
main();
import express from 'express';
import { createServer } from 'node:http';
import { Server } from 'socket.io';
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
import { availableParallelism } from 'node:os';
import cluster from 'node:cluster';
import { createAdapter, setupPrimary } from '@socket.io/cluster-adapter';
if (cluster.isPrimary) {
const numCPUs = availableParallelism();
// create one worker per available core
for (let i = 0; i < numCPUs; i++) {
cluster.fork({
PORT: 3000 + i
});
}
// set up the adapter on the primary thread
setupPrimary();
} else {
const app = express();
const server = createServer(app);
const io = new Server(server, {
connectionStateRecovery: {},
// set up the adapter on each worker thread
adapter: createAdapter()
});
// [...]
// each worker will listen on a distinct port
const port = process.env.PORT;
server.listen(port, () => {
console.log(`server running at http://localhost:${port}`);
});
}
就是这样!这将为你机器上可用的每个 CPU 生成一个工作线程。让我们看看它的实际效果:
¥That's it! This will spawn one worker thread per CPU available on your machine. Let's see it in action:
/videos/tutorial/scaling-up.mp4
正如你在地址栏中看到的,每个浏览器选项卡都连接到不同的 Socket.IO 服务器,并且适配器只是在它们之间转发 chat message
事件。
¥As you can see in the address bar, each browser tab is connected to a different Socket.IO server, and the adapter is simply forwarding the chat message
events between them.
目前有 5 个官方适配器实现:
¥There are currently 5 official adapter implementations:
¥the Redis adapter
¥the MongoDB adapter
¥the Postgres adapter
集群适配器 号
¥the Cluster adapter
因此,你可以选择最适合你需求的一种。但请注意,某些实现不支持连接状态恢复功能,你可以找到兼容性矩阵 此处。
¥So you can choose the one that best suits your needs. However, please note that some implementations do not support the Connection state recovery feature, you can find the compatibility matrix here.
在大多数情况下,你还需要确保 Socket.IO 会话的所有 HTTP 请求都到达同一服务器(也称为 "粘性会话")。不过,这里不需要这样做,因为每个 Socket.IO 服务器都有自己的端口。
¥In most cases, you would also need to ensure that all the HTTP requests of a Socket.IO session reach the same server (also known as "sticky session"). This is not needed here though, as each Socket.IO server has its own port.
更多信息在 此处。
¥More information here.
我们的聊天应用终于完成了!在本教程中,我们了解了如何:
¥And that finally completes our chat application! In this tutorial, we have seen how to:
在客户端和服务器之间发送事件
¥send an event between the client and the server
将事件广播给所有或部分连接的客户端
¥broadcast an event to all or a subset of connected clients
处理临时断线
¥handle temporary disconnections
放大
¥scale up
你现在应该对 Socket.IO 提供的功能有了更好的了解。现在是你构建自己的实时应用的时候了!
¥You should now have a better overview of the features provided by Socket.IO. Now it's your time to build your own realtime application!
- CommonJS
- ES modules