UNPKG

@stoplight/moleculer

Version:

Fast & powerful microservices framework for Node.JS

258 lines (225 loc) 5.57 kB
/* * moleculer * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer) * MIT Licensed */ "use strict"; const _ = require("lodash"); const utils = require("../utils"); const BaseCacher = require("./base"); const LRU = require("lru-cache"); const { METRIC } = require("../metrics"); const Lock = require("../lock"); /** * Cacher factory for memory cache * * @class MemoryLRUCacher */ class MemoryLRUCacher extends BaseCacher { /** * Creates an instance of MemoryLRUCacher. * * @param {object} opts * * @memberof MemoryLRUCacher */ constructor(opts) { super(opts); // Cache container this.cache = new LRU({ max: this.opts.max, maxAge: this.opts.ttl ? this.opts.ttl * 1000 : null, updateAgeOnGet: !!this.opts.ttl }); // Async lock this._lock = new Lock(); // Start TTL timer this.timer = setInterval(() => { /* istanbul ignore next */ this.checkTTL(); }, 30 * 1000); this.timer.unref(); // Set cloning this.clone = this.opts.clone === true ? _.cloneDeep : this.opts.clone; } /** * Initialize cacher * * @param {any} broker * * @memberof MemoryLRUCacher */ init(broker) { super.init(broker); this.connected = true; broker.localBus.on("$transporter.connected", () => { // Clear all entries after transporter connected. Maybe we missed some "cache.clear" events. return this.clean(); }); if (this.opts.lock && this.opts.lock.enabled !== false && this.opts.lock.staleTime) { /* istanbul ignore next */ this.logger.warn("setting lock.staleTime with MemoryLRUCacher is not supported."); } } /** * Close cacher * * @memberof MemoryLRUCacher */ close() { clearInterval(this.timer); return Promise.resolve(); } /** * Get data from cache by key * * @param {any} key * @returns {Promise} * * @memberof MemoryLRUCacher */ get(key) { this.logger.debug(`GET ${key}`); this.metrics.increment(METRIC.MOLECULER_CACHER_GET_TOTAL); const timeEnd = this.metrics.timer(METRIC.MOLECULER_CACHER_GET_TIME); if (this.cache.has(key)) { this.logger.debug(`FOUND ${key}`); this.metrics.increment(METRIC.MOLECULER_CACHER_FOUND_TOTAL); let item = this.cache.get(key); const res = this.clone ? this.clone(item) : item; timeEnd(); return this.broker.Promise.resolve(res); } else { timeEnd(); } return this.broker.Promise.resolve(null); } /** * Save data to cache by key * * @param {String} key * @param {any} data JSON object * @param {Number} ttl Optional Time-to-Live * @returns {Promise} * * @memberof MemoryLRUCacher */ set(key, data, ttl) { this.metrics.increment(METRIC.MOLECULER_CACHER_SET_TOTAL); const timeEnd = this.metrics.timer(METRIC.MOLECULER_CACHER_SET_TIME); if (ttl == null) ttl = this.opts.ttl; data = this.clone ? this.clone(data) : data; this.cache.set(key, data, ttl ? ttl * 1000 : null); timeEnd(); this.logger.debug(`SET ${key}`); return this.broker.Promise.resolve(data); } /** * Delete a key from cache * * @param {string|Array<string>} key * @returns {Promise} * * @memberof MemoryLRUCacher */ del(keys) { this.metrics.increment(METRIC.MOLECULER_CACHER_DEL_TOTAL); const timeEnd = this.metrics.timer(METRIC.MOLECULER_CACHER_DEL_TIME); keys = Array.isArray(keys) ? keys : [keys]; keys.forEach(key => { this.cache.del(key); this.logger.debug(`REMOVE ${key}`); }); timeEnd(); return this.broker.Promise.resolve(); } /** * Clean cache. Remove every key by match * @param {string|Array<string>} match string. Default is "**" * @returns {Promise} * * @memberof MemoryLRUCacher */ clean(match = "**") { this.metrics.increment(METRIC.MOLECULER_CACHER_CLEAN_TOTAL); const timeEnd = this.metrics.timer(METRIC.MOLECULER_CACHER_CLEAN_TIME); const matches = Array.isArray(match) ? match : [match]; this.logger.debug(`CLEAN ${matches.join(", ")}`); this.cache.keys().forEach(key => { if (matches.some(match => utils.match(key, match))) { this.logger.debug(`REMOVE ${key}`); this.cache.del(key); } }); timeEnd(); return this.broker.Promise.resolve(); } /** * Get data and ttl from cache by key. * * @param {string|Array<string>} key * @returns {Promise} * * @memberof MemoryLRUCacher */ getWithTTL(key) { // There are no way to get the ttl of LRU cache :( return this.get(key).then(data => { return { data, ttl: null }; }); } /** * Acquire a lock * * @param {string|Array<string>} key * @param {Number} ttl Optional Time-to-Live * @returns {Promise} * * @memberof MemoryLRUCacher */ lock(key, ttl) { return this._lock.acquire(key, ttl).then(() => { return () => this._lock.release(key); }); } /** * Try to acquire a lock * * @param {string|Array<string>} key * @param {Number} ttl Optional Time-to-Live * @returns {Promise} * * @memberof MemoryLRUCacher */ tryLock(key, ttl) { if (this._lock.isLocked(key)) { return this.broker.Promise.reject(new Error("Locked.")); } return this._lock.acquire(key, ttl).then(() => { return () => this._lock.release(key); }); } /** * Check & remove the expired cache items * * @memberof MemoryLRUCacher */ checkTTL() { this.cache.prune(); } /** * Return all cache keys with available properties (ttl, lastUsed, ...etc). * * @returns Promise<Array<Object>> */ getCacheKeys() { return Promise.resolve( this.cache.keys().map(key => { return { key }; }) ); } } module.exports = MemoryLRUCacher;