不管你的伺服器有多少 CPU、多大量的記憶體,每秒可以處理的請求數終究是有限的。為了避免資源被少數惡意使用者用完,造成阻斷服務攻擊(Denial Of Service),一定要限制每個使用者能夠使用的資源量。
而最常見的資源限制方式,就是做限流(rate limiting),當一個使用者無法再發請求給伺服器時,自然就沒辦法再消耗伺服器上的資源
如果你架設的 API 服務除了用 Node.js/Go 寫成的 application server 之外,前面沒有擋任何 reverse proxy、load balancer,那最簡單的方式就是用 express/gin 的 middleware 來做限流了
在 Node.js 裡,有一個用來做限流的 express middleware 叫做 express-rate-limit,如果我想限制每個 IP 在十分鐘內最多只能發 1000 個請求到 server,那只要這樣寫就可以了
const rateLimit = require("express-rate-limit");
const globalLimiter = rateLimit({
windowMs: 10 * 60 * 1000, // 10 分鐘
max: 1000
});
app.use(globalLimiter)
app.get('/api/user', getUserHandler)
app.post('/api/user', createUserHandler)
// ...
如此一來,只要有人嘗試在十分鐘內發送超過 1000 個請求給 API Server,就會馬上收到 429 Too Many Requests
而 Go 的話也有一個 Gin 的 middleware 叫做 limiter,用起來稍微複雜一點,但基本概念就是設定好時間跟次數後,把 middleware 套用上去就可以了
package main
import (
"github.com/ulule/limiter/v3"
"github.com/ulule/limiter/v3/drivers/store/memory"
mgin "github.com/ulule/limiter/v3/drivers/middleware/gin"
"github.com/gin-gonic/gin"
)
func main() {
// 每十分鐘最多 1000 個請求
rate := limiter.Rate{
Period: 10 * time.Minute,
Limit: 1000,
}
// 把資料存在記憶體裡面
store := memory.NewStore()
middleware := mgin.NewMiddleware(limiter.New(store, rate))
router := gin.Default()
// 套用限流的 middleware
router.Use(middleware)
router.GET("/api/user", getUserHandler)
router.POST("/api/user", createUserHandler)
router.Run(":8888")
}
一般來說根據每個 API endpoint 要做的事情不同,所需要的資源(CPU、時間、$$$)也會不同,所以像上面的範例只有規定「每十分鐘內最多一千個請求」並不是個好方法,應該要針對高成本的 endpoint 做更嚴格的限制
舉個比較極端的例子,譬如說你有一個 API endpoint POST /api/verify/{phoneNumber}
是用來發送認證簡訊的,而每發一封簡訊需要 0.5 元。所以如果攻擊者在十分鐘內連續送出 1000 個發送簡訊的請求,就算你的 server 頂得住流量,也會讓你每十分鐘就浪費 500 元(一天就噴 72000),可能公司還沒開始賺錢就倒了XD
所以為了防止這種情況,應該要針對特別浪費資源(不管是運算資源還是 $$$)的 api 做特別的限制,才不會被攻擊者找到弱點後直接被打掛
const rateLimit = require("express-rate-limit");
const globalLimiter = rateLimit({
windowMs: 10 * 60 * 1000,
max: 1000
});
// 每個小時最多發 5 次簡訊
const verifyPhoneLimiter = rateLimit({
windowMs: 60 * 60 * 1000,
max: 5
});
app.use(globalLimiter)
app.get('/api/user', getUserHandler)
app.post('/api/verify/:phoneNumber', verifyPhoneLimiter, verifyPhoneHandler)
今天講了怎麼在 API server 裡面自己用 middleware 做限流,雖然這樣做感覺很方便,但也會讓 API server 無法完全專注在業務邏輯上,所以明天要來說說如果在 API server 前面有擋 nginx 之類的 load balancer 的話,要怎麼在上面設定限流以減低 API server 的負擔
如果對於今天的內容有什麼問題的話歡迎在下方留言跳出來,沒有的話那我們就明天見囉~