【问题标题】:Redis won't retrieve data from cacheRedis 不会从缓存中检索数据
【发布时间】:2020-06-08 09:03:28
【问题描述】:

我正在学习一个教程,并创建了一个 cache.js 文件,该文件采用 mongoose 查询并将其 JSON.stringify 为该查询返回的值的键。目标是缓存它,然后在 app.js 中附加 .cache() mongoose.find()

当前,如果缓存为空,我让它从数据库中执行 GET,然后将其存储在缓存中。我有一个

console.log("CACHE VALUE #2");
console.log(cacheValue1);

确保数据被存储并成功输出数据。这条线有效。但是有了这条线,

console.log("CACHE VALUE #1");
console.log(cacheValue);

cacheValue 为空。

这是为什么呢?

它将值存储在底部,而键永远不会改变,所以我不明白为什么它不会返回数据而不是 null。

所以Cache Value #1 始终为空,Cache Value #2 具有正确的数据。

控制台输出:

GRABBING FROM DB
CLIENT CONNECTION STATUS: true
Setting CACHE to True
ABOUT TO RUN A QUERY
{"$and":[{"auctionType":{"$eq":"publicAuction"}},{"auctionEndDateTime":{"$gte":1582903244869}},{"blacklistGroup":{"$ne":"5e52cca7180a7605ac94648f"}},{"startTime":{"$lte":1582903244869}}],"collection":"listings"}
CACHE VALUE #1
null
CACHE VALUE #2
(THIS IS WHERE ALL MY DATA SHOWS UP)
const mongoose = require('mongoose');
const redis = require('redis');
const util = require('util');
var env = require("dotenv").config({ path: './.env' });

const client = redis.createClient(6380, process.env.REDISCACHEHOSTNAME + '.redis.cache.windows.net', {
  auth_pass: process.env.REDISCACHEKEY,
  tls: { servername: process.env.REDISCACHEHOSTNAME + '.redis.cache.windows.net' }
});


client.get = util.promisify(client.get);


const exec = mongoose.Query.prototype.exec;

mongoose.Query.prototype.cache = function () {
  this.useCache = true;
  console.log("Setting CACHE to True")
  return this;
}

mongoose.Query
  .prototype.exec = async function () {
    if (!this.useCache) {
      console.log("GRABBING FROM DB")
      console.log("CLIENT CONNECTION STATUS: " + client.connected);

      return exec.apply(this, arguments);
    }

    console.log("ABOUT TO RUN A QUERY")
    const key = JSON.stringify(Object.assign({}, this.getQuery(), {
      collection: this.mongooseCollection.name
    }));


    //See if we have a value for 'key' in redis
    console.log(key);
    const cacheValue = await client.get(key);
    console.log("CACHE VALUE #1");
    console.log(cacheValue);
    //If we do, return that
    if (cacheValue) {
      console.log("cacheValue IS TRUE");
      const doc = JSON.parse(cacheValue);
      return Array.isArray(doc)
        ? doc.map(d => new this.model(d))
        : new this.model(doc);
    }

    //Otherwise, issue the query and store the result in redis
    const result = await exec.apply(this, arguments);

    let redisData = JSON.stringify(result);
    //stores the mongoose query result in redis



    await client.set(key, JSON.stringify(redisData)), function (err) {
      console.error(err);

    }
    const cacheValue1 = await client.get(key);
    console.log("CACHE VALUE #2");
    console.log(cacheValue1);




    return result;
  }


【问题讨论】:

  • 您是否使用某种 Web 框架(express、koa、restify)来提供结果,如果是的话,使用某种中间件会更容易实现
  • 我正在使用带有平均堆栈的 Azure Redis,所以也可以表达。我觉得我真的很接近让它工作了。代码用.cache() 调用,像这样pastebin.com/xW1Lzr82
  • 您确定查询在后续运行之间完全没有变化吗?这段代码看起来很好,除了你的密钥非常复杂(你可以散列对象并使用散列作为密钥而不是顺便说一句)。您的密钥似乎包含几个不同的时间戳,您确定这些在查询之间不会改变吗?我会记录请求之间的查询并确保它们没有改变。

标签: node.js asynchronous mongoose redis node-promisify


【解决方案1】:

根据您链接的pastebin,您的查询使用Date.now() 作为其值。这意味着每次运行查询时,时间戳都有不同的值。

因为你的键是实际的查询,并且查询有基于Date.now()的动态值,所以你的键永远不会相同,这就是为什么你以后在缓存中找不到它们,每个查询都生成一个由于Date.now() 的动态值,唯一键。

【讨论】:

  • 我明白你在说什么,这是有道理的。如果我输出console.log(key),它会及时输出当时的日期。我猜这种事情很复杂。你有什么想法可以让一切都很好地融合在一起吗?
  • 您必须更改生成密钥的方式以便它们可重现,或者更改查询密钥的方式。 Redis 的命令比基本的 get 和 set 多得多,但我认为最简单的方法是修复键。您可以更改时间戳(在克隆查询结构之后),然后再将它们作为键保存,以便将它们四舍五入到最接近的分钟左右,但我不确定这是最好的方法。
  • 我脑子里一直在玩一些想法。我在评估它为真或假之后使用正则表达式替换unix时间戳,但我不确定这是否可以预先评估日期并将unix时间戳替换为真/假。我将添加某些缓存策略,例如创建拍卖并在 5 分钟内开始,每 4 分钟清除一次缓存。也有用某种标签替换时间戳的想法。还在头脑风暴中哈哈
  • 我认为你必须在这里问自己两个关于这个特定拍卖查询的重要问题,看看它是否值得缓存。 1) 这些数据多久更改一次? 2) 过时的数据会受到伤害吗?具体针对1,开始和结束时间可以改变吗?如果您确定结束时间不会改变,您可以过滤过期的拍卖客户端。但是,如果您的数据经常更改,并且此查询用于无法接受陈旧数据的地方,那么这可能根本不适合缓存。
  • 尝试将其缓存到一定容量对我来说很重要,这就是我将每 4 分钟使缓存过期的原因。如果拍卖在 5 分钟内开始,那么拍卖将始终在 5 分钟内出现,这将为我节省大量 GET 请求。我正在使用 cosmosdb,它并不便宜。这只是我作为成本预测postimg.cc/DW367ZCn,所以如果我可以将它抵消到它每 4 分钟只有一次 GET 而不是 100/1000 人点击的地方,那么每一点都会很重要。如果我只是摆脱 unix 时间戳,我认为它仍然是独一无二的,足以成为一个键。
【解决方案2】:

我在一个最小的示例中复制了您的代码,并让它按照您的要求工作。它在第一次请求后提供来自 redis 缓存的响应。我刚刚记录了值并在您的代码中发现了一些小错误,您将很容易发现它们(使用文档调用 this.model,修复集合并删除 redis 客户端的最后一个获取)。我不认为这是缓存响应的最佳方式,因为您在每个请求中都将变量设置为 true,并且在使用缓存之前您还可以访问 mongo 和 mongoose。使用中间件可以防止所有这一切,您将带走再多一些毫秒,但它仍然不是最糟糕的方式,所以这是我的最小工作示例。

const http = require('http')
const mongoose = require('mongoose')
const redis = require('redis')
const port = 3000;
const util = require('util');

const client = redis.createClient()
client.get = util.promisify(client.get);
mongoose.connect('mongodb://localhost:27017/testdb', {useNewUrlParser: true});
const exec = mongoose.Query.prototype.exec;
  var Schema = mongoose.Schema;

  var testSchema = new Schema({
    testfield:  String, // String is shorthand for {type: String}
  });


 var Test = mongoose.model('Test', testSchema);

mongoose.Query.prototype.cache = function() {
  this.useCache = true;
  console.log("Setting CACHE to True")
  return this;
}

mongoose.Query
  .prototype.exec = async function () {
    if (!this.useCache) {
      console.log("GRABBING FROM DB")
      console.log("CLIENT CONNECTION STATUS: " + client.connected);

      return exec.apply(this, arguments);
    }

    console.log("ABOUT TO RUN A QUERY")
      console.log("Query ==", this.getQuery())
      console.log("Collection == ", this.mongooseCollection.name);
    const key = JSON.stringify(Object.assign({}, this.getQuery(), {
      collection: this.mongooseCollection.name
    }));


    //See if we have a value for 'key' in redis
    console.log("KEY FROM QUERY AND COLLECTION",key);
    const cacheValue = await client.get(key);
    console.log("CACHE VALUE #1");
    console.log(cacheValue);
    //If we do, return that
    if (cacheValue) {
      console.log("cacheValue IS TRUE");
      const doc = JSON.parse(cacheValue);
        console.log("DOC == ",doc);
      return Array.isArray(doc)
        ? doc.map(d => d)
        : doc
           // return exec( Array.isArray(doc)
       // ? doc.map(d => new this.model(d))
        //: new this.model(doc))
    }

    //Otherwise, issue the query and store the result in redis
    const result = await exec.apply(this, arguments);
   // console.log("EXEC === ", exec);
   // console.log("result from query == ", result);
    let redisData = JSON.stringify(result);
    //stores the mongoose query result in redis

    console.log("REDis data ===", redisData);

    await client.set(key, redisData, function (err) {
      console.error(err);

    })




    return result;
  }

const server = http.createServer(function(req, res) {
    if(req.url === '/'){
        Test.find({}).cache().exec().then(function( docs) {
          console.log("DOCS in response == ", docs);
          res.writeHead(200, { 'Content-Type': 'text/plain' });
          res.end(JSON.stringify(docs))
        })
    }

})


server.listen(port, function() {

    console.log(`Server listening on port ${port}`)
 })

【讨论】:

  • 这不是他们的代码的问题,而是他们用作键的特定查询的问题。在您的示例中,查询是静态的,在他们发布的带有示例查询的粘贴箱中,您会看到他们的查询具有动态 Date.now() 组件。您的解决方案在与他们的查询一起使用时仍然会遇到同样的问题。
  • 是的,你是对的。我发布答案后看到了pastebin。不能使用 Date.now() 作为键,但是有一个 100% 的解决方案
  • 为了解决这个问题,我相信最简单的方法是在声明时获取 Date.now 值并将其分配给变量并将其传递给 cache.js 然后我可以进行字符串替换并将日期替换为""。但我不知道如何将值传递给 mongoose.Query .prototype.exec = async function () { 从 app.js 到 cache.js pastebin.com/MH3nFx5r
猜你喜欢
  • 2020-09-28
  • 1970-01-01
  • 2020-07-16
  • 2013-10-28
  • 2018-08-11
  • 1970-01-01
  • 2014-01-28
  • 2011-10-26
  • 2012-12-10
相关资源
最近更新 更多