当前位置: 首页 > news >正文

golang+redis 实现分布式限流

实现分布式滑动窗口限流

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/go-redis/redis/v8"
)

// Redis 客户端
var ctx = context.Background()
var rdb = redis.NewClient(&redis.Options{
	Addr:     "localhost:6379", // Redis 服务器地址
	Password: "",               // 无密码
	DB:       0,                // 默认数据库
})

// 限流配置
const (
	windowSize  = 10               // 时间窗口(秒)
	maxRequests = 5                // 允许的最大请求数
	requestKey  = "sliding_window" // Redis Key
)

// 限流检查
func isAllowed() bool {
	now := time.Now().Unix()           // 当前时间戳
	minTime := now - int64(windowSize) // 窗口开始时间

	// 使用 Lua 脚本保证操作的原子性
	luaScript := `
        redis.call("ZREMRANGEBYSCORE", KEYS[1], "-inf", ARGV[1]) -- 清除过期请求
        local reqCount = redis.call("ZCOUNT", KEYS[1], "-inf", "+inf") -- 获取当前窗口内请求数
        if reqCount < tonumber(ARGV[2]) then
            redis.call("ZADD", KEYS[1], ARGV[3], ARGV[3]) -- 添加当前请求时间戳
            redis.call("EXPIRE", KEYS[1], ARGV[4]) -- 设置过期时间,防止 key 长期存在
            return 1
        else
            return 0
        end
    `

	result, err := rdb.Eval(ctx, luaScript, []string{requestKey}, minTime, maxRequests, now, windowSize).Int()
	if err != nil {
		fmt.Println("Redis error:", err)
		return false
	}
	return result == 1
}

func main() {
	for i := 0; i < 10; i++ {
		if isAllowed() {
			fmt.Println("Request allowed")
		} else {
			fmt.Println("Rate limit exceeded")
		}
		time.Sleep(1 * time.Second) // 模拟请求间隔
	}
}

令牌桶限流

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/go-redis/redis/v8"
)

// Redis 客户端
var ctx = context.Background()
var rdb = redis.NewClient(&redis.Options{
	Addr:     "localhost:6379", // Redis 地址
	Password: "",               // Redis 认证密码(无则留空)
	DB:       0,                // 使用默认数据库
})

// 令牌桶配置
const (
	bucketKey  = "token_bucket" // Redis 令牌桶 Key
	bucketSize = 10             // 令牌桶容量
	refillRate = 2              // 每秒补充的令牌数量
)

// 初始化令牌桶
func initBucket() {
	rdb.Set(ctx, bucketKey, bucketSize, 0) // 初始化令牌桶,存满令牌
}

// 获取令牌
func getToken() bool {
	luaScript := `
        local tokens = redis.call("GET", KEYS[1])
        if tokens == false then
            redis.call("SET", KEYS[1], ARGV[2])
            tokens = ARGV[2]
        end
        tokens = tonumber(tokens)
        if tokens > 0 then
            redis.call("DECR", KEYS[1])
            return 1
        else
            return 0
        end
    `
	result, err := rdb.Eval(ctx, luaScript, []string{bucketKey}, 1, bucketSize).Int()
	if err != nil {
		fmt.Println("Redis Error:", err)
		return false
	}
	return result == 1
}

// 定期补充令牌
func refillTokens() {
	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()
	for range ticker.C {
		rdb.IncrBy(ctx, bucketKey, refillRate)
		currentTokens, _ := rdb.Get(ctx, bucketKey).Int()
		if currentTokens > bucketSize {
			rdb.Set(ctx, bucketKey, bucketSize, 0)
		}
		fmt.Printf("Tokens refilled: %d\n", currentTokens)
	}
}

func main() {
	initBucket()
	go refillTokens() // 启动令牌补充协程

	for i := 0; i < 20; i++ {
		if getToken() {
			fmt.Println("Request allowed")
		} else {
			fmt.Println("Rate limit exceeded")
		}
		time.Sleep(50 * time.Millisecond) // 模拟请求间隔
	}
}

相关文章:

  • 蓝桥杯 握手问题
  • 【C#高阶编程】—单例模式详解
  • MySQL性能优化,sql优化有哪些,数据库如何优化设计(二)
  • 【软件工程】08_结构化设计方法
  • Bash 脚本基础
  • numpy学习笔记15:模拟100次随机游走,观察平均行为
  • 数据处理专题(二)
  • vue2 el-table跨分页多选以及多选回显
  • Springboot的MultipartFile,获取不到inputStream
  • SeaCMS代码审计
  • 基于深度学习的OCR+NLP,医疗化验单智能识别方案
  • 【量化实战】利用miniqmt实现远程下单的完整指南
  • 阿里开源QwQ-32B推理模型!32.5B vs 671B|仅需1/10成本
  • python函数的多种参数使用形式
  • R语言软件配置(自用)
  • 人工智能之数学基础:矩阵的降维
  • 对上传的图片进行压缩,以保证它的大小不超过X MB
  • 亚马逊新品广告投放策略:从零到爆单的全链路解析
  • 黑客如何查找网络安全漏洞
  • 用 pytorch 从零开始创建大语言模型(四):从零开始实现一个用于生成文本的GPT模型
  • 广西干旱程度有所缓解,未来一周旱情偏重地区降水量仍不足
  • 民生访谈|宝妈宝爸、毕业生、骑手……上海如何为不同人群提供就业保障
  • 气温“过山车”现象未来或更频繁且更剧烈
  • 野猪穿过江苏电视台楼前广场,被抓捕后送往红山森林动物园
  • 张家界乒乓球公开赛设干部职级门槛引关注,回应:仅限嘉宾组
  • 规范涉企案件审判执行工作,最高法今天发布通知