Redis无法从缓存中检索数据。

7
我正在跟随一个教程,我创建了一个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 始终为 null,而 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)提供你的结果?如果是的话,使用某种中间件来实现将会更加容易。 - C.Gochev
我正在使用 Azure Redis 和 Mean Stack,所以肯定也包括 Express。我感觉离让它工作很近了。代码是通过.cache()这样的方式调用的:https://pastebin.com/xW1Lzr82 - user6680
你确定查询在后续运行中不会发生任何变化吗?这段代码看起来很好,除了你的键非常复杂(你可以将对象哈希并使用哈希作为键)。你的键似乎包含几个不同的时间戳,你确定这些在查询之间不会改变吗?我建议在请求之间记录查询并确保它们没有改变。 - Jon Church
2个回答

1
基于你所提供的pastebin,你的查询使用Date.now()作为其值。这意味着每次运行查询时,时间戳都不同。
由于你的键是实际查询,而查询具有基于Date.now()的动态值,因此你的键永远不会相同,这就是为什么稍后不能在缓存中找到它们的原因,每个查询都生成一个唯一的键,因为Date.now()的动态值。

我理解你的意思,这很有道理。如果我输出console.log(key),它会输出那一刻的日期。这可能会使事情变得复杂。你有什么想法可以让所有东西和谐地协作吗? - user6680
你需要改变生成密钥的方式,使其可重现,或者改变查询密钥的方式。Redis有比基本的get和set更多的命令,但我认为最简单的方法是修复密钥。在将时间戳持久化为密钥之前(克隆查询结构后),您可以将它们更改为最接近一分钟左右的四舍五入值,但我不确定这是否是最佳方法。 - Jon Church
我一直在脑海中琢磨着几个想法。其中一个是使用正则表达式,在将Unix时间戳评估为真或假之后进行替换,但我不确定在预先评估日期并用真/假替换Unix时间戳是否可行。我打算添加某些缓存策略,比如创建一个拍卖,并在5分钟后开始,并且每4分钟清除一次缓存。我还有一个用某种标记替换时间戳的想法。还在头脑风暴中哈哈。 - user6680
我认为你需要就这个拍卖查询问题问自己两个重要问题,以确定它是否值得缓存。第一,这些数据有多频繁地变动?第二,过期的数据会不会有影响?具体来说,对于问题1,起始时间和结束时间是否会发生变化?如果你确定结束时间不会改变,可以在客户端过滤已过期的拍卖。但是,如果你的数据经常变动并且这个查询在某个地方被使用,而过期的数据是无法接受的,那么这可能根本不适合缓存。 - Jon Church
对我来说,尝试将其缓存到某个容量是很重要的,这就是为什么我会每4分钟过期一次缓存。如果拍卖在5分钟内开始,那么拍卖将始终在5分钟内出现,这将节省大量GET请求。我正在使用cosmosdb,而且它不便宜。这只是我的成本预测 https://postimg.cc/DW367ZCn,因此,如果我可以将一些偏移量设置为每4分钟仅有一个GET,而不是100/1000人点击,那么每一点都将计算在内。如果我只是摆脱unix时间戳,那么我认为它仍然足够独特,可以作为一个键。 - user6680

0

我在一个最小的示例中复制了您的代码,并按照您的要求使其正常工作。它在第一次请求后从redis缓存中提供响应。我只是记录了一些值,并在您的代码中发现了一些小错误,您很容易就能发现它们(使用doc调用this.model,修复set并删除redis客户端的最后一个get)。我不认为这是缓存响应的最佳方式,因为您在每个请求中都将变量设置为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}`)
 })

这不是他们代码的问题,而是他们使用的特定查询作为密钥的问题。在你的例子中,查询是静态的,在他们发布带有示例查询的pastebin中,你会发现他们的查询具有动态的Date.now()组件。当使用他们的查询时,你的解决方案仍将遇到他们正在经历的相同问题。 - Jon Church
是的,你说得对。在我回答之后看到了 pastebin。不能使用 Date.now() 作为键,但是有一个100%的解决方案。 - C.Gochev
为了解决这个问题,我认为最简单的方法是在声明时获取Date.now值并将其分配给一个变量,然后将其传递给cache.js,然后我可以进行字符串替换,并用""替换日期。但我无法弄清楚如何从app.js将该值传递给mongoose.Query.prototype.exec = async function () {到cache.js https://pastebin.com/MH3nFx5r - user6680

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接