Node.js中的不可靠行为

时间:2015-01-03 16:59:35

标签: javascript node.js underscore.js bluebird knex.js

我有一个Node.js应用程序,在初始化时,从SQL数据库中读取两个表并重建它们在内存中的关系。它们用于同步查找(很少)变化的数据。

问题:即使应用程序报告成功加载数据,有时我也无法访问数据。

代码:

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

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 });
  }
};

用例

if (data.handle) {
  carrier = utils.getCarrierByHandle(data.handle);

  if (_.isEmpty(carrier)) {
    throw new InternalError('Unknown carrier', { handle: data.handle });
  }
}

发生了什么:记录所有错误;一旦我在日志中看到错误(即"未知运营商"),我会检查SQL数据库以查看它是否应该被识别。到目前为止一直如此,所以我检查调试日志以查看数据是否已加载。我总是看到"加载X国家"和#34;加载Y载体"正确的值,没有#34;无法加载常数"或任何其他类型的麻烦。

这种情况发生在我启动应用程序的大约10%时间并且问题仍然存在(即12小时后似乎没有消失)并且似乎无论输入如何都会发生,这让我认为数据没有被正确引用。

问题:

  1. constants.js 有什么问题吗?或者我做的事情显然是错误的?我已尝试将其设置为周期性加载(即使我不知道在这种情况下会发生这种情况)。

  2. 为什么我(有时)无法访问我的数据?

  3. 我该怎么做才能找出错误?

  4. 有什么方法可以解决这个问题吗?还有什么我可以实现所需的行为吗?排除了 constants.js 中的数据的硬编码。

  5. 其他信息:

    • constants.reload()实际上从来没有从 constants.js 之外调用。

    • constants.js 仅在 utils.js 需要 d。

    • utils.js app.js (应用程序条目) require d;所有文件需要 d,然后才

    • SQL访问是通过建立在knex.js和bluebird之上的内部库完成的;到目前为止,它一直非常稳定。

    版本:

    Node.js v0.10.33

    下划线 1.7.0

    bluebird 2.3.11

    knex 0.6.22

3 个答案:

答案 0 :(得分:0)

  

constants.reload()从未实际从外部调用   constants.js。

那是你的问题。 constants.reload()从数据库中读取,这是一个非常有趣的过程。节点的require()是一个同步过程。在constants.js中需要utils.js并且返回module.exports值时,您的数据库查询仍在运行。并且在app.js到达它从utils模块调用方法的点的任何时间点,该查询仍然仍然正在运行,从而导致错误。

你可以说要求utils.js有副作用,需要constants.js,它有执行数据库查询的副作用,它具有同时修改自由变量的副作用{{1} }和_countries

_carriers_countries初始化为未解决的承诺。让_carriers解决它们。使utils.js api异步。

promises.js:

reload()

utils.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; }
};

用例:

getCarrierByHandle: function(handle) {
  // ...

  return constants.carriers().then(function (carriers) {
    return _.findWhere(carriers, { 'handle' : handle });
  });
}

此设计还将消除对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 }); 方法的需求。

或者,您可以在初始化时调用ready并挂起所有可能相关的操作,直到它完成。这种方法也会淘汰constants.reload()方法。

  

我能做些什么来弄清楚什么是错的?

你本可以分析你的日志,并观察到“已装载的X国家”和“已装载的Y载体”有时是在“未知载体”之后写的,这有助于您意识到ready的成功是一种竞争条件。 / p>

答案 1 :(得分:0)

}).finally(function () {
  _ready = true;
});

finally中的代码将始终被调用,无论是否在promise链中抛出错误。此外,永远不会达到您的reload().catch(/* ... */)子句,因为finally会吞下错误。

Geo.Country.find()Geo.Carrier.Descriptor.find()可能会引发错误,_ready仍会设置为true,并且您的国家和运营商未设置的问题会持续存在。

如果您在没有ready调用的情况下设计系统,就不会出现此问题,正如我在上一篇文章中所述。希望这可以告诉您这里的问题真的超出finally吞噬catch。真正的问题是依赖副作用;自由变量的修改导致脆弱的系统,特别是涉及异步时。我强烈建议不要这样做。

答案 2 :(得分:0)

试试这个

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; }
};