Day06-流量限制(一)

前言

不管你的服务器有多少 CPU、多大量的记忆体,每秒可以处理的请求数终究是有限的。为了避免资源被少数恶意使用者用完,造成阻断服务攻击(Denial Of Service),一定要限制每个使用者能够使用的资源量。

而最常见的资源限制方式,就是做限流(rate limiting),当一个使用者无法再发请求给服务器时,自然就没办法再消耗服务器上的资源

整个 Application Server 的限流

如果你架设的 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")
}

针对不同的 endpoint 做限流

一般来说根据每个 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 的负担

如果对於今天的内容有什麽问题的话欢迎在下方留言跳出来,没有的话那我们就明天见罗~


<<:  使用MLFlow tracking功能比较training结果

>>:  Day_09 有线网路应用(二)

第30天~TTS(文字转语音)+STT(语音转文字)

TTS(文字转语音) 开新档案 布置XML档- 按钮也是要绑onClick- 步骤: 1.先宣告 2...

Docker云端:WebDAV+NextCloud完整云端储存环境

隔了几天忙一下...(最近真的忙爆,谁叫我是鸟事处理大师呢...? (自我膨胀?) 第三十一篇!! ...

认识JavaScrip

JavaScript(通常缩写为JS)是可以内嵌於网页中,是一个成熟的动态程序语言,在网站里加入互动...

【Day12】特殊性营运流程篇-专案

#odoo #开源系统 #数位赋能 #E化自主 当我们提起「专案」一词,我还是比较喜欢美国专案管理学...

【Day20】:Servo控制-By PWM输出

Servo 对於简单的角度控制,大家第一个想到的就是伺服马达了吧,大小也适中,非常适合用在机器人上。...