聚类
君既成节点之应用,已备,择八 vCPU 之实例以部署之。部署之事毕矣。万事顺遂,然不自知未用部署之全能。吾知 Node.js 运于单线程,是故节点之应用,一时仅用一 vCPU — 而君取八 vCPU 之实例,则其余七 vCPU 岂非闲坐乎?
此之解法在于聚群。聚群者,乃使一应用运行多实例,各实例虽为独立之体,然仍能成事,且共驻一端口。今之疑问——此将如何运作?岂非会导致实例间生纷扰?简而答之,无碍也.
聚群之道
聚群既成,遂得众进程。其类二:
- 主节点——唯主节点一焉。主节点司启工人进程,理之,若工人死其一,则复生之。 主节点惟理而已,不运代码(如接数据库、启服务器等事)。 注——若主节点废,则全群将崩。
- 工人——此乃应用实运行之所在,侍用户。
要义
- 吾侪之例,有八虚核,故有九事——一主事加八役事。
- 每役事各有其忆,役事间无共通。
- 役事共一港,连结散布于其间。
- 主事故为愚钝,不运码亦不接库。
- 役事不见其昆仲——每役事,唯己独存。
代码片段
import cluster from "node:cluster";
import os from "node:os";
import app from "./src/app";
import { connectDB } from "./src/config/database";
import { createServer } from "http";
const PORT = process.env.PORT || 3000;
const enableCluster = process.env.NODE_ENV === "development";
if (enableCluster && cluster.isPrimary) {
const numWorkers = os.cpus().length;
for (let i = 0; i < numWorkers; i++) cluster.fork();
cluster.on("exit", (worker) => {
console.log(`worker ${worker.process.pid} died — respawning`);
cluster.fork();
});
} else {
const httpServer = createServer(app);
connectDB().then(() => httpServer.listen(PORT));
}
代码说明
于生产之中,吾辈常以 pm2 此类服务以理群集,然此间乃以本初之选为之。是故,必先得 node 之 cluster 与 os 模块。
次察此程是否为主程。若为主程,则依可用核数而生新工——此非硬定,可随宜变之,然不应逾核数/虚拟CPU数。若非主程(谓已处工中),则运实后端码——接数据库、启服务器。是故今有八工实例已起运行(加主程监之)。
藉由 process.pid,可察每工之獨一識別。
註——此識別,及一例之中所發,唯存於此。他例無法觸及其資料、過程等。
利弊
利:
- 用盡CPU核
- 崩潰隔離
- 內置於Node
- 較高吞吐,CPU受限之務
- 死者自生
弊:
- 每工有独RAM(无共态)
- 内存缓存/会话默破
- WebSockets/SSE需额外基建
- 难调试——‘何工录此?’
- 主崩则整群殁
注——于Linux,负载均衡循轮转之序;于Windows,则由操作系统自定路由之策。
大需谨记
此般之道,足敷简易群集或习得之需,惟吾之应用须用无状态数据(以数据库为支撑之REST API)而已。
于此情形,数据库乃真源所在。诸工无需识知彼此。任一工皆可应任一请求。
有状态连接(WebSocket)
前提——须知WebSocket之理。
今事易矣。既接而 HTTP 之请升为 WebSocket,则 socket 之详(孰居何 socket)存于内存,在彼工作者。故若 A 之用户通于工作者一,B 之用户通于工作者二,二者皆已登录,二者之数据皆存于 DB。然活 socket 居于异工作者。今 A 之用户发讯于 B,工作者一欲推之至 B 之 socket — 然 B 之 socket 居于工作者二之内存,非工作者一之内存也。故讯得存于 DB,然实时递至 B 之效不及。
复次,工人各自独立,故不能相语,问"君有此用户乎? "
一 TCP 之套接字,存于一进程之内。 "
黏性会话
试想一用户至工A处,设socket而通。会话之详,存于工A之忆。然于次请,用户忽移至工B。今用户欲续前语,工乃查此会话存否,然工B无其录(彼详在工A)。是故交涉不谐。
为使君易喻,今举二法以明之。
类比一(酒店前台)—汝入住A酒店。前台将汝名记于204室。后汝入B酒店索房钥,B酒店不知汝何人,盖汝入住之详,惟存于A酒店前台耳。
类比二(站内储物柜)—汝于A站之五号柜卸行囊,得票。后往B站,欲用旧票。然B站无匹配之柜,盖行囊犹在A站也。
欲解此困,需黏性会话。此法使一用户恒驻一工作节点,将一客户之请托悉系于同节点。
尚有一事,值得知晓 — Socket.IO之连接握手,初时乃多HTTP请求(长轮询为备),待升格为WebSocket方成。无粘性,则握手之请散落异工,连接竟无由立。故粘性会话非但用户已连之后所需,初接之际尤要。
REDIS适配器于Socket
虽粘滞,而工者犹不能相语。是故居工一之用户A,无以推讯于坐工二之用户B。此乃用套接字或实时通信之应用大弊也。为解此,吾有适配器——其一为Socket.IO之Redis适配器。此适配器,实为发布/订阅之协调层。既设此,则当工一发讯,适配器即发布此发于共通之车(Redis)。凡工皆订阅此车,而实主B之套接字者,则取此讯而本地传送之。今应用之运行,遂如单实例运行之应用矣。
粘性+适配器
二者解不同之题,实需并用以成其功.
- 粘性会话,使用户之请恒落同工,故连接(及握手)无中断之患.
- Redis适配器,则保工欲推讯于异工所居之用户,讯犹可通过共通之发布/订阅总线而达之。
独粘者 — 用户得连,然异工间讯仍不通。独接者 — 工得相播,然初连自断。合之 — 应用如一例,于用户观之.
要言__
节点单线程。集群每核生一工。REST因数据库共享而自由扩展。套接字则不然——连接存于一工之内存。当以粘性会话(使握手完成)加发布订阅适配器(使工可递送彼此之消息)而治之。
是故此节总括节点.js应用之基本集群。
读毕,谢君。

























