Golang 限流系统架构终极指南

在 Golang 中实现用户级别(例如按 IP 或用户 ID)的限流,常用的算法包括滑动窗口令牌桶以及基于 Redis 的分布式方案。下面将详细介绍四种实现方式,并提供可运行的示例代码和选型建议。


方案1:使用滑动窗口(推荐用于单机精确限流)

滑动窗口通过记录每个请求的时间戳,统计当前时间窗口内的请求数量,精度高且能避免临界问题。

原理

  • 为每个用户维护一个请求时间戳队列(如 slice)。
  • 每次请求时,将当前时间戳加入队列,并移除所有早于 当前时间 - 窗口大小 的时间戳。
  • 检查剩余数量是否超过阈值(1000)。

实现示例

package main

import (
    "sync"
    "time"
)

type SlideWindowLimiter struct {
    windowSize time.Duration // 窗口大小,如1分钟
    maxRequest int           // 最大请求数,如1000
    requests   map[string][]time.Time
    mu         sync.Mutex
}

func NewSlideWindowLimiter(windowSize time.Duration, maxRequest int) *SlideWindowLimiter {
    return &SlideWindowLimiter{
        windowSize: windowSize,
        maxRequest: maxRequest,
        requests:   make(map[string][]time.Time),
    }
}

// Allow 判断某个用户是否允许请求
func (l *SlideWindowLimiter) Allow(userID string) bool {
    l.mu.Lock()
    defer l.mu.Unlock()

    now := time.Now()
    cutoff := now.Add(-l.windowSize)

    // 获取该用户的请求时间戳列表
    times, exists := l.requests[userID]
    if !exists {
        times = []time.Time{}
    }

    // 过滤掉窗口之外的时间戳
    valid := make([]time.Time, 0, len(times))
    for _, t := range times {
        if t.After(cutoff) {
            valid = append(valid, t)
        }
    }

    // 判断是否超过限制
    if len(valid) >= l.maxRequest {
        l.requests[userID] = valid // 保存过滤后的列表(虽然超限,但保留用于下次判断)
        return false
    }

    // 允许请求,加入当前时间戳
    valid = append(valid, now)
    l.requests[userID] = valid
    return true
}

优缺点

  • 优点:精确控制窗口内请求数,无突刺问题。
  • 缺点:每个用户需要维护时间戳列表,内存占用随请求频率增加;需要自行处理过期数据清理。

方案2:使用令牌桶算法

令牌桶允许一定程度的突发流量,适合流量波动的场景。

原理

  • 桶容量为 1000,按固定速率(每秒 1000/60 ≈ 16.67 个)添加令牌。
  • 请求到来时从桶中取走一个令牌,若桶空则拒绝。

手动实现示例

package main

import (
    "sync"
    "time"
)

type TokenBucket struct {
    capacity    int           // 桶容量
    rate        float64       // 令牌生成速率(个/秒)
    tokens      float64       // 当前令牌数
    lastRefill  time.Time     // 上次填充时间
    mu          sync.Mutex
}

func NewTokenBucket(capacity int, rate float64) *TokenBucket {
    return &TokenBucket{
        capacity:   capacity,
        rate:       rate,
        tokens:     float64(capacity),
        lastRefill: time.Now(),
    }
}

func (tb *TokenBucket) refill() {
    now := time.Now()
    elapsed := now.Sub(tb.lastRefill).Seconds()
    newTokens := elapsed * tb.rate
    tb.tokens = min(float64(tb.capacity), tb.tokens+newTokens)
    tb.lastRefill = now
}

func (tb *TokenBucket) Allow() bool {
    tb.mu.Lock()
    defer tb.mu.Unlock()
    tb.refill()
    if tb.tokens >= 1 {
        tb.tokens--
        return true
    }
    return false
}

func min(a, b float64) float64 {
    if a < b {
        return a
    }
    return b
}

使用时需为每个用户维护一个 TokenBucket 实例。


方案3:使用 golang.org/x/time/rate 包(官方限流器)

该包基于令牌桶算法,使用方便且性能优秀,适合单机限流。

安装

go get golang.org/x/time/rate</
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值