[英]Unreliable behaviour in Node.js
I have a Node.js application that, upon initialisation, reads two tables from an SQL database and reconstructs their relationship in memory. 我有一个Node.js应用程序,该应用程序在初始化时会从SQL数据库读取两个表,并在内存中重建它们之间的关系。 They're used for synchronously looking up data that changes (very) infrequently.
它们用于同步查找很少(很少)更改的数据。
Problem : Sometimes I can't access the data, even though the application reports successfully loading it. 问题 :即使应用程序报告成功加载数据,有时我也无法访问数据。
Code: 码:
constants.js constants.js
module.exports = {
ready: function () { return false; }
};
var log = sysLog('core', 'constants')
, Geo = require('../models/geo.js');
var _ready = false
, _countries = []
, _carriers = [];
function reload() {
_ready = false;
var index = Object.create(null);
return Geo.Country.find().map(function (country) {
var obj = country.toPlainObject()
, id = obj.id;
delete obj.id;
index[id] = obj;
return Object.freeze(obj);
}).then(function (countries) {
log.debug('Loaded ' + countries.length + ' countries');
_countries = countries;
return Geo.Carrier.Descriptor.find().map(function (carrier) {
var obj = carrier.toPlainObject();
if (obj.country) {
obj.country = index[obj.country];
}
return Object.freeze(obj);
}).then(function (carriers) {
log.debug('Loaded ' + carriers.length + ' carriers');
_carriers = carriers;
});
}).finally(function () {
_ready = true;
});
}
reload().catch(function (err) {
log.crit({ message: 'Could not load constants', reason: err });
process.exit(-42);
}).done();
module.exports = {
reload : reload,
ready : function () { return _ready; },
countries : function () { return _countries; },
carriers : function () { return _carriers; }
};
utils.js utils.js
var log = sysLog('core', 'utils')
, constants = require('./constants');
module.exports = {
getCountryByISO: function(iso) {
if (!iso) {
return;
}
if ('string' != typeof iso) {
throw new Error('getCountryByISO requires a string');
}
if (!constants.ready()) {
throw new UnavailableError('Try again in a few seconds');
}
switch (iso.length) {
case 2:
return _.findWhere(constants.countries(), { 'iso2' : iso.toUpperCase() });
case 3:
return _.findWhere(constants.countries(), { 'iso3' : iso.toUpperCase() });
default:
throw new Error('getCountryByISO requires a 2 or 3 letter ISO code');
}
},
getCarrierByCode: function(code) {
if (!code) {
return;
}
if ('string' != typeof code) {
throw new Error('getCarrierByCode requires a string');
}
if (!constants.ready()) {
throw new UnavailableError('Try again in a few seconds');
}
return _.findWhere(constants.carriers(), { 'code' : code });
},
getCarrierByHandle: function(handle) {
if (!handle) {
return;
}
if ('string' != typeof handle) {
throw new Error('getCarrierByHandle requires a string');
}
if (!constants.ready()) {
throw new UnavailableError('Try again in a few seconds');
}
return _.findWhere(constants.carriers(), { 'handle' : handle });
}
};
Use case 用例
if (data.handle) {
carrier = utils.getCarrierByHandle(data.handle);
if (_.isEmpty(carrier)) {
throw new InternalError('Unknown carrier', { handle: data.handle });
}
}
What's going on: All errors are logged; 发生了什么:记录了所有错误; as soon as I see an error (ie "Unknown carrier") in the logs, I check the SQL database to see if it should've been recognised.
一旦我在日志中看到错误(即“未知运营商”),就会检查SQL数据库以查看是否应该识别它。 That has always been the case so far, so I check the debug log to see if data was loaded.
到目前为止,情况一直如此,因此我检查了调试日志以查看是否已加载数据。 I always see "Loaded X countries" and "Loaded Y carriers" with correct values and no sign of "Could not load constants" or any other kind of trouble.
我总是看到“已加载X个国家”和“已加载Y个运营商”具有正确的值,并且没有“无法加载常量”或其他任何麻烦的迹象。
This happens around 10% of the time I start the application and the problem persists (ie didn't seem to go away after 12+ hours) and seems to occur regardless of input, leading me to think that the data isn't referenced correctly. 这种情况发生在我启动应用程序的大约10%的时间中,并且问题仍然存在(即似乎在12个多小时后都没有消失),并且似乎无论输入什么都发生了,这使我认为数据没有正确引用。
Questions: 问题:
Is there something wrong in constants.js or am I doing something very obviously wrong? constants.js中有问题吗,还是我做的很明显是错误的事情? I've tried setting it up for cyclical loading (even though I am not aware of that happening in this case).
我尝试将其设置为周期性加载(即使我不知道这种情况下发生的情况)。
Why can't I (sometimes) access my data? 为什么我有时不能访问我的数据?
What can I do to figure out what's wrong? 我该怎么办才能找出问题所在?
Is there any way I can work around this? 有什么办法可以解决这个问题? Is there anything else I could to achieve the desired behaviour?
我还能实现期望的行为吗? Hard-coding the data in constants.js is excluded.
排除在constants.js中对数据进行硬编码。
Additional information: 附加信息:
constants.reload()
is never actually called from outside of constants.js . constants.reload()
实际上从未从constants.js外部调用。
constants.js is require d only in utils.js . constants.js仅在utils.js中是require d。
utils.js is require d in app.js (application entry); utils.js是需要在app.js(应用项)d; all files require d before it do not require it.
所有文件都需要 d,然后才不需要它。
SQL access is done through an in-house library built on top of knex.js and bluebird; SQL访问通过在knex.js和bluebird之上构建的内部库完成; so far it's been very stable.
到目前为止,它非常稳定。
Versions: 版本:
Node.js v0.10.33 Node.js v0.10.33
underscore 1.7.0 下划线 1.7.0
bluebird 2.3.11 蓝鸟 2.3.11
knex 0.6.22 纳克斯 0.6.22
constants.reload() is never actually called from outside of constants.js.
constants.reload()实际上从未从constants.js外部调用。
That's your issue. 那是你的问题。
constants.reload()
reads from a database, which is an aysnchronous process. constants.reload()
从数据库读取数据,这是一个异步过程。 Node's require()
is a synchronous process. 节点的
require()
是一个同步过程。 At the time constants.js
is required in utils.js
and the module.exports
value is returned, your database query is still running. 当时
constants.js
需要在utils.js
和module.exports
返回值,你的数据库查询仍在运行。 And at whatever point in time that app.js
reaches the point where it calls a method from the utils module, that query could still be running, resulting in the error. 而且在
app.js
到达它从utils模块调用方法的任何时间点,该查询仍然可以运行,从而导致错误。
You could say that requiring utils.js has the side-effect of requiring constants.js, which has the side-effect of executing a database query, which has the side-effect of concurrently modifying the free variables _countries
and _carriers
. 您可以说,要求utils.js具有要求constants.js的副作用,后者具有执行数据库查询的副作用,该副作用具有同时修改自由变量
_countries
和_carriers
。
Initialize _countries
and _carriers
as unresolved promises. 初始化
_countries
和_carriers
为未解决的承诺。 Have reload()
resolve them. 让
reload()
解决它们。 Make the utils.js api async. 使utils.js api异步。
promises.js: promises.js:
// ...
var Promise = require('bluebird');
var countriesResolve
, carriersResolve;
var _ready = false
, _countries = new Promise(function (resolve) {
countriesResolve = resolve;
})
, _carriers = new Promise(function (resolve) {
carriersResolve = resolve;
});
function reload() {
_ready = false;
var index = Object.create(null);
return Geo.Country.find().map(function (country) {
// ...
}).then(function (countries) {
log.debug('Loaded ' + countries.length + ' countries');
countriesResolve(countries);
return Geo.Carrier.Descriptor.find().map(function (carrier) {
// ...
}).then(function (carriers) {
log.debug('Loaded ' + carriers.length + ' carriers');
carriersResolve(carriers);
});
}).finally(function () {
_ready = true;
});
}
reload().catch(function (err) {
log.crit({ message: 'Could not load constants', reason: err });
process.exit(-42);
}).done();
module.exports = {
reload : reload,
ready : function () { return _ready; },
countries : function () { return _countries; },
carriers : function () { return _carriers; }
};
utils.js utils.js
getCarrierByHandle: function(handle) {
// ...
return constants.carriers().then(function (carriers) {
return _.findWhere(carriers, { 'handle' : handle });
});
}
Use case: 用例:
utils.getCarrierByHandle(data.handle).then(function (carrier) {
if (_.isEmpty(carrier)) {
throw new InternalError('Unknown carrier', { handle: data.handle });
}
}).then(function () {
// ... next step in application logic
});
This design will also eliminate the need for a ready
method. 这种设计还将消除对
ready
方法的需求。
Alternatively, you could call constants.reload()
on initialization and hang all possibly-dependent operations until it completes. 另外,您可以在初始化时调用
constants.reload()
并挂起所有可能依赖的操作,直到完成。 This approach would also obsolete the ready
method. 这种方法也将淘汰
ready
方法。
What can I do to figure out what's wrong?
我该怎么办才能找出问题所在?
You could have analyzed your logs and observed that "Loaded X countries" and "Loaded Y carriers" were sometimes written after "Unknown carrier", helping you realize that the success of utils.getCarrierByHandle()
was a race condition. 您可能已经分析了日志,并观察到有时在“未知承运人”之后写有“已加载X个国家”和“已加载Y个运营商”,这有助于您认识到
utils.getCarrierByHandle()
的成功是竞争的条件。
}).finally(function () {
_ready = true;
});
Code in a finally
will always get called, regardless of if an error was thrown up the promise chain. 无论是否在承诺链中抛出错误,
finally
代码始终都会被调用。 Additionally, your reload().catch(/* ... */)
clause will never be reached, because finally
swallows the error. 此外,将永远不会到达您的
reload().catch(/* ... */)
子句,因为finally
吞噬该错误。
Geo.Country.find()
or Geo.Carrier.Descriptor.find()
could throw an error, and _ready
would still be set to true
, and the problem of your countries and carriers not being set would persist. Geo.Country.find()
或Geo.Carrier.Descriptor.find()
可能会引发错误,并且_ready
仍将设置为true
,而您的国家和运营商未设置的问题将继续存在。
This problem would not have occurred if you had designed your system without a ready
call, as I described in my previous post. 如您在上一篇文章中所述,如果您在设计系统时没有
ready
电话就不会发生此问题。 Hopefully this informs you that the issue here is really beyond finally
swallowing a catch
. 希望这会通知您,这里的问题确实是超出
finally
吞catch
。 The real issue is relying on side-effects ; 真正的问题在于副作用 。 the modification of free variables results in brittle systems, especially when asynchrony is involved.
自由变量的修改会导致系统变脆,特别是在涉及异步时。 I highly recommend against it.
我强烈建议您反对。
Try this 尝试这个
var log = sysLog('core', 'constants');
var Geo = require('../models/geo.js');
var index;
var _countries;
var _carriers;
function reload() {
index = Object.create(null);
_countries = Geo.Country.find().map(function (country) {
var obj = country.toPlainObject();
var id = obj.id;
delete obj.id;
index[id] = obj;
return Object.freeze(obj);
});
_carriers = _countries.then(function(countries) {
return Geo.Carrier.Descriptor.find().map(function (carrier) {
var obj = carrier.toPlainObject();
if (obj.country) {
obj.country = index[obj.country];
}
return Object.freeze(obj);
});
});
return _carriers;
}
reload().done();
module.exports = {
reload : reload,
countries : function () { return _countries; },
carriers : function () { return _carriers; }
};
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.