将redis用作REST Api用户的缓存(以便保存Api请求)

时间:2017-07-30 19:21:11

标签: node.js rest api caching redis

我是API用户,我只能获得数量有限的高流量网站请求(~1k并发访问者)。为了保存API请求,我想缓存不太可能改变的特定请求的响应。

但是我想至少每15秒刷新一次redis键(API响应)。我想知道最好的办法是什么?

我的想法:

  • 我认为TTL字段对于这种情况会很方便。只需为此键设置15秒的TTL即可。当我查询此密钥并且它不存在时,我只会使用API​​再次请求它。 问题:由于这是一个高流量的网站,我预计会有大约20-30个请求,直到我收到API的响应,这将导致20到30个API内的请求几毫秒。所以我需要"暂停"所有传入的请求,直到有API响应
  • 我的第二个想法是每隔15秒更新一次密钥。我可以设置一个每15秒运行一次的后台任务,或者在页面请求时我可以检查我的控制器是否需要刷新。我更喜欢最后一个想法,但因此我需要保持redis关键时代,这似乎非常昂贵而且它不是内置功能?

您对此用例有何建议?

我的控制器代码:

function players(req, res, next) {
    redisClient.getAsync('leaderboard:players').then((playersLeaderboard) => {
        if(!playersLeaderboard) {
            // We need to get a fresh copy of the playersLeaderboard
        }

        res.set('Cache-Control', 's-maxage=10, max-age=10')
        res.render('leaderboards/players', {playersLeaderboard: playersLeaderboard})
    }).catch((err) => {
        logger.error(err)
    })
}

2 个答案:

答案 0 :(得分:1)

只需在node.js服务器启动时获取并缓存数据,然后设置15秒的间隔以获取新数据并更新缓存。避免将TTL用于此用例。

function fetchResultsFromApi(cb) {
   apiFunc((err, result) => {
        // do some error handling
       // cache result in redis without ttl
       cb();
    });
}

fetchResultsFromApi(() => {
    app.listen(port);
    setInterval(() => {
        fetchResultsFromApi(() => {});
    }, 15000);
}

<强>优点:

  1. 实施起来非常简单
  2. 不需要排队客户请求
  3. 超快的响应时间
  4. <强>缺点:

    1. 缓存更新可能不会在每15秒后执行/完成。这里和那里可能只有几毫秒。我认为它不会对你正在做的事情产生很大的影响,你可以在15秒之前减少更新缓存的间隔时间。

答案 1 :(得分:1)

我想这更像是一个架构问题,而不是典型的“帮助我的代码无法正常工作”。

让我解释一下你的要求。

问:我想缓存一些不太可能改变的HTTP请求的响应,我希望这些缓存的响应每15秒刷新一次。有可能吗?

A:是的,你会感谢Javascript是单线程的,所以它会非常直接。

这里有一些基础知识。 NodeJS是一个事件驱动的框架,这意味着在1个时间点它将只执行一段代码,直到完成为止。

如果在此过程中遇到任何aysnc来电,它会调用它们并向event-loop添加一个事件,以便在收到回复时说“callback”。当代码例程完成后,它将从队列中弹出下一个事件来运行它们。

基于这些知识,我们知道我们可以通过构建function来实现此目的,以便只在update cached-responses每次到期时发出1次异步调用。如果异步调用已经在运行,那么只需将其回调函数放入队列即可。这样您就不会进行多次异步调用来获取新结果。

我不熟悉async模块,因此我使用promises提供了一个伪代码示例。

伪码:

var fetch_queue = [];
var cached_result = {
    "cached_result_1": {
        "result" : "test",
        "expiry" : 1501477638 // epoch time 15s in future
    }
}

 var get_cached_result = function(lookup_key) {
    if (cached_result.hasOwnProperty(lookup_key)) {
        if (result_expired(cached_result[lookup_key].expiry)) {
            // Look up cached
            return new Promise(function (resolve) {
                 resolve(cached_result[lookup_key].result);
            });
        }
        else {
            // Not expired, safe to use cached result
            return update_result();
        }
    }

}

var update_result = function() {
    if (fetch_queue.length === 0) {
        // No other request is retrieving an updated result.
        return new Promise(function (resolve, reject) {
            // call your API to get the result.
            // When done call.
            resolve("Your result");

            // Inform other requests that an updated response is ready.
            fetch_queue.forEach(function(promise) {
                promise.resolve("Your result");
            })

            // Compute the new expiry epoch time and update the cached_result
        })
    }
    else {
        // Create a promise and park it into the queue
        return new Promise(function(resolve, reject) {
            fetch_queue.push({
                resolve: resolve,
                reject: reject
            })
        });
    }
}

get_cached_result("cached_result_1").then(function(result) {
    // reply the result
})

注意:顾名思义,代码不是实际工作解决方案,但概念就在那里。

值得注意的是,setInterval是一种方法它不能保证该函数将在15秒标记时被完全调用。 API仅确保在预期时间之后发生某些事情。

虽然建议的解决方案将确保只要cached result已过期,下一个查找的人就会发出请求,以下请求将等待初始请求返回。