实现一个单机版令牌桶限流器(字节)

令牌桶说明

令牌桶是一种常见的限流算法,它的工作原理如下:

  1. 令牌生成:系统以固定的速率向桶中添加令牌
  2. 令牌消耗:请求需要消耗令牌才能被处理
  3. 容量限制:桶有最大容量,多余的令牌会被丢弃
  4. 请求处理:当有请求到来时,如果有足够的令牌则处理,否则拒绝

具体参考这篇文章

参考golang实现

go 复制代码
package main

import (
	"fmt"
	"sync"
	"time"
)

// TokenBucket 令牌桶结构
type TokenBucket struct {
	capacity  int64        // 桶容量
	tokens    int64        // 当前令牌数
	rate      int64        // 每秒生成令牌速率
	mutex     sync.Mutex   // 互斥锁
	ticker    *time.Ticker // 定时器
	stopChan  chan bool    // 停止信号
}

// NewTokenBucket 创建一个新的令牌桶
func NewTokenBucket(capacity, rate int64) *TokenBucket {
	tb := &TokenBucket{
		capacity: capacity,
		tokens:   capacity, // 初始时桶是满的
		rate:     rate,
		ticker:   time.NewTicker(time.Second), // 每秒触发一次
		stopChan: make(chan bool),
	}
	
	// 启动后台goroutine定时生成令牌
	go func() {
		for {
			select {
			case <-tb.ticker.C:
				tb.mutex.Lock()
				// 添加令牌,不超过桶容量
				tb.tokens = min(tb.tokens+tb.rate, tb.capacity)
				tb.mutex.Unlock()
			case <-tb.stopChan:
				tb.ticker.Stop()
				return
			}
		}
	}()
	
	return tb
}

// Allow 判断是否允许执行操作
func (tb *TokenBucket) Allow() bool {
	return tb.AllowN(1)
}

// AllowN 判断是否允许执行n个操作
func (tb *TokenBucket) AllowN(n int64) bool {
   // 只负责检查和消费令牌,减少了锁的持有时间,提高了并发性能
	tb.mutex.Lock()
	defer tb.mutex.Unlock()
	
	// 判断令牌是否足够
	if tb.tokens >= n {
		tb.tokens -= n
		return true
	}
	
	return false
}

// Stop 停止令牌桶
func (tb *TokenBucket) Stop() {
	close(tb.stopChan)
}

// min 返回较小值
func min(a, b int64) int64 {
	if a < b {
		return a
	}
	return b
}

func main() {
	// 创建一个容量为10,每秒生成2个令牌的令牌桶
	bucket := NewTokenBucket(10, 2)
	defer bucket.Stop() // 程序结束前停止令牌桶,优雅关闭,避免协程泄漏
	
	fmt.Println("开始测试令牌桶限流器...")
	
	// 测试1: 连续快速请求
	fmt.Println("\n测试1: 连续快速请求")
	for i := 0; i < 15; i++ {
		if bucket.Allow() {
			fmt.Printf("请求%d: 通过\n", i+1)
		} else {
			fmt.Printf("请求%d: 被拒绝\n", i+1)
		}
		time.Sleep(100 * time.Millisecond) // 间隔100毫秒发送请求,15个请求在1.5s内发出,共需要15个令牌
	}
	
	// 等待一段时间让令牌桶重新填满
	fmt.Println("\n等待5秒让令牌桶重新填充...")
	time.Sleep(5 * time.Second)
	
	// 测试2: 短时间大量请求
	fmt.Println("\n测试2: 短时间大量请求")
	passed := 0
	for i := 0; i < 15; i++ {
		if bucket.Allow() {
			passed++
			fmt.Printf("请求%d: 通过\n", i+1)
		} else {
			fmt.Printf("请求%d: 被拒绝\n", i+1)
		}
	}
	fmt.Printf("总共通过: %d/15\n", passed)
	
	// 测试3: 批量请求
	fmt.Println("\n测试3: 批量请求测试")
        // 创建一个容量为20,每秒生成5个令牌的令牌桶
	bucket2 := NewTokenBucket(20, 5)
        // 优雅关闭
	defer bucket2.Stop()
	
	// 请求5个令牌
	if bucket2.AllowN(5) {
		fmt.Println("批量请求5个令牌: 通过")
	} else {
		fmt.Println("批量请求5个令牌: 被拒绝")
	}
	
	// 请求15个令牌(超过当前令牌数)
	if bucket2.AllowN(15) {
		fmt.Println("批量请求15个令牌: 通过")
	} else {
		fmt.Println("批量请求15个令牌: 被拒绝")
	}
	
	// 等待一段时间后再尝试
	time.Sleep(3 * time.Second)
	if bucket2.AllowN(15) {
		fmt.Println("等待3s后批量请求15个令牌: 通过")
	} else {
		fmt.Println("等待3s后批量请求15个令牌: 被拒绝")
	}
}
相关推荐
2401_8414956418 小时前
【Web开发】基于Flask搭建简单的应用网站
后端·python·flask·视图函数·应用实例·路由装饰器·调试模式
生命不息战斗不止(王子晗)18 小时前
2026面试大纲 - java数据结构与集合专题
java·数据结构·面试
Dragon Wu18 小时前
SpringBoot3 当前最新版knife4j openapi3 集成方案
spring boot·后端·springboot
女王大人万岁18 小时前
Go语言JSON标准库(encoding/json):功能解析与实战指南
服务器·开发语言·后端·golang·json
小高Baby@18 小时前
Go语言中面向对象的三大特性之继承的理解
开发语言·后端·golang
小高Baby@18 小时前
Go语言中面向对象的三大特性之封装的理解
开发语言·后端·golang
Ivanqhz18 小时前
向量化计算
开发语言·c++·后端·算法·支持向量机·rust
小沈同学呀18 小时前
SpringBoot 使用Docx4j实现 DOCX 转 PDF
spring boot·后端·pdf·docx4j
计算机学姐18 小时前
基于SpringBoot的校园流浪动物救助平台
java·spring boot·后端·spring·java-ee·tomcat·intellij-idea
想要一只奶牛猫18 小时前
SpringBoot 配置文件
java·spring boot·后端