UNPKG

toshihiko-memcached

Version:

The memcached support for Toshihiko as an addon.

268 lines (229 loc) 7.1 kB
/** * XadillaX created at 2015-03-05 16:26:20 * * Copyright (c) 2015 Huaban.com, all rights * reserved */ "use strict"; var 囍 = require("lodash"); var _Memcached = require("memcached"); var EventEmitter = require("events").EventEmitter; var util = require("util"); var Scarlet = require("scarlet-task"); var async = require("async"); var MEMCACHED_COMMAND_MAX_LENGTH = 250; /** * Toshihiko Memcached * @param {Object} servers refer to https://github.com/3rd-Eden/node-memcached#server-locations * @param {Object} options refer to https://github.com/3rd-Eden/node-memcached#options, and you may give a prefix * @constructor */ var Memcached = function(servers, options) { EventEmitter.call(this); this.servers = servers; this.options = options; this.prefix = (options && options.prefix) ? options.prefix : ""; if(options) { delete options.prefix; } this.memcached = new _Memcached(this.servers, this.options); var self = this; this.memcached.on("failure", function(details) { self.emit("failure", details); }); this.memcached.on("reconnecting", function(details) { self.emit("reconnecting", details); }); if(options && options.customizeKey) { this._getKey = options.customizeKey.bind(this); } }; util.inherits(Memcached, EventEmitter); /** * set customize key function * @param {Function} func the function */ Memcached.prototype.setCustomizeKeyFunc = function(func) { this._getKey = func.bind(this); }; /** * get key value * @param {String} dbName the database name * @param {String} tableName the table name * @param {String|Object} key the primary key * @param {String} the keyname in memcached */ Memcached.prototype._getKey = function(dbName, tableName, key) { if(typeof key !== "object") { return this.prefix + dbName + ":" + tableName + ":" + key; } // get primary keys var keys = 囍.keys(key); if(!keys.length) { return this.prefix + dbName + ":" + tableName; } else if(keys.length === 1) { return this.prefix + dbName + ":" + tableName + ":" + key[keys[0]]; } var minlen = 1; for(var i = 0; i < keys.length; i++) { for(var j = i + 1; j < keys.length; j++) { var ml = Math.min(keys[i].length, keys[j].length); for(var k = 0; k < ml; k++) { if(keys[i][k] !== keys[j][k]) { if(k > minlen) { minlen = k + 1; } break; } } if(k === ml && k > minlen) { minlen = k + 1; } } } // sort keys keys.sort(); // add keys to memcached key var base = this.prefix + dbName + ":" + tableName; for(var i = 0; i < keys.length; i++) { base += ":"; base += keys[i].substr(0, minlen); base += key[keys[i]]; } return base; }; /** * _getKeys * * @param {String} dbName database name * @param {String} tableName table name * @param {Array} keys the primary keys * @return {Array} return the keynames in memcached */ Memcached.prototype._getKeys = function(dbName, tableName, keys) { var self = this; return keys.map(function(key) { return self._getKey(dbName, tableName, key); }); }; /** * delete one cached data * @param {String} dbName database name * @param {String} tableName table name * @param {String|Object} key primary key * @param {Function} callback the callback function */ Memcached.prototype.deleteData = function(dbName, tableName, key, callback) { key = this._getKey(dbName, tableName, key); this.memcached.del(key, callback); }; /** * delete one or more cached data * @param {String} dbName database name * @param {String} tableName table name * @param {Array} keys primary keys array * @param {Function} callback the callback function */ Memcached.prototype.deleteKeys = function(dbName, tableName, keys, callback) { var self = this; async.eachLimit(keys, 10, function(key, callback) { self.deleteData(dbName, tableName, key, callback); }, function(err) { callback(err); }); }; /** * set cached data * @param {String} dbName database name * @param {String} tableName table name * @param {String|Object} key the primary key * @param {Object} data the data * @param {Function} callback the callback function */ Memcached.prototype.setData = function(dbName, tableName, key, data, callback) { key = this._getKey(dbName, tableName, key); this.memcached.set(key, data, 0, callback); }; /** * get crowd of data * @param {Array} keys the memcached keys * @param {Function} callback the callback function */ Memcached.prototype._getCrowdOfData = function(keys, callback) { this.memcached.getMulti(keys, function(err, data) { if(err) { return callback(err); } var result = []; for(var i = 0; i < keys.length; i++) { if(data[keys[i]]) { result.push(data[keys[i]]); } } callback(undefined, result); }); }; /** * get data * @param {String} dbName database name * @param {String} tableName table name * @param {Array|String|Object} keys the primary key(s) * @param {Function} callback the callback function */ Memcached.prototype.getData = function(dbName, tableName, keys, callback) { var self = this; if(!util.isArray(keys)) { keys = [keys]; } // generate memcached keys keys = this._getKeys(dbName, tableName, keys); // no data to find if(!keys.length) { return callback(undefined, []); } // only one data to find if(keys.length === 1) { this.memcached.get(keys[0], function(err, data) { if(err) { return callback(err); } if(undefined === data) { return callback(undefined, []); } callback(undefined, [ data ]); }); return; } // otherwise... var crowd = [ [] ]; var str = "get"; for(var i = 0; i < keys.length; i++) { str += " "; str += keys[i]; if(str.length > MEMCACHED_COMMAND_MAX_LENGTH) { crowd.push([]); str = "get"; str += " "; str += keys[i]; } crowd[crowd.length - 1].push(keys[i]); } var errs = []; var result = []; var scarlet = new Scarlet(1); var scarletProcessor = function(TO) { var keys = TO.task; self._getCrowdOfData(keys, function(err, data) { return err ? errs.push(err) : Array.prototype.push.apply(result, data), scarlet.taskDone(TO); }); }; // get crowd of data one by one for(var i = 0; i < crowd.length; i++) { scarlet.push(crowd[i], scarletProcessor); } // the `done` function scarlet.afterFinish(crowd.length, function() { return callback(errs.length ? errs[0] : undefined, result); }); }; module.exports = Memcached;