时间:2025-12-24 13:57
人气:
作者:admin
基于白名单的全局限流插件,对指定的域名和URL路径进行全局限流控制,共享同一个限流计数器。
请求到达 → 检查域名白名单 → 检查路径白名单 → Redis限流判断 → 放行/拒绝
hostspaths(支持正则表达式)unitSecond: 统计周期,默认30秒qpm: 周期内最大请求数,默认10次key: Redis存储的key名称采用 Redis Sorted Set + Lua 脚本 实现滑动窗口限流:
-- Lua 脚本执行原子操作
1. ZREMRANGEBYSCORE: 删除过期数据(分数 < now - window)
2. ZCOUNT: 获取当前窗口内的请求数
3. 判断 count >= limit,超过则返回1
4. ZADD: 添加新请求(score=时间戳,member=UUID)
5. EXPIRE: 设置key过期时间
6. 返回0表示未超限
超过限制时返回:
HTTP 429 Too Many Requests
{
"code": 429,
"message": "Too Many Requests"
}
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| serviceName | string | 是 | - | Redis服务名称 |
| servicePort | int | 是 | - | Redis端口 |
| domain | string | 是 | - | Redis域名 |
| username | string | 是 | - | Redis用户名 |
| password | string | 是 | - | Redis密码 |
| timeout | int | 否 | - | 连接超时时间(ms) |
| hosts | []string | 是 | - | 域名白名单 |
| paths | []string | 是 | - | URL路径白名单(支持正则) |
| unitSecond | int | 否 | 30 | 统计周期(秒) |
| qpm | int | 否 | 10 | 周期内最大请求数 |
| key | string | 否 | global-limit-plugin-key | Redis key名称 |
serviceName: "test-redis-service"
servicePort: 6379
domain: "xxx.redis.rds.aliyuncs.com"
username: "user"
password: "password"
timeout: 50000
hosts:
- "api.example.com"
- "www.example.com"
paths:
- "/auth/token"
- "/api/sensitive/.*"
unitSecond: 60
qpm: 100
key: "my-global-limit"
| 特性 | Global Limit | Route Limit |
|---|---|---|
| 限流维度 | 全局共享计数器 | 每个URL独立计数 |
| 配置方式 | 统一配置qpm | 每个URL单独配置 |
| 适用场景 | 整体流量控制 | 精细化接口限流 |
| 路径过滤 | 白名单过滤 | 规则匹配 |
now := time.Now()
nowTimestamp := now.Unix() //秒数
intervalTime := int64(config.unitSecond)
// 使用 Lua 脚本实现:清理过期数据 + 计数 + 添加新记录 + 设置过期时间
// 返回值:0 表示未超限,1 表示已超限
luaScript := `
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
local member = ARGV[4]
local expire_time = tonumber(ARGV[5])
-- 删除过期数据(分数小于 now - window 的成员)
redis.call('ZREMRANGEBYSCORE', key, '-inf', now - window)
-- 获取当前窗口内的请求数
local count = redis.call('ZCOUNT', key, now - window, now)
if count >= limit then
return 1 -- 超过限制
end
-- 添加新请求
redis.call('ZADD', key, now, member)
-- 设置key的过期时间,防止key永久存在
redis.call('EXPIRE', key, expire_time)
return 0 -- 未超过限制
`
// 准备参数
var keyArr []interface{}
keyArr = append(keyArr, config.key)
var valueArr []interface{}
uuid := uuid.New()
expireTime := config.unitSecond * 2 // 过期时间设为统计周期的2倍
valueArr = append(valueArr, nowTimestamp, intervalTime, config.qpm, uuid.String(), expireTime)
// 执行 Lua 脚本
err := config.Client.Eval(luaScript, 1, keyArr, valueArr, func(response resp.Value) {
if response.Integer() == 1 {
// 超过限制
fmt.Println("TOO_MANY_REQUESTS 429 ,path:", ctx.Path(), ",ipAddress:", util.GetClientIP())
headers := [][2]string{{"Content-Type", "application/json"}}
proxywasm.SendHttpResponse(429, headers, []byte("{\"code\":429,\"message\":\"Too Many Requests\"}"), -1)
} else {
// 未超过限制,继续请求
proxywasm.ResumeHttpRequest()
}
})
if err != nil {
log.Errorf("rate limit error while calling redis: %v", err)
proxywasm.ResumeHttpRequest()
}
return types.ActionPause
作者:仓储大叔,张占岭,
荣誉:微软MVP
QQ:853066980
支付宝扫一扫,为大叔打赏!

下一篇:单元测试(go)