跳转至

node-cache-manager

https://github.com/BryanDonovan/node-cache-manager

build status Coverage Status

一个用于 nodejs 的缓存模块,允许在缓存、分级缓存和一致的接口中轻松包装函数。

特性

  • 在缓存中包装任何函数的简单方法。
  • 分级缓存——数据存储在每个缓存中,并首先从最高优先级的缓存中获取。
  • 使用任何你想要的缓存,只要它有相同的 API。
  • 通过mochaistanbulsinon实现 100%的测试覆盖率。

Express.js 例子

参见Express.js cache-manager 示例应用了解如何在你的应用中使用 node-cache-manager

安装

npm install cache-manager

存储引擎

概述

wrap 函数

首先,它包含一个 wrap 函数,可以让你在缓存中包装任何函数。 (注意,这是受到node-caching的启发。) 这可能就是您正在寻找的功能。举个例子,你可能需要这样做:

function getCachedUser(id, cb) {
  memoryCache.get(id, function (err, result) {
    if (err) {
      return cb(err);
    }

    if (result) {
      return cb(null, result);
    }

    getUser(id, function (err, result) {
      if (err) {
        return cb(err);
      }
      memoryCache.set(id, result);
      cb(null, result);
    });
  });
}

... 你可以使用wrap函数:

function getCachedUser(id, cb) {
  memoryCache.wrap(
    id,
    function (cacheCallback) {
      getUser(id, cacheCallback);
    },
    { ttl: ttl },
    cb
  );
}

内存缓存

第二node-cache-manager 具有内置的内存缓存(使用node-lru-cache),与你期望在大多数缓存中的标准函数:

    set(key, val, {ttl: ttl}, cb) // * see note below
    get(key, cb)
    del(key, cb)
    mset(key1, val1, key2, val2, {ttl: ttl}, cb) // set several keys at once
    mget(key1, key2, key3, cb) // get several keys at once
    // * Note that depending on the underlying store, you may be able to pass the
    // ttl as the third param, like this:
    set(key, val, ttl, cb)
    // ... or pass no ttl at all:
    set(key, val, cb)

分级缓存

第三node-cache-manager 允许你设置分级缓存策略。 在大多数情况下,这可能是有限的使用,但想象一下这样一个场景:你期望大量的流量,并不想每次请求都冲击你的主缓存(如 Redis)。 您决定将最常见的请求数据存储在内存缓存中,可能具有非常短的超时时间和/或较小的数据大小限制。 但你还是想把数据存储在 Redis 中,以备备份,以及处理那些不像你想存储在内存中的请求那样常见的请求。 这是 node-cache-manager 可以轻松且透明地处理的。

设置多个键

第四,它允许你获得和设置多个键,一次为缓存存储,支持它。 这意味着当获得多个键时,它将从最高优先级的开始通过不同的缓存(见下面的多存储),并合并它在每个级别上找到的值。

用法示例

参见下面的示例和示例目录中的示例。 参见 examples/redis_example ,了解如何使用连接池实现 Redis 缓存存储。

单一的存储

var cacheManager = require("cache-manager");
var memoryCache = cacheManager.caching({ store: "memory", max: 100, ttl: 10 /*seconds*/ });
var ttl = 5;
// Note: callback is optional in set() and del().
// Note: memory cache clones values before setting them unless `shouldCloneBeforeSet` is set to false

memoryCache.set("foo", "bar", { ttl: ttl }, function (err) {
  if (err) {
    throw err;
  }

  memoryCache.get("foo", function (err, result) {
    console.log(result);
    // >> 'bar'
    memoryCache.del("foo", function (err) {});
  });
});

function getUser(id, cb) {
  setTimeout(function () {
    console.log("Returning user from slow database.");
    cb(null, { id: id, name: "Bob" });
  }, 100);
}

var userId = 123;
var key = "user_" + userId;

// Note: ttl is optional in wrap()
memoryCache.wrap(
  key,
  function (cb) {
    getUser(userId, cb);
  },
  { ttl: ttl },
  function (err, user) {
    console.log(user);

    // Second time fetches user from memoryCache
    memoryCache.wrap(
      key,
      function (cb) {
        getUser(userId, cb);
      },
      function (err, user) {
        console.log(user);
      }
    );
  }
);

// Outputs:
// Returning user from slow database.
// { id: 123, name: 'Bob' }
// { id: 123, name: 'Bob' }

ttl 也可以通过传入一个函数来动态计算。例如,

var opts = {
    ttl: function(user) {
        if (user.id === 1) {
            return 0.1;
        } else {
            return 0.5;
        }
    }
};

memoryCache.wrap(key, function(cb) {
    getUser(userId, cb);
}, opts, function(err, user) {
    console.log(user);
}

你可以一次拿几个键。 请注意,这将返回它在缓存中找到的任何记录,由用户根据所提供的键检查结果,并调用底层数据存储来填充缺失的记录。 在实践中,如果你只是使用wrap函数在缓存中设置这些记录,这应该不是一个大问题。

附注: 理想情况下, wrap 函数将从缓存中获得它所能得到的,并从数据存储中填充缺失的记录,但我无法想到一种适用于所有情况的方法来做到这一点。 另一种选择是,如果找到了所有记录,则只返回缓存中的数据,但这将破坏多缓存。

更多信息请参见caching.unit.js中的单元测试。

例子:

var key1 = "user_1";
var key2 = "user_1";

memoryCache.wrap(
  key1,
  key2,
  function (cb) {
    getManyUser([key1, key2], cb);
  },
  function (err, users) {
    console.log(users[0]);
    console.log(users[1]);
  }
);

使用 mset()和 mget()设置/获取几个键的示例

memoryCache.mset("foo", "bar", "foo2", "bar2", { ttl: ttl }, function (err) {
  if (err) {
    throw err;
  }

  memoryCache.mget("foo", "foo2", function (err, result) {
    console.log(result);
    // >> ['bar', 'bar2']

    // Delete keys with del() passing arguments...
    memoryCache.del("foo", "foo2", function (err) {});

    // ...passing an Array of keys
    memoryCache.del(["foo", "foo2"], function (err) {});
  });
});

例子中使用的承诺

1
2
3
4
5
6
7
memoryCache
  .wrap(key, function () {
    return getUserPromise(userId);
  })
  .then(function (user) {
    console.log("User:", user);
  });

如果您使用的 Node 版本不包括本机承诺,您可以在传递给缓存模块的选项中指定承诺依赖项。例如,

var Promise = require("es6-promise").Promise;
cache = caching({ store: store, promiseDependency: Promise });

使用异步/等待示例

1
2
3
4
5
6
7
try {
  let user = await memoryCache.wrap(key, function () {
    return getUserPromise(userId);
  });
} catch (err) {
  // error handling
}

提示:应该用try - catch封装await调用来处理promise错误。

Express 应用使用示例

(参见Express.js 缓存管理器示例应用).

function respond(res, err, data) {
  if (err) {
    res.json(500, err);
  } else {
    res.json(200, data);
  }
}

app.get("/foo/bar", function (req, res) {
  var cacheKey = "foo-bar:" + JSON.stringify(req.query);
  var ttl = 10;
  memoryCache.wrap(
    cacheKey,
    function (cacheCallback) {
      DB.find(req.query, cacheCallback);
    },
    { ttl: ttl },
    function (err, result) {
      respond(res, err, result);
    }
  );
});

定制店

你可以通过创建一个与内置内存存储相同的 API(如 redis 或 memcached 存储)来使用自己的自定义存储。 要使用自己的存储,只需传入它的一个实例。

E.g.,

var myStore = require("your-homemade-store");
var cache = cacheManager.caching({ store: myStore });

Multi-Store

var multiCache = cacheManager.multiCaching([memoryCache, someOtherCache]);
userId2 = 456;
key2 = "user_" + userId;
ttl = 5;

// Sets in all caches.
// The "ttl" option can also be a function (see example below)
multiCache.set("foo2", "bar2", { ttl: ttl }, function (err) {
  if (err) {
    throw err;
  }

  // Fetches from highest priority cache that has the key.
  multiCache.get("foo2", function (err, result) {
    console.log(result);
    // >> 'bar2'

    // Delete from all caches
    multiCache.del("foo2");
  });
});

// Set the ttl value by context depending on the store.
function getTTL(data, store) {
  if (store === "redis") {
    return 6000;
  }
  return 3000;
}

// Sets multiple keys in all caches.
// You can pass as many key,value pair as you want
multiCache.mset("key", "value", "key2", "value2", { ttl: getTTL }, function (err) {
  if (err) {
    throw err;
  }

  // mget() fetches from highest priority cache.
  // If the first cache does not return all the keys,
  // the next cache is fetched with the keys that were not found.
  // This is done recursively until either:
  // - all have been found
  // - all caches has been fetched
  multiCache.mget("key", "key2", function (err, result) {
    console.log(result[0]);
    console.log(result[1]);
    // >> 'bar2'
    // >> 'bar3'

    // Delete from all caches
    multiCache.del("key", "key2");
    // ...or with an Array
    multiCache.del(["key", "key2"]);
  });
});

// Note: options with ttl are optional in wrap()
multiCache.wrap(
  key2,
  function (cb) {
    getUser(userId2, cb);
  },
  { ttl: ttl },
  function (err, user) {
    console.log(user);

    // Second time fetches user from memoryCache, since it's highest priority.
    // If the data expires in the memory cache, the next fetch would pull it from
    // the 'someOtherCache', and set the data in memory again.
    multiCache.wrap(
      key2,
      function (cb) {
        getUser(userId2, cb);
      },
      function (err, user) {
        console.log(user);
      }
    );
  }
);

// Multiple keys
multiCache.wrap(
  "key1",
  "key2",
  function (cb) {
    getManyUser(["key1", "key2"], cb);
  },
  { ttl: ttl },
  function (err, users) {
    console.log(users[0]);
    console.log(users[1]);
  }
);

指定在wrap函数中缓存什么

cachingmulticaching模块都允许你传入一个名为isCacheableValue的回调函数,wrap函数会根据从缓存或包装函数返回的每个值来调用这个回调函数。 这让你可以通过wrap来指定哪些值应该缓存,哪些值不应该缓存。 如果函数返回 true ,它将被存储在缓存中。 默认情况下,缓存会缓存除undefined之外的所有内容。

注意:cachingmulticaching中的set函数不使用isCacheableValue

例如,如果你不想缓存falsenull,你可以像这样传入一个函数:

1
2
3
var isCacheableValue = function (value) {
  return value !== null && value !== false && value !== undefined;
};

然后像这样将它传递给' caching ':

var memoryCache = cacheManager.caching({ store: "memory", isCacheableValue: isCacheableValue });

然后像这样将它传递给“multicaching”:

1
2
3
var multiCache = cacheManager.multiCaching([memoryCache, someOtherCache], {
  isCacheableValue: isCacheableValue,
});

在后台刷新缓存键

cachingmulticaching模块都支持在使用wrap函数时在后台刷新过期缓存键的机制。 这可以通过在创建缓存存储时添加“refreshThreshold”属性来实现。

如果设置了refreshThreshold并且ttl方法对已用存储区可用,则从缓存中获取一个值后将检查 ttl 。 如果剩余的 TTL 小于refreshThreshold,系统将生成一个后台 worker 来更新该值,遵循与标准抓取相同的规则。 同时,系统将返回旧值,直到过期。

在多缓存的情况下,用于刷新的存储区是首先找到键的存储区(优先级最高)。 然后将在所有存储中设置该值。

NOTES:

  • 在多缓存的情况下,将被检查刷新的存储是一个键将被首先找到(最高优先级)。
  • 如果阈值很低,工作函数很慢,键可能会过期,你可能会遇到一个更新值的竞赛条件。
  • 后台刷新机制目前不支持提供多键 wrap 功能。
  • 缓存存储需要提供ttl方法。

例如,像这样将 refreshThreshold 传递给caching:

1
2
3
var redisStore = require("cache-manager-ioredis");

var redisCache = cacheManager.caching({ store: redisStore, refreshThreshold: 3, isCacheableValue: isCacheableValue });

当从 Redis 中检索到一个剩余 TTL < 3sec 的值时,该值将在后台更新。

开发环境

你可以禁用真正的缓存,但仍然可以通过设置“none”存储来实现所有的回调功能。

文档

生成 JSDOC 3 文档:

make docs

测试

要运行测试,请首先运行:

npm install -d

运行测试和 JShint:

make

贡献

如果你想为项目做出贡献,请分叉它,并向我们发送一个拉请求。 请为任何新功能或 bug 修复添加测试。 同样在提交 pull 请求之前运行' make '。

许可证

node-cache-manager 是在 MIT 许可证下授权的。