前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket

# cluster

30 秒速记

  • cluster = 一个主进程 fork 出多个 worker,让单线程的 Node 吃满多核
  • 每个 worker 是独立进程,JS 堆不共享 → 内存里的 session、缓存、计数器都要挪到 Redis / 数据库
  • 主进程只管监督和分发:监听 exit 事件拉起新 worker,重启要加退避,避免崩溃循环
  • 定时任务放在 worker 里会重复跑好几遍 → 只在主进程或单独进程跑,多机就上分布式锁
  • Node 16 起 cluster.isMaster 改名 cluster.isPrimary;容器化部署常改成一容器一进程,交给 K8s 扩副本

cluster 解决的是「Node 单线程只用得上一个 CPU 核」的问题:主进程 fork 出几个 worker,大家对外共用同一个端口。 说白了就是开了几家分店共用一个门牌号,前台负责把客人领到各家店。代价是每个 worker 都是独立进程,内存不互通,放在变量里的登录态、限流计数到了别的进程就没了。我上线前一定会检查三件事:状态有没有外置、worker 挂了能不能自动拉起、定时任务会不会被跑好几遍。

理解「cluster」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 process 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

cluster 是 Node 内置的多进程方案。主进程调用 cluster.fork() 启动若干个 worker,每个 worker 里照常写 http.createServer().listen(3000),并不会报端口占用,因为真正绑定端口的是主进程,worker 只是领连接干活。

const cluster = require('node:cluster');
const http = require('node:http');
const os = require('node:os');

if (cluster.isPrimary) { // Node 16 之前写 cluster.isMaster
  for (let i = 0; i < os.availableParallelism(); i++) cluster.fork();
  cluster.on('exit', (worker, code) => {
    console.log(`worker ${worker.process.pid} 退出,code=${code},拉起新的`);
    cluster.fork();
  });
} else {
  http.createServer((req, res) => {
    res.end(`handled by ${process.pid}\n`); // 多刷几次,pid 会轮着变
  }).listen(3000);
}
@前端进阶之旅: 代码已经复制到剪贴板

os.availableParallelism() 是 Node 18.14 之后才有的,老版本用 os.cpus().length。这段代码能说明三件事:多个进程共享一个端口;请求会在 worker 之间轮流分配;某个 worker 崩了,主进程能感知并补上。工程上还要补的是重启退避、优雅退出(worker.disconnect() 后等连接处理完)和状态外置。线上多数团队不会手写这套,而是用 PM2 的 cluster 模式,它底层就是这个模块。

面试官追问

追问 18 核机器开了 8 个 worker,用户登录后刷新一下又变成未登录,为什么?
参考回答

session 存在进程内存里,第二次请求被分到了另一个 worker,它那边没有这份数据。把 session 放到 Redis,或者改成无状态的 JWT,别指望靠粘性会话糊过去。

追问 2每天凌晨发一次日报邮件,上了 cluster 之后用户一天收到 4 封,怎么改?
参考回答

每个 worker 都执行了同一段 setInterval / node-cron。单机就只在主进程里跑,或者判断 cluster.worker.id === 1 再启动;多台机器就要用 Redis 锁或者独立的任务服务,保证全局只有一个实例在跑。

追问 3worker 挂了自动重启,结果代码有问题一启动就崩,CPU 被打满,怎么防?
参考回答

在 cluster.on('exit') 里别无脑 fork,记一下最近一段时间的重启次数,按 1s、2s、4s 退避,超过阈值就停下来告警。PM2 的 exp_backoff_restart_delay 做的就是这件事。

追问 4发版时想不断流量地替换所有 worker,怎么做?
参考回答

一次换一个:先 fork 新 worker,等它发出 listening,再对旧的调 worker.disconnect(),旧进程不再接新连接,处理完手头请求后退出。全部换完就完成了滚动重启,中间始终有进程在接客。

追问 5已经在 K8s 里跑了,还要不要在容器里再开 cluster?
参考回答

一般不开。Pod 副本数、健康检查、重启策略平台都管了,容器里再套一层 cluster 等于两套编排,资源限制和日志都更难看清。一个容器一个 Node 进程,按 CPU 配额扩副本更省心。

# 写在前面

30 秒速记

  • 这一部分只回答两个问题:多个 worker 怎么共用一个端口、连接怎么分给各个 worker
  • 端口共享:worker 里的 listen() 被拦截,变成向主进程发 queryServer,真正 bind 的只有主进程
  • 请求分发:默认 round-robin,主进程 accept 连接后把 socket 句柄通过 IPC 发给某个 worker
  • Windows 默认不走轮询,由系统直接分配;可以用 cluster.schedulingPolicy 或环境变量 NODE_CLUSTER_SCHED_POLICY 改
  • 读源码前先会用 cluster.fork()、isPrimary,否则 master / worker 两条代码线很容易看混

这部分讲 cluster 最常被追问的两件事:端口为什么不冲突,连接是怎么分出去的。 一句话版本是:worker 以为自己在 listen(3000),其实是请主进程代劳,主进程只绑定一次端口,然后把每个新连接的句柄转交给 worker。看源码时我建议在每段代码上标清楚它跑在主进程还是 worker,因为同名的 send、onmessage 两边都有,混了就看不懂了。

下文适合对cluster模块有一定了解的同学阅读。主要包含两部分内容:

  1. cluster模块如何实现端口共享
  2. cluster模块如何分发请求

理解「写在前面」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

面试官追问

追问 14 个 worker 都 listen(3000),为什么不报 EADDRINUSE?
参考回答

worker 里的 listen 不会真去绑端口,net 模块发现自己在 worker 里,就走 cluster._getServer 发消息给主进程。主进程对同一组地址参数只建一个监听,所以系统层面只有一次 bind。

追问 2那怎么证明真正占着 3000 端口的是主进程?
参考回答

lsof -i :3000 或 ss -ltnp 看一下,监听那一行的 PID 是主进程。再在 worker 里打印 process.pid 并多刷几次请求,会看到处理请求的 pid 在轮换,但监听者始终只有一个。

追问 3默认的分发策略是什么,能改吗?
参考回答

除 Windows 外默认是 cluster.SCHED_RR,主进程轮询分配;设成 cluster.SCHED_NONE 就交给操作系统,让各 worker 自己去抢连接。要在第一次 fork 之前设置,或者用 NODE_CLUSTER_SCHED_POLICY=none。

追问 4为什么 Node 默认不让操作系统直接分配?
参考回答

让系统分配时,Linux 上常出现少数几个进程拿走大部分连接,负载很不均。主进程轮询多一次 IPC 转发,但分得均匀,所以 Node 选了后者。

追问 5worker 和主进程之间靠什么通信?
参考回答

fork 时建立的 IPC 通道,process.send() 发消息,句柄(socket、server)也能跟着消息一起传。cluster 的内部消息会带 cmd: 'NODE_CLUSTER',走 internalMessage 事件,和业务自己发的 message 分开。

# 端口共享源码分析

30 秒速记

  • worker 调 server.listen(3000) → net 模块转去 cluster._getServer → 通过 IPC 发 act: 'queryServer'
  • 主进程按 address:port:addressType:fd:index 拼 key,同一个 key 只建一个 RoundRobinHandle
  • RoundRobinHandle 里主进程自己
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
      • 写在前面
      • 端口共享源码分析
      • 请求分发源码分析
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket
完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

cluster|Nodejs专题系列

# cluster

30 秒速记

  • cluster = 一个主进程 fork 出多个 worker,让单线程的 Node 吃满多核
  • 每个 worker 是独立进程,JS 堆不共享 → 内存里的 session、缓存、计数器都要挪到 Redis / 数据库
  • 主进程只管监督和分发:监听 exit 事件拉起新 worker,重启要加退避,避免崩溃循环
  • 定时任务放在 worker 里会重复跑好几遍 → 只在主进程或单独进程跑,多机就上分布式锁
  • Node 16 起 cluster.isMaster 改名 cluster.isPrimary;容器化部署常改成一容器一进程,交给 K8s 扩副本

cluster 解决的是「Node 单线程只用得上一个 CPU 核」的问题:主进程 fork 出几个 worker,大家对外共用同一个端口。 说白了就是开了几家分店共用一个门牌号,前台负责把客人领到各家店。代价是每个 worker 都是独立进程,内存不互通,放在变量里的登录态、限流计数到了别的进程就没了。我上线前一定会检查三件事:状态有没有外置、worker 挂了能不能自动拉起、定时任务会不会被跑好几遍。

理解「cluster」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 process 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

cluster 是 Node 内置的多进程方案。主进程调用 cluster.fork() 启动若干个 worker,每个 worker 里照常写 http.createServer().listen(3000),并不会报端口占用,因为真正绑定端口的是主进程,worker 只是领连接干活。

const cluster = require('node:cluster');
const http = require('node:http');
const os = require('node:os');

if (cluster.isPrimary) { // Node 16 之前写 cluster.isMaster
  for (let i = 0; i < os.availableParallelism(); i++) cluster.fork();
  cluster.on('exit', (worker, code) => {
    console.log(`worker ${worker.process.pid} 退出,code=${code},拉起新的`);
    cluster.fork();
  });
} else {
  http.createServer((req, res) => {
    res.end(`handled by ${process.pid}\n`); // 多刷几次,pid 会轮着变
  }).listen(3000);
}
@前端进阶之旅: 代码已经复制到剪贴板

os.availableParallelism() 是 Node 18.14 之后才有的,老版本用 os.cpus().length。这段代码能说明三件事:多个进程共享一个端口;请求会在 worker 之间轮流分配;某个 worker 崩了,主进程能感知并补上。工程上还要补的是重启退避、优雅退出(worker.disconnect() 后等连接处理完)和状态外置。线上多数团队不会手写这套,而是用 PM2 的 cluster 模式,它底层就是这个模块。

面试官追问

追问 18 核机器开了 8 个 worker,用户登录后刷新一下又变成未登录,为什么?
参考回答

session 存在进程内存里,第二次请求被分到了另一个 worker,它那边没有这份数据。把 session 放到 Redis,或者改成无状态的 JWT,别指望靠粘性会话糊过去。

追问 2每天凌晨发一次日报邮件,上了 cluster 之后用户一天收到 4 封,怎么改?
参考回答

每个 worker 都执行了同一段 setInterval / node-cron。单机就只在主进程里跑,或者判断 cluster.worker.id === 1 再启动;多台机器就要用 Redis 锁或者独立的任务服务,保证全局只有一个实例在跑。

追问 3worker 挂了自动重启,结果代码有问题一启动就崩,CPU 被打满,怎么防?
参考回答

在 cluster.on('exit') 里别无脑 fork,记一下最近一段时间的重启次数,按 1s、2s、4s 退避,超过阈值就停下来告警。PM2 的 exp_backoff_restart_delay 做的就是这件事。

追问 4发版时想不断流量地替换所有 worker,怎么做?
参考回答

一次换一个:先 fork 新 worker,等它发出 listening,再对旧的调 worker.disconnect(),旧进程不再接新连接,处理完手头请求后退出。全部换完就完成了滚动重启,中间始终有进程在接客。

追问 5已经在 K8s 里跑了,还要不要在容器里再开 cluster?
参考回答

一般不开。Pod 副本数、健康检查、重启策略平台都管了,容器里再套一层 cluster 等于两套编排,资源限制和日志都更难看清。一个容器一个 Node 进程,按 CPU 配额扩副本更省心。

# 写在前面

30 秒速记

  • 这一部分只回答两个问题:多个 worker 怎么共用一个端口、连接怎么分给各个 worker
  • 端口共享:worker 里的 listen() 被拦截,变成向主进程发 queryServer,真正 bind 的只有主进程
  • 请求分发:默认 round-robin,主进程 accept 连接后把 socket 句柄通过 IPC 发给某个 worker
  • Windows 默认不走轮询,由系统直接分配;可以用 cluster.schedulingPolicy 或环境变量 NODE_CLUSTER_SCHED_POLICY 改
  • 读源码前先会用 cluster.fork()、isPrimary,否则 master / worker 两条代码线很容易看混

这部分讲 cluster 最常被追问的两件事:端口为什么不冲突,连接是怎么分出去的。 一句话版本是:worker 以为自己在 listen(3000),其实是请主进程代劳,主进程只绑定一次端口,然后把每个新连接的句柄转交给 worker。看源码时我建议在每段代码上标清楚它跑在主进程还是 worker,因为同名的 send、onmessage 两边都有,混了就看不懂了。

下文适合对cluster模块有一定了解的同学阅读。主要包含两部分内容:

  1. cluster模块如何实现端口共享
  2. cluster模块如何分发请求

理解「写在前面」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

面试官追问

追问 14 个 worker 都 listen(3000),为什么不报 EADDRINUSE?
参考回答

worker 里的 listen 不会真去绑端口,net 模块发现自己在 worker 里,就走 cluster._getServer 发消息给主进程。主进程对同一组地址参数只建一个监听,所以系统层面只有一次 bind。

追问 2那怎么证明真正占着 3000 端口的是主进程?
参考回答

lsof -i :3000 或 ss -ltnp 看一下,监听那一行的 PID 是主进程。再在 worker 里打印 process.pid 并多刷几次请求,会看到处理请求的 pid 在轮换,但监听者始终只有一个。

追问 3默认的分发策略是什么,能改吗?
参考回答

除 Windows 外默认是 cluster.SCHED_RR,主进程轮询分配;设成 cluster.SCHED_NONE 就交给操作系统,让各 worker 自己去抢连接。要在第一次 fork 之前设置,或者用 NODE_CLUSTER_SCHED_POLICY=none。

追问 4为什么 Node 默认不让操作系统直接分配?
参考回答

让系统分配时,Linux 上常出现少数几个进程拿走大部分连接,负载很不均。主进程轮询多一次 IPC 转发,但分得均匀,所以 Node 选了后者。

追问 5worker 和主进程之间靠什么通信?
参考回答

fork 时建立的 IPC 通道,process.send() 发消息,句柄(socket、server)也能跟着消息一起传。cluster 的内部消息会带 cmd: 'NODE_CLUSTER',走 internalMessage 事件,和业务自己发的 message 分开。

# 端口共享源码分析

30 秒速记

  • worker 调 server.listen(3000) → net 模块转去 cluster._getServer → 通过 IPC 发 act: 'queryServer'
  • 主进程按 address:port:addressType:fd:index 拼 key,同一个 key 只建一个 RoundRobinHandle
  • RoundRobinHandle 里主进程自己 net.createServer().listen(),这是唯一一次真正的 bind
  • 监听成功后主进程把 handle.onconnection 换成 distribute,新连接不再自己处理,而是派给 worker
  • 主进程回一条带 ack 的消息,worker 按 ack 找回当初的回调,拿到一个「假的」handle,触发自己的 listening

端口共享的本质是:worker 的 listen 被改写成「请主进程帮我监听」,主进程对同样的监听参数只真正绑定一次端口。 链路是这样的:worker 调 listen,net 模块发现自己在 cluster 的 worker 里,就发 queryServer 给主进程;主进程用地址、端口等参数拼一个 key,没有就新建 RoundRobinHandle,在里面真正 listen,并把连接回调劫持成分发逻辑;最后回消息给 worker,worker 拿到一个占位的 handle,照常触发 listening 事件。所以 worker 那边看起来一切正常,实际上它从没碰过端口。

← 5分钟入门非对称加密用法cookie_parser深入 →

fe