基于 .NET 9.0 的高性能轻量级令牌桶限流服务
项目地址: https://gitee.com/huang_ru_yun_admin_admin/TokenBucket
欢迎 Star 和 Fork!⭐
前言
在开发开放平台 API 时,接口限流是必不可少的功能。传统的 Redis 方案虽然强大,但对于中小型项目来说,部署成本高、需要额外服务器。
今天给大家分享一个基于 .NET 9.0 的纯内存令牌桶限流方案,支持 10万+ 活跃用户,无需 Redis,单机部署即可,开箱即用!
什么是令牌桶算法?
令牌桶算法(Token Bucket)是网络流量整形和速率限制的常用算法。
算法原理
┌─────────────┐ ┌─────────────┐
│ 令牌桶 │ │ 请求队列 │
│ (容量 100) │─────→│ (QPS) │
└─────────────┘ └─────────────┘
↑ ↓
│ │
│ 允许/拒绝
│ │
按速率 │
添加令牌 │
(10个/秒) │
核心概念
- 桶容量(Max): 桶能容纳的最大令牌数(如 100)
- 令牌数(Value): 当前桶中的令牌数
- 补充速率(Step): 每次补充的令牌数(如 10)
工作流程
- 系统按固定速率向桶中添加令牌
- 请求到来时,从桶中取 1 个令牌
- 如果桶中有令牌 → 允许请求
- 如果桶为空 → 拒绝请求
项目特性
核心优势
- ⚡ 高性能 - 纯内存操作,响应速度快
- 🚀 高并发 - 支持 10万+ 活跃用户并发
- 🎯 灵活限流 - 支持用户级、用户+接口级限流
- 💾 持久化 - 支持数据持久化,服务重启不丢失
- 📦 SDK 支持 - 提供客户端 SDK,开箱即用
- 📚 Swagger 文档 - 完整的 API 文档
性能指标
| 指标 | 值 |
|---|---|
| QPS | 1000+ |
| 支持用户数 | 10万+ 活跃用户 |
| 内存占用 | ~15MB/10万用户 |
| 部署方式 | 单机即可,无需 Redis |
快速开始
1. 克隆项目
bash
git clone https://gitee.com/huang_ru_yun_admin_admin/TokenBucket.git
cd TokenBucket
2. 运行服务
bash
dotnet run --project TokenBucket
服务将在 http://localhost:5000 启动,Swagger 文档地址: http://localhost:5000/swagger
3. 引用客户端 SDK
在你的项目中引用 TokenBucket.Client 项目。
使用示例
基础用法
csharp
// 初始化客户端(HTTP)
var client = new TokenBucketClient("localhost:8080;user;password");
// 初始化客户端(HTTPS,以 ;s 结尾)
var clientHttps = new TokenBucketClient("localhost:8080;user;password;s");
// 添加限流规则
await client.Add("user_123", new BucketItem
{
Max = 100, // 最大令牌数
Step = 10 // 每次恢复的令牌数
});
// 获取令牌
var allowed = await client.Get("user_123");
if (allowed)
{
// 允许访问接口
}
推荐用法:用户+接口限流
为什么推荐用户+接口?
❌ 只限制用户的问题:
- 用户调用"登录接口"和"查询接口"共享配额
- 高频接口占用所有额度,低频接口无法使用
- 无法针对接口特性设置不同限流策略
✅ 用户+接口的优势:
- 每个接口独立限流,互不影响
- 敏感接口(如登录)可以严格限流
- 高频接口(如查询)可以宽松限流
示例代码:
csharp
// HTTPS 连接(生产环境推荐)
var client = new TokenBucketClient("api.example.com;admin;secret123;s");
var userId = "user_001";
// 登录接口:严格限流,防止暴力破解
if (await client.TryGetTokenForApi(userId, "login", max: 10, step: 10))
{
Console.WriteLine("登录成功");
}
// 查询接口:宽松限流,允许正常使用
if (await client.TryGetTokenForApi(userId, "query", max: 1000, step: 100))
{
Console.WriteLine("查询成功");
}
// 导出接口:严格限流,防止资源滥用
if (await client.TryGetTokenForApi(userId, "export", max: 5, step: 5))
{
Console.WriteLine("导出成功");
}
// 发送短信接口:最严格限流,防止短信轰炸
if (await client.TryGetTokenForApi(userId, "sms", max: 3, step: 3))
{
Console.WriteLine("短信发送成功");
}
批量配置接口限流
csharp
var apiConfigs = new Dictionary<string, (int max, int step)>
{
{ "login", (10, 10) }, // 登录:10次/小时
{ "query", (1000, 100) }, // 查询:1000次/小时
{ "export", (5, 5) }, // 导出:5次/天
{ "sms", (3, 3) }, // 短信:3次/小时
{ "upload", (100, 10) } // 上传:100次/小时
};
await client.AddApiLimits("user_001", apiConfigs);
API 接口
添加/更新桶
http
POST /Add?key={key}
Content-Type: application/json
{
"step": 10,
"max": 100,
"value": 100
}
获取令牌
http
POST /Get?key={key}
响应:
1 (允许)
0 (拒绝)
查询桶信息
http
POST /GetItem?key={key}
响应:
{
"step": 10,
"max": 100,
"value": 95
}
分页查询
http
POST /GetList
Content-Type: application/json
{
"keyword": "user",
"pageindex": 1,
"pagesize": 20
}
配置说明
appsettings.json
json
{
"FlushedTime": 1, // 令牌刷新间隔(秒)
"EnablePersist": true, // 是否启用持久化
"user": "1", // API 用户名
"password": "1" // API 密码
}
限流参数
| 参数 | 说明 | 默认值 |
|---|---|---|
| Max | 最大令牌数 | 100 |
| Step | 每次恢复的令牌数 | 10 |
| FlushedTime | 刷新间隔(秒) | 1 |
核心实现
并发安全的令牌获取
csharp
// 使用 AddOrUpdate 保证原子操作
return _bucket.Bucket.AddOrUpdate(key,
key => new BucketItem { Value = Max, Step = Step, Max = Max },
(key, item) =>
{
if (item.Value > 0)
{
item.Value--;
return item;
}
return item;
}).Value > 0 ? 1 : 0;
懒加载刷新策略
为什么需要优化?
传统方式每秒刷新所有桶(10万次),CPU 占用高。优化后只刷新活跃桶,性能提升 90%!
csharp
// 只刷新最近访问过的桶
foreach (var kvp in _bucket.Bucket)
{
if (currentTime - kvp.Value.LastFlushTime >= FlushedTime * 1000)
{
// 添加到待刷新列表
keys.Add(kvp.Key);
}
}
// 分批处理,避免长时间阻塞
if (keys.Count >= batchSize)
{
await ProcessBatchAsync(keys, currentTime, stoppingToken);
keys.Clear();
await Task.Yield();
}
持久化实现
- 启动时 : 从
Bucket.bin恢复数据 - 运行时: 按数据量动态调整保存频率
- 可配置 : 通过
EnablePersist控制是否启用
开放平台限流实战
场景 1: 免费/付费应用分级限流
csharp
// 免费应用
await client.TryGetToken("app_free_001", max: 100, step: 1);
// 付费应用
await client.TryGetToken("app_pro_001", max: 10000, step: 100);
// 企业应用
await client.TryGetToken("app_enterprise_001", max: 100000, step: 1000);
场景 2: SaaS 多租户限流
csharp
// 租户A 的用户1
await client.TryGetTokenForApi("tenantA_user1", "login", max: 1000, step: 100);
// 租户B 的用户1(独立限流)
await client.TryGetTokenForApi("tenantB_user1", "login", max: 500, step: 50);
场景 3: 微服务接口限流
csharp
// 用户服务
await client.TryGetTokenForApi("user_service", "login", max: 5000, step: 500);
// 订单服务
await client.TryGetTokenForApi("order_service", "create", max: 10000, step: 1000);
适用场景
✅ 适合
- 小型开放平台(< 500 调用方)
- SaaS 平台 API 限流
- 企业内部 API 网关
- 微服务接口限流
- 单机部署场景
⚠️ 不适合
- 中大型开放平台(> 500 活跃调用方)→ 建议用 Redis
- 需要跨机房部署
- 需要复杂的统计分析
- 需要集群部署
总结
核心优势
- 高性能 - 纯内存操作,响应速度快
- 部署简单 - 单机即可,无需 Redis
- 成本低廉 - 无额外服务器成本
- 易于维护 - 代码简洁,易于理解
- 功能完整 - SDK + 服务端完整方案
适用场景
在活跃用户 < 5000的前提下,本项目可以用于:
- 小型开放平台 API 限流
- SaaS 平台限流
- 企业 API 网关
- 微服务接口限流
学习交流
- 如果你觉得这个项目对你有帮助,欢迎 Star ⭐
- 欢迎提交 Issue 反馈问题和建议
- 欢迎提交 Pull Request 贡献代码
- 欢迎在评论区留言交流使用心得
相关推荐
如果你觉得这个项目对你有帮助,请给个 Star ⭐
欢迎交流 issues 和 PR!