乐趣区

关于node.js:node-异常数据响应排查pm2-Cluster-Mode异步

昨天收到一个铁子的反馈

node 外面写的一个 js 文件外面的办法,这个 js 文件外面有申明一个 var 全局变量(global),而后下面说到的办法就是先判断全局变量是否有值,要是就值就间接返回,要是没有值就通过接口去获取值;
而后在页面上特定的一个操作之后,会把这个全局变量的值清空为 null,而后就走下面的获取接口的值
本地失常,服务器上谬误

他们 给出的信息必定是不够的,我就顺嘴问了几个信息

  • 是不是报错?因为报错会导致 Node 利用重启,继而导致状态生效。

    没报错

  • 是否提供复现代码?这里个别取决于我的项目的体量,或者目前问题定位进度

    无奈提供。本地失常,服务器上谬误

  • 执行环境是什么?commonjs?ESM?Ts?这里想看看是不是有什么骚操作,比如说 serverless 之类的无奈保留状态。

    Node

  • 而后还问了本地和服务器通过什么启动的服务?这里我想确认一下是不是 Cluster,因为 Cluster 状态是不共享,须要非凡计划。

    node

其实到这里我就晓得,这个人不是业余做 Node 的,后面的信息有可能也有毒。

这个时候忽然给我发来了 日志截图,这间接破案了。Cluster 模式数据共享问题,本地 node 起的服务所以不存在这个问题,服务器应该是 pm2 start index.js -i 4 之类的。

0| www xxxxx
1| www xxxxx
3| www xxxxx
0| www xxxxx

接下来就是最小复现 demo 排查问题,修复计划了。

复现 Cluster 数据共享问题

其实在他让我看到是 Cluster 的时候就曾经定位到问题了,非常明显的数据共享问题

上面来看咱们的复现例子,能够发现 单个实例输入是正确的 ,正是 因为申请落到不同的机器(实例)导致不同的响应

if (!global.a) {global.a = 1}
console.log(global.a, Date.now())
function randomTask() {console.log(++global.a, Date.now())
    if (global.a < 5) {setTimeout(randomTask, Math.random() * 1000)
    }
}
randomTask();

Statelessify your application
Be sure your application is stateless meaning that no local data is stored in the process, for example sessions/websocket connections, session-memory and related. Use Redis, Mongo or other databases to share states between processes.
Another resource on how to write efficient, production ready stateless application is The Twelve Factor Application manifesto.

修复

不启动 Cluster 集群模式

因为本地是非 Cluster 集群模式,所以体现失常。那么第一个解决办法就是生产环境也不开启集群模式,然而一般来说这个计划是不可取的,生产环境的申请比拟高,集群模式才是最优解法。

减少单实例的数据服务 | 降为单实例模式

相似于 redis,只不过是新建一个单实例的 nodeJs 脚本。获取数据 & 更新数据都是申请这个脚本服务。

因为不应用集群模式所以也就不存在共享问题了。同时也防止了上一个解法的问题,因为数据服务不对外开放,只给内网的服务开明,所以申请量级不会太大。

redis

Published & subscribe

通过 redis 来实现公布订阅性能。更新数据的时候 Published 所有 Worker 更新数据。Subscribe 收到更新的时候更新本人的数据。

代码如下。
至于为什么会有多个 redis 实例呢?这是 因为一个 redis 实例只能为发布者或者订阅者,所以咱们须要有两个实例,一个用来公布更新后的数据,一个用来监听其余 worker 发来的更新。

// ioredis
const Redis = require("ioredis")
let redisClient3 = new Redis()
let redisClient4 = new Redis()

setInterval(() => {const message = { foo: Math.random(), pid: process.pid };
    const channel = `my-channel-${1 + Math.round(Math.random())}`;
    
    redisClient3.publish(channel, JSON.stringify(message));
    console.log("Published %s to %s", message, channel);
}, 5000);

redisClient4.subscribe("my-channel-1", "my-channel-2", (err, count) => {if (err) {console.error("Failed to subscribe: %s", err.message);
    } else {
        console.log(`Subscribed successfully! This client is currently subscribed to ${count} channels.`
        );
    }
});

redisClient4.on("message", (channel, message) => {console.log(`Received ${message} from ${channel}`);
});

fs

因为集群实例间无奈通信,所以须要找到一个能够独特拜访的,那么本地磁盘也是一个可行的计划。然而 fs 有可能会存在抵触,还是不倡议应用了。

cluster 模块

因为 pm2 启动的全是 Worker 所以这个计划不太适宜咱们。

if (cluster.isMaster) {const worker = cluster.fork();
  worker.send('你好');
} else if (cluster.isWorker) {process.on('message', (msg) => {process.send(msg);
  });
}
退出移动版