简体   繁体   English

Node.js中的行为不可靠

[英]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: 问题:

  1. 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). 我尝试将其设置为周期性加载(即使我不知道这种情况下发生的情况)。

  2. Why can't I (sometimes) access my data? 为什么我有时不能访问我的数据?

  3. What can I do to figure out what's wrong? 我该怎么办才能找出问题所在?

  4. 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.jsmodule.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 . 希望这会通知您,这里的问题确实是超出finallycatch 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM