为什么节点模块中定义的变量在两个用户之间共享

why a variable defined in a node module is shared between two users

我运行一个节点API服务器pm2集群模式将与mysql 数据库服务器。

在模块 x.js 中,我有这样的代码:


let insertMappingQuery = ``;
...
...
const constructInsertMappingQuery = () => {
insertMappingQuery += `
    INSERT IGNORE INTO messages_mapping (message_id, contact_id)
    VALUES (` + message_id + `, ` + contact_id + ` + `);`;
}

当用户发送消息时,函数将调用模块 x 并针对他的消息执行上面的代码(假设 message_id = 1

INSERT IGNORE INTO messages_mapping (message_id, contact_id)
    VALUES (1, some_id);

然后另一个用户发送一条消息并执行代码,比如 message_id = 2 但是查询将如下所示:

INSERT IGNORE INTO messages_mapping (message_id, contact_id)
    VALUES (1, some_id);
INSERT IGNORE INTO messages_mapping (message_id, contact_id)
    VALUES (2, some_id);

所以基本上当用户二发送消息时,此查询将包含用户一已经执行的内容。 因此用户一将插入两次他的记录。

这种情况不会一直发生,但会发生很多次(我会说是 30% 到 50%),发生这种情况时我找不到任何模式。

用户不必同时进行,可能会有一些时间差(几分钟甚至几小时)。

会不会和内存中的变量没有被清除有关?还是某种内存泄漏?

我不明白两个不同的用户如何共享一个变量。

显然我不知道 requireing 一个模块会缓存它并重用它。因此,该模块中的全局变量也将被缓存。

所以这里最好的解决方案是避免使用全局变量并重构代码。但是,如果您需要快速解决方案,您可以使用:

delete require.cache[require.resolve('replaceWithModulePathHere')]

示例:

let somefuncThatNeedsModuleX = () => {
  delete require.cache[require.resolve('./x')];
  const x = require('./x');
}

记住 require 缓存模块和所有后续 require 调用都被赋予 相同的 东西,所以写一些导出函数的东西,或者 class,这样你就可以安全地 call/instantiate 没有变量共享的东西。

例如:

const db = require(`your/db/connector`);

const Mapper = {
  addToMessageMapping: async function(messageId, contactId) {
    const query = `
      INSERT IGNORE INTO messages_mapping (message_id, contact_id)
      VALUES (${message_id}, ${contact_id});
    `;
    ...
    return db.run(query);
  },
  ...
}

module.exports = Mapper;

当然这也可能是一个 class,或者它甚至可能直接是那个函数 - 唯一改变的是你如何使它 运行 不冲突-with-any-other-call 函数。

现在,此代码的使用者完全相信以下内容没有副作用:

const mapper = require('mapper.js');
const express, app, etc, whatever = ...

....

app.post(`/api/v1/mappings/message/:msgid`, (req, res, next) => {
  const post = getPOSTparamsTheUsualWay();

  mapper.addToMessageMapping(req.params.msgId, post.contactId)
        .then(() => next());
        .catch(error => next(error));

}, ..., moreMiddleware, ... , (req,res) => {
  res.render(`blah.html`, {...});
});

另请注意,模板字符串的存在专门通过将字符串与+连接来防止字符串组合,重点是它们可以将${...}放入其中它们和 "whatever is in those curly braces" 中的模板(变量、函数调用,实际上是任何 JS)。

(他们拥有的第二个功能是,您可以使用函数名称作为前缀来标记它们,并且该函数将 运行 作为模板操作的一部分,但并不是很多人每天都需要这个.${...} 模板虽然?每天,数千次)。

当然还有最后一点:看起来您正在创建原始 SQL,这总是一个坏主意。为您正在使用的任何数据库库使用准备好的语句:它支持它们,并且意味着任何用户输入都是安全的。现在,有人可以使用 ); DROP TABLE messages_mapping; -- 的消息 ID post 到您的 API 并完成:您的 table 不见了。欢乐时光。