惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
Netflix TechBlog - Medium
V
Vulnerabilities – Threatpost
Last Week in AI
Last Week in AI
I
InfoQ
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Help Net Security
D
Docker
www.infosecurity-magazine.com
www.infosecurity-magazine.com
B
Blog RSS Feed
Forbes - Security
Forbes - Security
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Latest news
Latest news
S
SegmentFault 最新的问题
J
Java Code Geeks
C
CXSECURITY Database RSS Feed - CXSecurity.com
MongoDB | Blog
MongoDB | Blog
量子位
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
F
Full Disclosure
Engineering at Meta
Engineering at Meta
AWS News Blog
AWS News Blog
月光博客
月光博客
Cisco Talos Blog
Cisco Talos Blog
V
Visual Studio Blog
雷峰网
雷峰网
博客园_首页
Project Zero
Project Zero
美团技术团队
Google DeepMind News
Google DeepMind News
IT之家
IT之家
P
Palo Alto Networks Blog
有赞技术团队
有赞技术团队
S
Security @ Cisco Blogs
U
Unit 42
C
Cisco Blogs
Hugging Face - Blog
Hugging Face - Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Security Archives - TechRepublic
Security Archives - TechRepublic
GbyAI
GbyAI
Stack Overflow Blog
Stack Overflow Blog
S
Schneier on Security
TaoSecurity Blog
TaoSecurity Blog
The Register - Security
The Register - Security
WordPress大学
WordPress大学
T
Threat Research - Cisco Blogs
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
I
Intezer
The Last Watchdog
The Last Watchdog
Cloudbric
Cloudbric
Help Net Security
Help Net Security

博客园 - koushr

hls k8s常用命令 k8s 1.18.0安装 mysql json函数 group by的列、where的列的有效性 go基础第六篇:sync包 doris脚本 es es语法 veo ride nacos 枫叶互动 极光矩阵 富途 移卡科技 nginx高可用 k8s第一篇:k8s集群架构组件 架构设计第二篇:支付模块架构设计 可观测性体系设计第一篇:可观测性体系基本概念 架构设计第一篇:点赞模块功能设计 ES高级第一篇:倒排索引 山海星辰 python第一篇:基础语法 pulsar基础第二篇:命令行 pulsar基础第一篇:pulsar安装及基本概念 clickhouse第三篇:安装 clickhouse第二篇:MergeTree引擎 clickhouse第一篇:引擎 http2.0 gin入参多次获取 docker第二篇:docker安装常用中间件
redis高阶第一篇:令牌桶算法限流
koushr · 2024-09-15 · via 博客园 - koushr

令牌桶算法核心思想是:

1、以恒定速率向桶中添加令牌

2、桶中令牌数有上限,为桶的容量,超过容量后令牌会直接丢弃

3、请求到来时会尝试从桶中取一个令牌,取成功则通过,服务端会处理此请求,否则拒绝。

token_bucket.lua,内容如下:

redis.replicate_commands()

local tokens_key = KEYS[1]
local timestamp_key = KEYS[2]

local rate = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local now = tonumber(ARGV[3]) or redis.call('TIME')[1]
local requested = tonumber(ARGV[4])

local fill_time = capacity / rate
local ttl = math.floor(fill_time * 2)

local last_tokens = tonumber(redis.call("get", tokens_key)) or capacity
local last_refreshed = tonumber(redis.call("get", timestamp_key)) or 0

local delta = math.max(0, now-last_refreshed)
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
local allowed = filled_tokens >= requested
local new_tokens = allowed and filled_tokens - requested or filled_tokens

if ttl > 0 then
  redis.call("setex", tokens_key, ttl, new_tokens)
  redis.call("setex", timestamp_key, ttl, now)
end

return { allowed and 1 or 0, new_tokens }

上面lua脚本拷贝自spring-cloud-gateway-server-xxx.jar包中META-INF目录中scripts目录中的request_rate_limiter.lua文件。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-gateway-server</artifactId>
    <version>4.3.0</version>
</dependency>

在3.0.0版本中,还可以看到可以通过redis.log(redis.LOG_WARNING, "tokens_key " .. tokens_key)在redis中打印日志。

上面lua脚本的解释:

KEYS[1]是存放令牌数的key,KEYS[2]是存放请求时间的key

ARGV[1]是往桶中放令牌的速率,单位是个/秒,如值为5,表示每秒往桶中放5个令牌。ARGV[2]是桶的容量,ARGV[4]是当前请求要使用多少个令牌,一般都是1个。

allowed_num是请求是否可以被处理的标识,值为1表示请求可以被处理,值为0表示请求数已超过阈值,应该被拦截。

定义判断是否允许请求通过的方法,如果服务端是golang开发,则如下:

func getKeys(id, routeId string) []string {
    prefix := fmt.Sprintf("request_rate_limiter.{%v.%v}.", routeId, id)
    tokenKey := fmt.Sprintf("%vtokens", prefix)
    timestampKey := fmt.Sprintf("%vtimestamp", prefix)
    return []string{tokenKey, timestampKey}
}

func isAllowed(userId, routeId string, capacity, rate, tokens int) (bool, error) {
    result, err := rdb.Eval(ctx, script, getKeys(userId, routeId), rate, capacity, "", tokens).Result()
    if err != nil {
        return false, err
    }

    arr, _ := result.([]interface{})
    a, _ := arr[0].(int64)
    return a == 1, nil
}

这串代码是根据org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter中的getKeys()方法和isAllowed()方法翻译过来的。

调用上述函数代码示例:

func main() {
    for i := 0; i < 10000000; i++ {
        allowed, err := isAllowed("12345", "/feed/v1/channels", 10, 5, 1) // capacity=10, rate=5 tokens/sec, 取1个
        if err != nil {
            fmt.Println("Error:", err)
            return
        }
        if allowed {
            fmt.Println(time.Now().Format(time.DateTime), "Request allowed")
        } else {
            fmt.Println(time.Now().Format(time.DateTime), "Rate limit exceeded")
        }
    }
}