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 有线网路应用(二)

[Day 19] Sass - Functions

嗨!来到了第19天~今天要谈的是Sass界的计算机 @function与@return 在规模比较大...

[Day 19] 针对网页的单元测试(五)

再写登入的验证及功能 今天我们要来做登入的判断跟动作, 我们在HomeController.php引...

[Day 28] 从零开始学Python - 深度学习Keras:如果你能预知这条路的陷阱,我想你依然错得很过瘾

注:本文同步刊载在Medium,若习惯Medium的话亦可去那边看呦! 接下来让我们来聊聊Pytho...

DAY 18 制作 Nav Bar - dropdown

针对 dropdown 的部分,我们要来细节微调他的 style ,让他符合 vogue 上的设计,...

Log Agent - Fluent Bit Parser元件

Fluent bit回顾 Log Agent - Fluent Bit 简介 Log Agent -...