2017-07-30 66 views
0

我是一個API用戶,我只有有限數量的請求可用於高流量網站(大約1k個併發訪問者)。爲了節省API請求,我希望緩存不太可能改變的特定請求的響應。使用redis作爲REST Api用戶的緩存(爲了保存Api請求)

但是我想至少每隔15秒刷新一次redis密鑰(API響應)。我想知道最好的方法是什麼?

我的想法:

  • 我以爲TTL字段將適合完成這個場景。只需爲此密鑰設置15秒的TTL。當我查詢這個鍵並且它不存在時,我只需要使用API​​再次請求它。 問題:由於這是一個高流量的網站,我期待大約20-30個請求,直到我得到了API的響應,這將導致在幾個毫秒內對API的20-30次請求。所以我需要「暫停」所有傳入的請求,直到出現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) 
    }) 
} 

回答

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. 很簡單,超快速的響應時間

缺點:

  1. 高速緩存更新可能不會在每隔15秒完全執行/完成。這裏和那裏可能只有幾毫秒。我認爲它不會對你正在做的事情產生很大的影響,你可以在15秒之前減少更新緩存的間隔時間。
1

我想這是比那些典型的「幫助我的代碼不工作」的類型更體系結構的問題。

讓我解釋你的要求。

問:我想緩存一些不太可能改變的HTTP請求的響應,我希望這些緩存的響應每15秒刷新一次。可能嗎?

- 答:是的,你要感謝Javascript是單線程的事實,所以它將是非常簡單的。

這裏有一些基礎知識。 NodeJS是一個事件驅動的框架,這意味着在一個時間點它將只執行一段代碼,直到完成。

如果在此過程中遇到任何aysnc調用,它將調用它們並向event-loop添加事件以在接收到響應時說「callback」。代碼例程完成後,它會彈出隊列中的下一個事件來運行它們。

基於這些知識,我們知道我們可以通過構建一個function來實現此目的,以便每次過期時僅觸發1個異步調用update,cached-responses。如果一個異步調用已經在執行,那麼只需將它們的回調函數放入一個隊列即可。這樣可以避免多次異步調用來獲取新結果。

我不熟悉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已過期,下一個查找它的人將執行請求,並且以下請求將等待最初的請求返回。