为什么节点模块中定义的变量在两个用户之间共享
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%),发生这种情况时我找不到任何模式。
用户不必同时进行,可能会有一些时间差(几分钟甚至几小时)。
会不会和内存中的变量没有被清除有关?还是某种内存泄漏?
我不明白两个不同的用户如何共享一个变量。
显然我不知道 require
ing 一个模块会缓存它并重用它。因此,该模块中的全局变量也将被缓存。
所以这里最好的解决方案是避免使用全局变量并重构代码。但是,如果您需要快速解决方案,您可以使用:
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 不见了。欢乐时光。
我运行一个节点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%),发生这种情况时我找不到任何模式。
用户不必同时进行,可能会有一些时间差(几分钟甚至几小时)。
会不会和内存中的变量没有被清除有关?还是某种内存泄漏?
我不明白两个不同的用户如何共享一个变量。
显然我不知道 require
ing 一个模块会缓存它并重用它。因此,该模块中的全局变量也将被缓存。
所以这里最好的解决方案是避免使用全局变量并重构代码。但是,如果您需要快速解决方案,您可以使用:
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 不见了。欢乐时光。