Go 语言结合 Redis 实现固定窗口、滑动窗口、令牌桶和漏桶限流算法的示例代 ...

打印 上一主题 下一主题

主题 881|帖子 881|积分 2643

固定窗口算法



  • 原理:将时间划分为固定大小的窗口,在每个窗口内对哀求举行计数。如果哀求数超过设定的阈值,则拒绝后续哀求,直到进入下一个窗口。
  • 代码:
  1. package main
  2. import (
  3.     "fmt"
  4.     "time"
  5.     "github.com/go-redis/redis/v8"
  6. )
  7. // rdb 是全局的 Redis 客户端实例,用于与 Redis 服务器进行交互
  8. var rdb = redis.NewClient(&redis.Options{
  9.     Addr:     "localhost:6379", // Redis 服务器地址和端口
  10.     Password: "",               // Redis 密码,这里为空表示无密码
  11.     DB:       0,                // 使用默认的数据库编号
  12. })
  13. // 定义固定窗口算法的常量
  14. const (
  15.     windowSize = 60 // 窗口大小,单位为秒
  16.     limit      = 100 // 窗口内允许的最大请求数
  17. )
  18. // fixedWindowRateLimit 函数实现了固定窗口限流算法
  19. func fixedWindowRateLimit(key string) bool {
  20.     // 获取当前时间的 Unix 时间戳
  21.     currentTime := time.Now().Unix()
  22.     // 计算当前窗口的起始时间
  23.     windowStart := currentTime - (currentTime % windowSize)
  24.     // 构建当前窗口对应的 Redis key
  25.     windowKey := fmt.Sprintf("%s:%d", key, windowStart)
  26.     // 对 Redis 中的当前窗口计数器进行自增操作,并获取自增后的计数值
  27.     count, err := rdb.Incr(rdb.Context(), windowKey).Result()
  28.     if err != nil {
  29.         // 如果自增操作出现错误,打印错误信息并返回 false
  30.         fmt.Println("Error incrementing counter:", err)
  31.         return false
  32.     }
  33.     // 如果计数值为 1,说明是该窗口的第一个请求,为该窗口设置过期时间
  34.     if count == 1 {
  35.         rdb.Expire(rdb.Context(), windowKey, time.Duration(windowSize)*time.Second)
  36.     }
  37.     // 如果计数值超过了允许的最大请求数,返回 false 表示请求被限流
  38.     if count > limit {
  39.         return false
  40.     }
  41.     // 否则,返回 true 表示请求通过
  42.     return true
  43. }
  44. func main() {
  45.     // 调用 fixedWindowRateLimit 函数进行限流判断
  46.     if fixedWindowRateLimit("api_request") {
  47.         fmt.Println("请求通过")
  48.     } else {
  49.         fmt.Println("请求被限流")
  50.     }
  51. }
复制代码


  • 优缺点:实现简单,但可能会出现临界问题,即在窗口切换的刹时可能会出现流量高峰。
滑动窗口算法



  • 原理:将固定窗口进一步细分,通过多个小窗口来统计哀求数,随着时间的推移,窗口不断滑动,从而更精确地控制流量。
  • 代码:
  1. package main
  2. import (
  3.     "fmt"
  4.     "time"
  5.     "github.com/go-redis/redis/v8"
  6. )
  7. // rdb 是全局的 Redis 客户端实例,用于与 Redis 服务器进行交互
  8. var rdb = redis.NewClient(&redis.Options{
  9.     Addr:     "localhost:6379", // Redis 服务器地址和端口
  10.     Password: "",               // Redis 密码,这里为空表示无密码
  11.     DB:       0,                // 使用默认的数据库编号
  12. })
  13. // 定义滑动窗口算法的常量
  14. const (
  15.     windowSize = 60 // 窗口大小,单位为秒
  16.     limit      = 100 // 窗口内允许的最大请求数
  17. )
  18. // slidingWindowRateLimit 函数实现了滑动窗口限流算法
  19. func slidingWindowRateLimit(key string) bool {
  20.     // 获取当前时间的 Unix 时间戳
  21.     currentTime := time.Now().Unix()
  22.     // 移除 Redis 有序集合中时间戳小于当前窗口起始时间的元素
  23.     rdb.ZRemRangeByScore(rdb.Context(), key, "0", fmt.Sprintf("%d", currentTime-windowSize))
  24.     // 向 Redis 有序集合中添加当前时间戳,分数为当前时间戳
  25.     rdb.ZAdd(rdb.Context(), key, &redis.Z{
  26.         Score:  float64(currentTime),
  27.         Member: currentTime,
  28.     })
  29.     // 获取 Redis 有序集合中当前窗口内的元素数量
  30.     count, err := rdb.ZCard(rdb.Context(), key).Result()
  31.     if err != nil {
  32.         // 如果获取元素数量操作出现错误,打印错误信息并返回 false
  33.         fmt.Println("Error getting count:", err)
  34.         return false
  35.     }
  36.     // 如果元素数量超过了允许的最大请求数,返回 false 表示请求被限流
  37.     if count > limit {
  38.         return false
  39.     }
  40.     // 否则,返回 true 表示请求通过
  41.     return true
  42. }
  43. func main() {
  44.     // 调用 slidingWindowRateLimit 函数进行限流判断
  45.     if slidingWindowRateLimit("api_request") {
  46.         fmt.Println("请求通过")
  47.     } else {
  48.         fmt.Println("请求被限流")
  49.     }
  50. }
复制代码


  • 优缺点:比固定窗口算法更精确地控制流量,但实现相对复杂,且需要更多的 Redis 操作。
令牌桶算法



  • 原理:体系以固定的速率向令牌桶中添加令牌,每个哀求需要从令牌桶中获取一个或多个令牌才能被处理。如果令牌桶中没有足够的令牌,则哀求被拒绝。
  • 代码:
  1. package main
  2. import (
  3.     "fmt"
  4.     "time"
  5.     "github.com/go-redis/redis/v8"
  6. )
  7. // rdb 是全局的 Redis 客户端实例,用于与 Redis 服务器进行交互
  8. var rdb = redis.NewClient(&redis.Options{
  9.     Addr:     "localhost:6379", // Redis 服务器地址和端口
  10.     Password: "",               // Redis 密码,这里为空表示无密码
  11.     DB:       0,                // 使用默认的数据库编号
  12. })
  13. // 定义令牌桶算法的常量
  14. const (
  15.     capacity        = 100        // 令牌桶的容量
  16.     rate            = 1          // 令牌生成速率,每秒生成 1 个令牌
  17.     requiredTokens  = 1          // 每个请求所需的令牌数
  18. )
  19. // tokenBucketScript 是用于执行令牌桶算法的 Lua 脚本
  20. var tokenBucketScript = `
  21. -- 获取传入的令牌桶 key
  22. local tokens_key = KEYS[1]
  23. -- 构建存储上次更新时间的 key
  24. local last_time_key = tokens_key .. ":last_time"
  25. -- 获取令牌桶容量
  26. local capacity = tonumber(ARGV[2])
  27. -- 获取令牌生成速率
  28. local rate = tonumber(ARGV[3])
  29. -- 获取当前时间
  30. local now = tonumber(ARGV[1])
  31. -- 获取每个请求所需的令牌数
  32. local required_tokens = tonumber(ARGV[4])
  33. -- 从 Redis 中获取上次更新时间
  34. local last_time = tonumber(redis.call('get', last_time_key))
  35. -- 从 Redis 中获取当前令牌数
  36. local tokens = tonumber(redis.call('get', tokens_key))
  37. -- 如果上次更新时间为空,说明是首次请求,初始化相关值
  38. if last_time == nil then
  39.     last_time = now
  40.     tokens = capacity
  41. end
  42. -- 计算从上次更新到现在生成的令牌数
  43. local generated_tokens = (now - last_time) * rate
  44. -- 更新令牌数,确保不超过令牌桶容量
  45. tokens = math.min(capacity, tokens + generated_tokens)
  46. -- 如果令牌数小于请求所需的令牌数,说明令牌不足,拒绝请求
  47. if tokens < required_tokens then
  48.     redis.call('set', last_time_key, last_time)
  49.     redis.call('set', tokens_key, tokens)
  50.     return 0
  51. else
  52.     -- 否则,处理请求,扣除相应的令牌数
  53.     tokens = tokens - required_tokens
  54.     redis.call('set', last_time_key, now)
  55.     redis.call('set', tokens_key, tokens)
  56.     return 1
  57. end
  58. `
  59. // tokenBucketRateLimit 函数调用 Lua 脚本来实现令牌桶限流算法
  60. func tokenBucketRateLimit(key string) bool {
  61.     // 获取当前时间的 Unix 时间戳
  62.     currentTime := time.Now().Unix()
  63.     // 执行 Lua 脚本,并获取执行结果
  64.     result, err := rdb.Eval(rdb.Context(), tokenBucketScript, []string{key}, currentTime, capacity, rate, requiredTokens).Int64()
  65.     if err != nil {
  66.         // 如果执行脚本出现错误,打印错误信息并返回 false
  67.         fmt.Println("Error executing script:", err)
  68.         return false
  69.     }
  70.     // 如果结果为 1,表示请求通过;否则,表示请求被限流
  71.     return result == 1
  72. }
  73. func main() {
  74.     // 调用 tokenBucketRateLimit 函数进行限流判断
  75.     if tokenBucketRateLimit("api_request") {
  76.         fmt.Println("请求通过")
  77.     } else {
  78.         fmt.Println("请求被限流")
  79.     }
  80. }
复制代码


  • 优缺点:能够平滑地控制流量,答应一定程度的突发流量,但实现相对复杂。
漏桶限流算法



  • 原理:哀求就像水一样注入漏桶,漏桶以固定的速率处理哀求。如果哀求的注入速率超过漏桶的处理速率,多余的哀求将被丢弃。
  • 代码:
  1. package main
  2. import (
  3.     "fmt"
  4.     "time"
  5.     "github.com/go-redis/redis/v8"
  6. )
  7. // rdb 是全局的 Redis 客户端实例,用于与 Redis 服务器进行交互
  8. var rdb = redis.NewClient(&redis.Options{
  9.     Addr:     "localhost:6379", // Redis 服务器地址和端口
  10.     Password: "",               // Redis 密码,这里为空表示无密码
  11.     DB:       0,                // 使用默认的数据库编号
  12. })
  13. // 定义漏桶算法的常量
  14. const (
  15.     capacity     = 100        // 漏桶的容量
  16.     rate         = 1          // 漏桶的流出速率,每秒流出 1 个单位
  17.     requestSize  = 1          // 每个请求的大小
  18. )
  19. // leakyBucketScript 是用于执行漏桶算法的 Lua 脚本
  20. var leakyBucketScript = `
  21. -- 获取传入的漏桶 key
  22. local bucket_key = KEYS[1]
  23. -- 构建存储上次更新时间的 key
  24. local last_time_key = bucket_key .. ":last_time"
  25. -- 获取漏桶容量
  26. local capacity = tonumber(ARGV[2])
  27. -- 获取漏桶流出速率
  28. local rate = tonumber(ARGV[3])
  29. -- 获取当前时间
  30. local now = tonumber(ARGV[1])
  31. -- 获取每个请求的大小
  32. local request_size = tonumber(ARGV[4])
  33. -- 从 Redis 中获取上次更新时间
  34. local last_time = tonumber(redis.call('get', last_time_key))
  35. -- 从 Redis 中获取当前漏桶中的水量
  36. local water = tonumber(redis.call('get', bucket_key))
  37. -- 如果上次更新时间为空,说明是首次请求,初始化相关值
  38. if last_time == nil then
  39.     last_time = now
  40.     water = 0
  41. end
  42. -- 计算从上次更新到现在流出的水量
  43. local outflow = (now - last_time) * rate
  44. -- 更新漏桶中的水量,确保不小于 0
  45. water = math.max(0, water - outflow)
  46. -- 如果漏桶中的水量加上当前请求的大小超过了漏桶容量,说明漏桶已满,拒绝请求
  47. if water + request_size > capacity then
  48.     redis.call('set', last_time_key, last_time)
  49.     redis.call('set', bucket_key, water)
  50.     return 0
  51. else
  52.     -- 否则,处理请求,增加漏桶中的水量
  53.     water = water + request_size
  54.     redis.call('set', last_time_key, now)
  55.     redis.call('set', bucket_key, water)
  56.     return 1
  57. end
  58. `
  59. // leakyBucketRateLimit 函数调用 Lua 脚本来实现漏桶限流算法
  60. func leakyBucketRateLimit(key string) bool {
  61.     // 获取当前时间的 Unix 时间戳
  62.     currentTime := time.Now().Unix()
  63.     // 执行 Lua 脚本,并获取执行结果
  64.     result, err := rdb.Eval(rdb.Context(), leakyBucketScript, []string{key}, currentTime, capacity, rate, requestSize).Int64()
  65.     if err != nil {
  66.         // 如果执行脚本出现错误,打印错误信息并返回 false
  67.         fmt.Println("Error executing script:", err)
  68.         return false
  69.     }
  70.     // 如果结果为 1,表示请求通过;否则,表示请求被限流
  71.     return result == 1
  72. }
  73. func main() {
  74.     // 调用 leakyBucketRateLimit 函数进行限流判断
  75.     if leakyBucketRateLimit("api_request") {
  76.         fmt.Println("请求通过")
  77.     } else {
  78.         fmt.Println("请求被限流")
  79.     }
  80. }
复制代码


  • 优缺点:可以平滑地处理哀求,保证哀求以固定的速率被处理,但无法应对突发流量。
总结

算法实现复杂度限流精度突发流量处理实用场景固定窗口算法简单低差流量平稳、精度要求低的场景滑动窗口算法较复杂高一样平常流量波动大、精度要求高的场景令牌桶算法较复杂高好可容忍突发流量、需平滑限流的场景漏桶算法较复杂高差对流量稳定性要求极高的场景

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

惊雷无声

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表