API開發中如何使用限速應對大規模訪問

編者注:俗話說的好 「併發不夠,機器來湊」,當咱們面對高併發請求的時候增長機器是最簡單也是最土豪的作法。不過在資源有限的狀況除了去優化代碼咱們又該怎麼辦呢?今天咱們請來了 @有馬 同窗爲咱們分享一下他在這方面的經驗,但願能幫助到你們。javascript

———html

想要開發牢固的Web API只考慮安全是不夠的,還有一點咱們須要考慮,那就是應對大規模訪問的對策。不只是Web API服務,任何在網絡上公開的服務都會時不時地遇到來自外部的大規模訪問,好比「鹿晗關曉彤公佈戀情」這種實時熱點。當服務器遇到大規模訪問時,爲了處理這些訪問會耗盡資源,進而沒法提供服務。這時不只是這些大規模訪問,任何人都沒法和服務器端創建鏈接。java

咱們能夠經過程序絕不費力的訪問Web API,因此API服務器更容易遇到訪問負載高的狀況,針對這個問題,和普通的Web應用同樣,咱們能夠對API服務進行擴容,這是正確的作法,但本文不對擴容方案展開討論。接下來會討論限速在應對大規模訪問時一些重要的點,以及在ThinkJS開發的項目中應該怎樣作。git

限制用戶的訪問

爲了解決忽然出現大規模訪問的問題,最現實的方法是對每一個用戶的訪問次數進行限制。也就是肯定單個用戶在單位時間裏最大的訪問次數,若是用戶已經超過了最大訪問次數,用戶再次訪問時,服務端將會直接拒絕並返回錯誤信息。好比設置一個用戶10分鐘內只容許調用20次獲取短信驗證碼的接口,那麼當用戶在10分鐘內發起第21次請求時,服務器端便會返回錯誤信息,10分鐘以後纔會恢復訪問。若是進行訪問限速,就要先解決下面三個問題:github

  • 如何肯定限速的數值
  • 如何肯定限速時間單位
  • 在何時重置限速的數值

肯定限速數值

對數據頻繁更新的查詢類API而言,用戶須要頻繁的訪問的到最新的數據,若是設置1小時只能訪問10次的話,用戶確定不滿意,轉而去用能夠替代的服務。訪問限速的初衷是爲了應對服務器短期內遭遇大規模訪問不堪重負從而沒法提供服務,但若是讓用戶用起來不方便就得不償失了,因此要儘量的瞭解提供的API在什麼狀況下被使用,而後決定限速的數值。web

肯定限速時間單位

根據在線服務的不一樣,有些會以一天做爲訪問次數的時間單位,不過這對不少API來講有點長了,假設使用者正在寫腳本訪問API,開始並不清楚訪問次數的時間單位,那就可能須要讓他等24個小時才能繼續訪問API,或者換一個帳號。若是咱們以10分鐘做爲訪問次數的時間單位,若是超出訪問次數限制,也只須要等10分鐘就能繼續訪問了。雖然單位時間的設定和API返回的數據密切相關,但大部分已公開的API都設置了都設置了1小時左右的單位時間。redis

肯定重置限速數值的時間

當用戶超出訪問上限值時,服務端該如何返回響應消息呢?這種狀況下能夠返回HTTP協議中備好的「429 Too Many Request」狀態碼。429狀態碼在2012年4月發佈的RFC 6585中定義,當特定用戶在必定時間內發起的請求次數過多時,服務器端能夠返回該狀態碼錶示出錯。RFC 文檔中對該狀態碼描述以下:安全

429 Too Many Requests

   The 429 status code indicates that the user has sent too many
   requests in a given amount of time ("rate limiting").

   The response representations SHOULD include details explaining the
   condition, and MAY include a Retry-After header indicating how long
複製代碼

經過上面的描述能夠知道,響應消息中應該包含錯誤的詳細信息,而且能夠經過Retry-After告知用戶須要等待多長時間才能訪問API。Retry-After首部表示客戶端須要等待多長時間才能再次訪問。RFC文檔中用 MAY 標記該首部,表示即便不發送該首部也不會有什麼問題,只是在響應體加上該首部會顯得更加友好。bash

另外,Retry-After並非 429 狀態碼專用的響應首部。該首部在HTTP 1.1的RFC 7231中定義,它也一樣包含在帶有503和3xx系列的響應體中。並且Retry-After首部用秒數來指定時間,還可使用詳細的日期信息,能夠看一下RFC文檔中的描述:服務器

Retry-After

   Servers send the "Retry-After" header field to indicate how long the
   user agent ought to wait before making a follow-up request.  When
   sent with a 503 (Service Unavailable) response, Retry-After indicates
   how long the service is expected to be unavailable to the client.
   When sent with any 3xx (Redirection) response, Retry-After indicates
   the minimum time that the user agent is asked to wait before issuing
   the redirected request.

   The value of this field can be either an HTTP-date or a number of
   seconds to delay after the response is received.

     Retry-After = HTTP-date / delay-seconds

   A delay-seconds value is a non-negative decimal integer, representing
   time in seconds.

     delay-seconds  = 1*DIGIT
複製代碼

經過HTTP響應傳遞限速信息

在實施訪問限速的過程當中,若是能將當前用戶訪問次數限制、已使用的訪問次數以及什麼時候重置訪問限速等信息告訴用戶,會顯得很是友好。若是不返回這些信息的話,用戶可能爲了肯定限速是否解除而屢次嘗試訪問接口API,這樣一來無疑又增長了服務器的壓力。

限速信息能夠放在響應消息首部,另外一種是做爲響應消息體數據的一部分,目前將限速信息放在響應消息首部的方式成爲事實上的標準。

首部名 說明 類型
X-RateLimit-Limit 單位時間的訪問上限 Integer
X-RateLimit-Remaining 剩餘的訪問次數 Integer
X-RateLimit-Reset 訪問次數重置時間 UTC epoch seconds

看一下GitHub的限速策略,GitHub就使用了上面三個響應首部,沒有帶Retry-After首部。對於認證的請求每小時能夠訪問5000次,沒有認證的請求每小時訪問60次。

Twitter限速策略的時間窗口是15分鐘,比GitHub的時間窗口小不少,由於Twitter的數據更新的相對較較快,時間窗口設置小一些才能知足使用者獲取最新數據的需求。Twitter使用相似上面三個的響應首部傳達限速信息x-rate-limit-limit,x-rate-limit-remaining,x-rate-limit-reset。對於GET請求有兩種初始方案,一種是15分鐘15次請求,另外一種是15分鐘180次請求,而且只容許認證訪問。

經過對比GitHub和Twitter的限速策略,能夠知道只要準確傳達限速信息,響應頭部徹底能夠本身定義,重點是語義明確,且不能和其餘標準首部衝突。

在ThinkJS中實現API限速控制

要實現API訪問限速,須要對每一個用戶及應用訪問API的次數進行計數,通常會使用Redis等鍵值對存儲來記錄。ThinkJS 結合本身的路由映射方式實現了think-ratelimiter中間件對action進行限速,你須要在middleware.js裏進行以下配置,就能夠實現簡單的限速策略。

// in middleware.js
const redis = require('redis');
const { port, host, password } = think.config('redis');
const db = redis.createClient(port, host, { password });
const ratelimiter = require('think-ratelimiter');

module.exports = {
  // after router middleware
  {
    handle: ratelimiter,
    options: {
      db,
      errorMessage: 'Sometimes You Just Have To Slow Down',
      headers: {
        remaining: 'X-RateLimit-Remaining',
        reset: 'X-RateLimit-Reset',
        total: 'X-RateLimit-Limit'
      },
      resources: {
        'test/test': { // key 是 controller/action 的拼接
          id: ctx => ctx.ip,
          max: 5,
          duration: 7000 // ms
        }
      }
    }
  },
}
複製代碼

響應體首部X-RateLimit-Reset表示能夠恢復訪問的時間,同時也會帶着Retry-After首部,它的值是距離恢復時間的秒數。

總結

在ThinkJS開發的Web應用中,可使用中間件而後添加配置實現簡單的限速,若是你提供的web API服務訪問量比較大或者須要付費訪問等功能,就須要在真正的邏輯前加一層來作限速相關的事情,在ThinkJS中能夠實現一個services/ratelimit.js,而後在項目的base controller中實現限速等邏輯。

參考資料:

相關文章
相關標籤/搜索