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

令牌桶说明

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

  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个令牌: 被拒绝")
	}
}
相关推荐
qq_2975746712 小时前
【实战教程】SpringBoot 实现多文件批量下载并打包为 ZIP 压缩包
java·spring boot·后端
马猴烧酒.16 小时前
【面试八股|Java集合】Java集合常考面试题详解
java·开发语言·python·面试·八股
闻哥20 小时前
从测试坏味道到优雅实践:打造高质量单元测试
java·面试·单元测试·log4j·springboot
计算机程序设计小李同学20 小时前
基于 Spring Boot + Vue 的龙虾专营店管理系统的设计与实现
java·spring boot·后端·spring·vue
Charlie_lll1 天前
力扣解题-[3379]转换数组
数据结构·后端·算法·leetcode
南风知我意9571 天前
【前端面试5】手写Function原型方法
前端·面试·职场和发展
VX:Fegn08951 天前
计算机毕业设计|基于springboot + vue云租车平台系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
汤姆yu1 天前
2026基于springboot的在线招聘系统
java·spring boot·后端
计算机学姐1 天前
基于SpringBoot的校园社团管理系统
java·vue.js·spring boot·后端·spring·信息可视化·推荐算法
java1234_小锋1 天前
Java高频面试题:SpringBoot如何自定义Starter?
java·spring boot·面试