Ratelimit 的幾種方式 ... 或是別用?



前言

  • 每次做網頁服務都不免擔心會遇到過多的流量把服務塞住;一路嘗試過幾種 ratelimit 的方式,最後決定都先取消!
  • 本篇紀錄幾個常見的 ratelimit 的方式,以及為什麼我後來取消 (發現流量不夠)。



3 種 ratelimit 的方法

方法 1:OS - iptables

1
2
3
4
5
6
7
8
9
# Create a new chain to handle the rate limiting
sudo iptables -N RATE_LIMIT

# Add a rule to this chain to limit API connections to 5 per minute
sudo iptables -A RATE_LIMIT -p tcp --dport 8080 -m state --state NEW -m recent --set --name API
sudo iptables -A RATE_LIMIT -p tcp --dport 8080 -m state --state NEW -m recent --update --seconds 60 --hitcount 5 --name API -j DROP

# Apply the rate limiting to incoming API connections
sudo iptables -A INPUT -p tcp --dport 8080 -m state --state NEW -j RATE_LIMIT

Rule 說明:

  • line 2:建立一個新的 chain 來處理 rate limit;可當作 chain 是 rules 的集合

  • line 5:在 RATE_LIMIT 這個 chain 上新增一條規則:

    • 「從 port 8080、state 是 NEW 的 tcp 連線,設定一個名稱 API 來記錄」
  • line 6:在 RATE_LIMIT 這個 chain 上再新增一條規則:

    • 「名稱是 API 且從 port 8080、state 是 NEW 的 tcp 連線,如果在 60 秒內有 5 次以上的連線,則 DROP 這個連線」
  • line 9:將 RATE_LIMIT 這個 chain 加到 INPUT chain 上,這樣所有進來的連線都會經過這個 rate limit 的規則


Note

  • -m:(match) 指定要使用的模組

方法 2:Nginx Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
limit_req_zone $binary_remote_addr zone=demo_zone:10m rate=5r/s;

limit_req_status 444;

server {
   # ...
   location /api/v1 {
      limit_req zone=demo_zone;
      # ...
   }
}

其中

  • limit_req_zone:定義 rate limit 的設定;可以用多個 limit_req_zone 來定義不同的 rate limit
  • $binary_remote_addr:用來識別每個 client 的 IP
  • zone=demo_zone:10m:設定這個 zone: demo_zone 使用 10MB 的 shared memory zone,用來存放 rate limit 的資訊
  • rate=5r/s:設定 rate limit 為每秒 5 個 request
  • limit_req_status:當超過 rate limit 時,返回的 status code;預設是 503,我這邊用 444 代表不回應

更多設定可以參考 [2]


方法 3:你的 API 相關框架的套件

我做的服務目前都是用 Python - FastAPI,所以可以使用 slowapi 這個套件 [3] 來做 rate limit。

例如以下是 slowapi 他們文件的範例 [4]:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from fastapi import FastAPI
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded

limiter = Limiter(key_func=get_remote_address)
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

# Note: the route decorator must be above the limit decorator, not below it
@app.get("/home")
@limiter.limit("5/minute")
async def homepage(request: Request):
   return PlainTextResponse("test")

@app.get("/mars")
@limiter.limit("5/minute")
async def homepage(request: Request, response: Response):
   return {"key": "value"}

假如你是用 Node.js - express 框架,可以考慮 express-rate-limit 這個套件 [5]。



最後決定移除 rate limit 設定

三種方法都做過,個人最不傾向使用 Python 的套件,怕對效能影響太大。

前兩種設定以及更新都算方便,只是我對 nginx 的設定更熟悉一些,最後都是用 nginx 的 rate limit 設定。

可是後來發現:

  1. ratelimit 基本上就是增加系統效能壓力,目前也沒有真的有什麼效果;甚至也可能是一種邀請 DDOS 攻擊的方式。
  2. 提供的服務也不是真的有超大的流量、或是重要到需要預防 DDOS 攻擊。另一方面,我做的服務要是被 DDOS 可能還是榮幸。
  3. 真的遇到,就花錢加買雲端服務,讓他們來幫忙處理就好。

結論是,不用特別做 ratelimit,平時做好資料備份、或研究自己架構的 auto-scaling 會比較實際。

被打爆就重啟服務、資料有損毀就還原。




REF

  1. https://serverfault.com/questions/340256/should-i-rate-limit-packets-with-iptables
  2. https://docs.nginx.com/nginx/admin-guide/security-controls/controlling-access-proxied-http/#limiting-the-request-rate
  3. https://github.com/laurentS/slowapi
  4. https://slowapi.readthedocs.io/en/latest/
  5. https://www.npmjs.com/package/express-rate-limit

主題 StackJimmy 設計