高并发 Agent 令牌桶+ 限流 + 隔离 + Polly
📈 压力测试对比
有限制(Semaphore=5)
时间轴 → [0s] 5 个执行 ⬛⬛⬛⬛⬛ [1s] 5 个执行 ⬛⬛⬛⬛⬛ [2s] 5 个执行 ⬛⬛⬛⬛⬛ ... [10s] 5 个执行 ⬛⬛⬛⬛⬛ 总耗时:10 秒 API 压力:5 个/秒 ✅
无限制(无 Semaphore)
时间轴 → [0s] 50 个执行 ⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛ ↓ 🔥 API 限流 / 超时 / 失败 总耗时:1 秒(但大量失败) API 压力:50 个/秒 ❌
令牌桶算法原理【系统级】
采用**令牌桶算法(Token Bucket)**实现限流:
系统为每个用户维护一个独立的令牌桶
令牌桶按固定频率自动补充令牌
请求到达时,必须获取令牌才能执行
令牌充足时立即执行,令牌不足时排队或拒绝
令牌用完后按配置的补充速率恢复
📋 配置参数详解
1️⃣ TokenLimit(令牌桶容量)
| 属性 | 说明 |
|---|---|
| 含义 | 每个用户的令牌桶能存储的最大令牌数量 |
| 作用 | 控制用户突发请求的上限 |
| 初始状态 | 系统启动时令牌桶为满状态 |
| 消耗规则 | 每个请求消耗 1 个令牌 |
| 补充规则 | 令牌用完后按补充速率恢复,但不超过 TokenLimit 上限 |
| 效果 | TokenLimit 越大,允许的突发请求越多;为 0 时不能立即执行任何请求 |
2️⃣ TokensPerPeriod(每次补充令牌数量)
| 属性 | 说明 |
|---|---|
| 含义 | 每个补充周期内往令牌桶中添加的令牌数量 |
| 作用 | 控制用户的持续请求速率 |
| 补充条件 | 令牌桶未满时才补充,满了则停止补充 |
| 效果 | 与 ReplenishmentPeriod 配合决定每秒可用令牌数;值越大,用户可持续发送的请求越多 |
3️⃣ ReplenishmentPeriod(令牌补充周期)
| 属性 | 说明 |
|---|---|
| 含义 | 两次令牌补充之间的时间间隔 |
| 作用 | 控制令牌补充的时间频率 |
| 补充机制 | 定时器自动触发,无需手动干预 |
| 效果 | 与 TokensPerPeriod 配合决定每秒可用令牌数;周期越短,令牌恢复越快 |
4️⃣ QueueLimit(等待队列上限)
| 属性 | 说明 |
|---|---|
| 含义 | 令牌耗尽后,允许排队等待的请求最大数量 |
| 作用 | 提供缓冲机制,避免请求直接失败 |
| 入队条件 | 令牌桶为空且队列未满时,请求进入队列等待 |
| 出队条件 | 令牌补充后,队列头部请求优先获取令牌 |
| 效果 | QueueLimit 为 0 时令牌用完直接拒绝;值越大,缓冲能力越强,但请求等待时间可能越长 |
5️⃣ QueueProcessingOrder(队列处理顺序)
| 属性 | 说明 |
|---|---|
| 含义 | 队列中请求获取令牌的优先级顺序 |
| 可选值 | OldestFirst(先到先得) / NewestFirst(后来优先) |
| 作用 | 控制排队请求的执行顺序 |
| 推荐配置 | 生产环境推荐使用 OldestFirst,保证公平性 |
| 效果 | OldestFirst 保证先到达的请求先执行;NewestFirst 允许后到达的请求优先执行(适用于 VIP 场景) |
6️⃣ AutoReplenishment(自动补充)
| 属性 | 说明 |
|---|---|
| 含义 | 是否启用定时器自动补充令牌 |
| 可选值 | true(自动补充) / false(手动补充) |
| 作用 | 控制令牌补充的自动化程度 |
| 推荐配置 | 生产环境推荐设置为 true |
| 效果 | true 时系统自动按周期补充令牌;false 时需要手动触发补充逻辑 |
✅ 设计原则
| 原则 | 说明 |
|---|---|
| 每用户隔离 | 每个用户拥有独立的令牌桶,互不影响 |
| 令牌即配额 | 1 个令牌 = 1 个请求配额,无令牌不执行 |
| 自动恢复 | 令牌用完后按配置速率自动补充,无需手动干预 |
| 缓冲机制 | 令牌耗尽后允许排队等待,避免直接失败 |
| 公平优先 | 队列默认先到先得,保证请求处理公平性 |
| 配置灵活 | 所有参数从配置文件读取,支持动态调整 |
📋 令牌桶参数一句话总结
| 参数 | 一句话总结 |
|---|---|
| TokenLimit | 代表每个用户的令牌桶最大容量,桶满时最多可存储【TokenLimit】个令牌,一个令牌可发送一个请求! |
| TokensPerPeriod | 代表每次补充时往桶里添加的令牌数量,每经过【ReplenishmentPeriod】周期最多可补充【TokensPerPeriod】个令牌! |
| ReplenishmentPeriod | 代表两次补充之间的时间间隔,每隔【ReplenishmentPeriod】时间自动补充【TokensPerPeriod】个令牌! |
| QueueLimit | 代表令牌用完后允许排队等待的请求上限,队列满时最多可容纳【QueueLimit】个请求排队等待令牌补充! |
| QueueProcessingOrder | 代表队列中请求获取令牌的优先级顺序,设置为【OldestFirst】时先排队的请求优先获得令牌! |
| AutoReplenishment | 代表是否启用定时器自动补充令牌,设置为【true】时系统按周期自动补充,无需手动触发! |
🎯 完整串联版
TokenLimit 代表每个用户的令牌桶最大容量,桶满时最多可存储【TokenLimit】个令牌! TokensPerPeriod 代表每次补充时往桶里添加的令牌数量,每经过【ReplenishmentPeriod】周期最多可补充【TokensPerPeriod】个令牌! ReplenishmentPeriod 代表两次补充之间的时间间隔,每隔【ReplenishmentPeriod】时间自动补充【TokensPerPeriod】个令牌! QueueLimit 代表令牌用完后允许排队等待的请求上限,队列满时最多可容纳【QueueLimit】个请求排队等待令牌补充! QueueProcessingOrder 代表队列中请求获取令牌的优先级顺序,设置为【OldestFirst】时先排队的请求优先获得令牌! AutoReplenishment 代表是否启用定时器自动补充令牌,设置为【true】时系统按周期自动补充,无需手动触发! 一个令牌可发送一个请求,令牌用完后要么排队等待补充,要么直接拒绝!
总之一句话:
✅ 每个用户的令牌桶最多可存储【TokenLimit】个令牌,每经过【ReplenishmentPeriod】时间最多可补充【TokensPerPeriod】个令牌,一个令牌可发送一个请求!请求队列最大为QueueLimit个,也就是指:最多
QueueLimit个用户同时访问系统!
本项目相关
🏗️ 并发的完整防护体系(结合 KernelFactory)
┌─────────────────────────────────────────────────────────────────────────┐ │ 高并发防护体系 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ L1: RateLimitingService(入口限流) │ │ │ │ ├─ 作用域:每用户独立(ConcurrentDictionary<string, RateLimiter>) │ │ │ │ ├─ 控制:每用户每秒最多 N 个请求 │ │ │ │ ├─ 位置:Main 函数中手动调用 │ │ │ │ └─ 保护:系统整体不被单个用户打爆 │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ L2: SemaphoreSlim(系统并发控制) │ │ │ │ ├─ 作用域:全局共享(static 或单例) │ │ │ │ ├─ 控制:同时最多 N 个任务执行 │ │ │ │ ├─ 位置:Main 函数中手动调用 │ │ │ │ └─ 保护:下游 API 不被并发请求打爆 │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ L3: KernelFactory(Session 隔离) │ │ │ │ ├─ 作用域:每 Session 独立(ConcurrentDictionary<string, Kernel>) │ │ │ │ ├─ 控制:每用户独立 Kernel 实例 │ │ │ │ ├─ 位置:KernelFactory.GetOrCreateKernel() │ │ │ │ └─ 保护:会话数据不交叉,用户隔离 │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ L4: RateLimitingFilter(函数限流) │ │ │ │ ├─ 作用域:每插件独立(Dictionary<string, RateLimiter>) │ │ │ │ ├─ 控制:每插件每分钟最多 N 次调用 │ │ │ │ ├─ 位置:kernel.FunctionInvocationFilters.Add() │ │ │ │ └─ 保护:插件/工具不被过度调用 │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ L5: PollyFunctionFilter(重试 + 熔断 + 超时) │ │ │ │ ├─ 作用域:每插件独立(Dictionary<string, AsyncPolicyWrap>) │ │ │ │ ├─ 控制:失败重试 N 次,熔断 M 次,超时 T 秒 │ │ │ │ ├─ 位置:kernel.FunctionInvocationFilters.Add() │ │ │ │ └─ 保护:下游故障时快速恢复 │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ L6: SessionMemoryStore(Qdrant 存储隔离) │ │ │ │ ├─ 作用域:每 Session 独立(每用户一个 Collection) │ │ │ │ ├─ 控制:每用户独立数据空间 │ │ │ │ ├─ 位置:Kernel.Data["MemoryStore"] │ │ │ │ └─ 保护:用户数据不泄露,存储隔离 │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘
📋 注册时机对比
| 组件 | 注册时机 | 注册方式 | 生命周期 |
|---|---|---|---|
| RateLimitingService | Main 函数初始化 | new RateLimitingService(config) |
全局单例 |
| SemaphoreSlim | Main 函数初始化 | new SemaphoreSlim(5) |
全局单例 |
| KernelFactory | Main 函数初始化 | new KernelFactory(config, ...) |
全局单例 |
| RateLimitingFilter | Kernel 创建时 | kernel.FunctionInvocationFilters.Add() |
每 Kernel 实例 |
| PollyFunctionFilter | Kernel 创建时 | kernel.FunctionInvocationFilters.Add() |
每 Kernel 实例 |
| SessionMemoryStore | Kernel 创建时 | kernel.Data["MemoryStore"] = new SessionMemoryStore() |
每 Session 实例 |
🎯 请求执行流程
用户请求 ↓ ┌─────────────────────────────────────────┐ │ L1: RateLimitingService.TryAcquireAsync │ ← 有令牌吗? │ └─ 无 → 拒绝 ❌ │ │ └─ 有 → 继续 ✅ │ └─────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────┐ │ L2: SemaphoreSlim.WaitAsync │ ← 有并发配额吗? │ └─ 无 → 等待 ⏳ │ │ └─ 有 → 继续 ✅ │ └─────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────┐ │ L3: KernelFactory.GetOrCreateKernel │ ← 获取 Session 隔离的 Kernel │ └─ 每用户独立 Kernel 实例 │ └─────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────┐ │ L4: RateLimitingFilter │ ← 插件调用限流 │ └─ 超限 → 拒绝 ❌ │ │ └─ 未超 → 继续 ✅ │ └─────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────┐ │ L5: PollyFunctionFilter │ ← 重试 + 熔断 + 超时 │ └─ 失败 → 重试 🔁 │ │ └─ 熔断 → 快速失败 ⚡ │ │ └─ 成功 → 继续 ✅ │ └─────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────┐ │ L6: SessionMemoryStore.UpsertAsync │ ← 存储到独立 Collection │ └─ 每用户独立数据空间 │ └─────────────────────────────────────────┘ ↓ 返回结果
✅ 总结
| 层次 | 组件 | 作用域 | 控制对象 | 注册位置 |
|---|---|---|---|---|
| L1 | RateLimitingService | 每用户 | 入口请求 | Main 函数 |
| L2 | SemaphoreSlim | 全局 | 并发任务数 | Main 函数 |
| L3 | KernelFactory | 每 Session | Kernel 实例 | KernelFactory |
| L4 | RateLimitingFilter | 每插件 | 函数调用 | kernel.FunctionInvocationFilters.Add() |
| L5 | PollyFunctionFilter | 每插件 | 重试 + 熔断 + 超时 | kernel.FunctionInvocationFilters.Add() |
| L6 | SessionMemoryStore | 每 Session | 数据存储 | kernel.Data["MemoryStore"] |
✅ 核心
SemaphoreSlim 是控制系统整体并发任务数的! 防止系统被大量并发请求打爆!🫡
项目代码:
<ItemGroup>
<PackageReference Include="Microsoft.SemanticKernel" Version="1.78.0" />
<PackageReference Include="Qdrant.Client" Version="1.18.1" />
<PackageReference Include="Sdcb.DashScope" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="System.Threading.RateLimiting" Version="8.0.0" />
<PackageReference Include="Polly" Version="8.4.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
</ItemGroup>

using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; using Polly; using Polly.CircuitBreaker; using Polly.Retry; using Polly.Timeout; using Polly.Wrap; using Qdrant.Client; using Qdrant.Client.Grpc; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Text; using System.Text.Json; using System.Threading.RateLimiting; namespace ConsoleApp12 { #region 系统整体流量入口限流服务-处理高并发场景 /// <summary> /// 系统整体/流量入口级限流服务 【限制的同时允许多少人可以访问系统,令牌的补充机制】 /// </summary> public class RateLimitingService { private readonly ConcurrentDictionary<string, RateLimiter> _limiters = new(); private readonly int _tokenLimit; private readonly int _tokensPerPeriodCount; public RateLimitingService(IConfiguration config) { _tokenLimit = int.Parse(config["RateLimiting:TokenLimit"]!); _tokensPerPeriodCount = int.Parse(config["RateLimiting:TokensPerPeriodCount"]!); } public async Task<bool> TryAcquireAsync(string sessionId) { var limiter = _limiters.GetOrAdd(sessionId, _ => new TokenBucketRateLimiter(new() { TokenLimit = _tokenLimit, // 桶容量 TokensPerPeriod = _tokensPerPeriodCount, // 每次补充数量 ReplenishmentPeriod = TimeSpan.FromSeconds(1), // 补充周期 QueueLimit = 5, // ✅ 队列上限:最多排 5 个请求 QueueProcessingOrder = QueueProcessingOrder.OldestFirst, // 先到先得 AutoReplenishment = true // ✅ 自动补充:定时器自动补 })); using var lease = await limiter.AcquireAsync(1); return lease.IsAcquired; } } #endregion #region 用户会话、会话存储, 隔离核心工厂 public class KernelFactory { private readonly ConcurrentDictionary<string, Kernel> _kernels = new(); private readonly string _apiKey; private readonly string _endpoint; private readonly QdrantClient _qdrantClient; private readonly int _vectorSize; private readonly RateLimitingFilter _rateLimitFilter; private readonly PollyFunctionFilter _pollyFilter; public KernelFactory(IConfiguration config, RateLimitingFilter rateLimitFilter, PollyFunctionFilter pollyFilter) { _apiKey = config["DashScope:ApiKey"]!; _endpoint = config["DashScope:Endpoint"]!; _qdrantClient = new QdrantClient( host: config["Qdrant:Host"]!, port: int.Parse(config["Qdrant:Port"]!), apiKey: config["Qdrant:QdrantApiKey"]!); _vectorSize = int.Parse(config["Qdrant:VectorSize"]!); _rateLimitFilter = rateLimitFilter; _pollyFilter = pollyFilter; } /// <summary> /// 获取或创建 Kernel(自动注册过滤器) /// </summary> public Kernel GetOrCreateKernel(string sessionKey) { return _kernels.GetOrAdd(sessionKey, key => { Console.WriteLine($"[KernelFactory] Creating Kernel for Session: {key}"); var builder = Kernel.CreateBuilder(); builder.AddOpenAIChatCompletion( modelId: "qwen-plus", apiKey: _apiKey, endpoint: new Uri(_endpoint)); var kernel = builder.Build(); // ✅ 正确 kernel.FunctionInvocationFilters.Add(_rateLimitFilter); kernel.FunctionInvocationFilters.Add(_pollyFilter); kernel.Data["SessionKey"] = key; kernel.Data["MemoryStore"] = new SessionMemoryStore(_qdrantClient, key, _vectorSize); return kernel; }); } public SessionMemoryStore GetMemoryStore(Kernel kernel) { return kernel.Data["MemoryStore"] as SessionMemoryStore ?? throw new InvalidOperationException("MemoryStore not found"); } public void RemoveKernel(string sessionKey) { if (_kernels.TryRemove(sessionKey, out _)) { Console.WriteLine($"[KernelFactory] Removed Kernel for Session: {sessionKey}"); } } } public class SessionMemoryStore { private readonly QdrantClient _client; private readonly string _collectionName; private readonly int _vectorSize; private bool _collectionCreated; public SessionMemoryStore(QdrantClient client, string sessionId, int vectorSize) { _client = client; _collectionName = $"session_{sessionId}".Replace(":", "_").Replace("-", "_"); _vectorSize = vectorSize; } private async Task EnsureCollectionExistsAsync() { if (_collectionCreated) return; var exists = await _client.CollectionExistsAsync(_collectionName); if (!exists) { await _client.CreateCollectionAsync(_collectionName, new VectorParams { Size = (ulong)_vectorSize, Distance = Distance.Cosine }); } _collectionCreated = true; } public async Task UpsertAsync(string id, float[] vector, string text, string description) { await EnsureCollectionExistsAsync(); var point = new PointStruct { Id = new PointId { Uuid = id }, Vectors = vector, Payload = { ["text"] = text, ["description"] = description } }; await _client.UpsertAsync(_collectionName, new[] { point }); } } #endregion #region 函数/插件级限流组件 /// <summary> /// 函数/插件级限流组件 /// </summary> public class RateLimitingFilter : IFunctionInvocationFilter { private readonly RateLimiter _rateLimiter; private readonly Dictionary<string, RateLimiter> _pluginLimiters; /// <summary> /// 全局限流 /// </summary> public RateLimitingFilter(int requestsPerMinute = 60) { _rateLimiter = new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions { PermitLimit = requestsPerMinute, Window = TimeSpan.FromMinutes(1), QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 0 // ⚠️ 关键:队列限制设为 0,不等待 }); _pluginLimiters = new(); } /// <summary> /// 按插件限流 /// </summary> public void AddPluginLimit(string pluginName, int requestsPerMinute) { _pluginLimiters[pluginName] = new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions { PermitLimit = requestsPerMinute, Window = TimeSpan.FromMinutes(1), QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 0 // ⚠️ 关键:队列限制设为 0,不等待 }); } public async Task OnFunctionInvocationAsync( FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next) { // 选择限流器(插件级别优先) var limiter = _pluginLimiters.TryGetValue(context.Function.PluginName, out var pluginLimiter) ? pluginLimiter : _rateLimiter; // ✅ 修复:使用 AttemptAcquire 立即返回,不等待 using var lease = limiter.AttemptAcquire(1); if (!lease.IsAcquired) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"🚫 触发限流:{context.Function.PluginName}.{context.Function.Name}"); Console.ResetColor(); throw new RateLimitExceededException( $"🚫 函数调用频率超限:{context.Function.PluginName}.{context.Function.Name}"); } Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine($"🚦 限流通过:{context.Function.Name}"); Console.ResetColor(); await next(context); } } /// <summary> /// 限流异常 /// </summary> public class RateLimitExceededException : Exception { public RateLimitExceededException(string message) : base(message) { } } #endregion #region 超时/重试/熔断策略--函数/插件级别过滤器 public class PollyFunctionFilter : IFunctionInvocationFilter { private readonly ILogger<PollyFunctionFilter> _logger; // ✅ 全部使用非泛型 private readonly AsyncRetryPolicy _retryPolicy; private readonly AsyncCircuitBreakerPolicy _circuitBreaker; private readonly AsyncTimeoutPolicy _timeoutPolicy; private readonly AsyncPolicyWrap _policyWrap; // ✅ 非泛型组合 private readonly Dictionary<string, AsyncPolicyWrap> _pluginPolicies; /// <summary> /// 全局 Polly 策略 /// </summary> public PollyFunctionFilter(ILogger<PollyFunctionFilter> logger, int maxRetries = 3, int circuitBreakThreshold = 5, int timeoutSeconds = 20) { _logger = logger; // ✅ 重试:最多 3 次,指数退避(非泛型) _retryPolicy = Policy .Handle<Exception>() .WaitAndRetryAsync( retryCount: 3, sleepDurationProvider: retry => TimeSpan.FromSeconds(Math.Pow(1.1, retry)), onRetry: (outcome, timeSpan, retryNumber, context) => { _logger.LogWarning( "任务 {TaskId} 重试 {Retry}/{Max},等待 {Wait}s,原因:{Error}", context["TaskId"], retryNumber, 3, timeSpan.TotalSeconds, outcome.GetBaseException()?.Message); } ); // ✅ 熔断:连续失败 5 次 → 熔断 30 秒(非泛型) _circuitBreaker = Policy .Handle<Exception>() .CircuitBreakerAsync( exceptionsAllowedBeforeBreaking: circuitBreakThreshold, durationOfBreak: TimeSpan.FromSeconds(30), onBreak: (outcome, timeSpan) => { _logger.LogError("⚠️ 熔断器打开!连续失败 {Count} 次,暂停 {Seconds}s", circuitBreakThreshold, timeSpan.TotalSeconds); }, onReset: () => { _logger.LogInformation("✅ 熔断器复位,恢复正常"); } ); // ✅ 超时:20 秒(非泛型) _timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(timeoutSeconds)); // ✅ 组合策略(返回非泛型 AsyncPolicyWrap) // 执行顺序:Timeout → CircuitBreaker → Retry _policyWrap = Policy.WrapAsync(_timeoutPolicy, _circuitBreaker, _retryPolicy); _pluginPolicies = new(); } /// <summary> /// 为特定插件设置独立策略 /// </summary> public void AddPluginPolicy(string pluginName, int maxRetries = 3, int circuitBreakThreshold = 5, int timeoutSeconds = 20) { var retry = Policy .Handle<Exception>() .WaitAndRetryAsync(maxRetries, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt - 1))); var breaker = Policy .Handle<Exception>() .CircuitBreakerAsync(circuitBreakThreshold, TimeSpan.FromSeconds(30)); var timeout = Policy.TimeoutAsync(TimeSpan.FromSeconds(timeoutSeconds)); _pluginPolicies[pluginName] = Policy.WrapAsync(timeout, breaker, retry); _logger.LogInformation("🔧 插件 {Plugin} 独立策略:重试={Retries}, 熔断={Break}, 超时={Timeout}s", pluginName, maxRetries, circuitBreakThreshold, timeoutSeconds); } public async Task OnFunctionInvocationAsync( FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next) { // 选择策略(插件级优先) var policy = _pluginPolicies.TryGetValue(context.Function.PluginName, out var pluginPolicy) ? pluginPolicy : _policyWrap; try { _logger.LogInformation("🛡️ Polly 执行:{Plugin}.{Function}", context.Function.PluginName, context.Function.Name); // ✅ 用 Polly 包装执行 await policy.ExecuteAsync( async (ctx) => { await next(context); }, new Context { ["PluginName"] = context.Function.PluginName, ["FunctionName"] = context.Function.Name } ); } catch (BrokenCircuitException ex) { _logger.LogError("⚡ 熔断触发:{Plugin}.{Function} - {Error}", context.Function.PluginName, context.Function.Name, ex.Message); throw; } catch (TimeoutRejectedException ex) { _logger.LogError("⏱️ 超时触发:{Plugin}.{Function} - {Error}", context.Function.PluginName, context.Function.Name, ex.Message); throw; } catch (Exception ex) { _logger.LogError("❌ Polly 失败:{Plugin}.{Function} - {Error}", context.Function.PluginName, context.Function.Name, ex.Message); throw; } } } #endregion #region 向量服务 public class EmbeddingService { private readonly string _apiKey; private readonly HttpClient _httpClient = new(); public EmbeddingService(string apiKey) { _apiKey = apiKey; } public async Task<float[]> GenerateEmbeddingAsync(string text) { var endpoint = "https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings"; var requestBody = new { model = "text-embedding-v2", input = text }; var json = JsonSerializer.Serialize(requestBody); var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); _httpClient.DefaultRequestHeaders.Clear(); _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}"); var response = await _httpClient.PostAsync(endpoint, content); var responseJson = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { throw new Exception($"DashScope API 错误:{response.StatusCode}\n{responseJson}"); } using var doc = JsonDocument.Parse(responseJson); var embedding = doc.RootElement.GetProperty("data")[0].GetProperty("embedding"); var vector = new float[embedding.GetArrayLength()]; int idx = 0; foreach (var item in embedding.EnumerateArray()) { vector[idx++] = item.GetSingle(); } return vector; } } #endregion }

using ConsoleApp11; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; using Polly; using Polly.CircuitBreaker; using Polly.Retry; using Polly.Timeout; using Polly.Wrap; using Qdrant.Client; using Qdrant.Client.Grpc; using System.Collections.Concurrent; using System.ComponentModel; using System.Text.Json; using System.Threading.RateLimiting; namespace ConsoleApp12 { internal class Program { static async Task Main(string[] args) { Console.WriteLine("╔═══════════════════════════════════════════════════════════╗"); Console.WriteLine("║ 高并发 Agent Demo - 限流 + 隔离 + Polly ║"); Console.WriteLine("╚═══════════════════════════════════════════════════════════╝\n"); // ========== 1️⃣ 加载配置 ========== var config = new ConfigurationBuilder() .AddJsonFile("appsettings.json", optional: false) .Build(); // ========== 2️⃣ 初始化日志 ========== var loggerFactory = LoggerFactory.Create(builder => builder .AddConsole() .SetMinimumLevel(LogLevel.Information)); var pollyLogger = loggerFactory.CreateLogger<PollyFunctionFilter>(); // ========== 3️⃣ 初始化组件 ========== var rateLimitingService = new RateLimitingService(config); var embeddingService = new EmbeddingService(config["DashScope:ApiKey"]!); // ========== 4️⃣ 函数限流过滤器 ========== var functionRateLimiter = new RateLimitingFilter(requestsPerMinute: 60); functionRateLimiter.AddPluginLimit("BingSearch", 30); functionRateLimiter.AddPluginLimit("DatabasePlugin", 100); // ========== 5️⃣ Polly 过滤器 ========== var pollyFilter = new PollyFunctionFilter( logger: pollyLogger, maxRetries: 3, circuitBreakThreshold: 5, timeoutSeconds: 20 ); pollyFilter.AddPluginPolicy("BingSearch", maxRetries: 5, circuitBreakThreshold: 3, timeoutSeconds: 30); pollyFilter.AddPluginPolicy("DatabasePlugin", maxRetries: 2, circuitBreakThreshold: 10, timeoutSeconds: 10); // ========== 6️⃣ 系统级并发控制 ========== var systemSemaphore = new SemaphoreSlim(5); // ========== 7️⃣ 创建 Kernel 工厂(带过滤器)========== var kernelFactory = new KernelFactory(config, functionRateLimiter, pollyFilter); Console.WriteLine("\n📋 组件初始化完成:"); Console.WriteLine($" ✅ 入口限流:每用户 {config["RateLimiting:TokenLimit"]} 令牌/秒"); Console.WriteLine($" ✅ 系统并发:最多 5 个任务"); Console.WriteLine($" ✅ 函数限流:全局限流 60 次/分钟"); Console.WriteLine($" ✅ Polly 策略:重试 3 次 + 熔断 5 次 + 超时 20 秒\n"); // ========== 8️⃣ 模拟高并发请求 ========== Console.WriteLine("🚀 开始模拟 50 个并发请求...\n"); var requests = Enumerable.Range(1, 50).Select(i => $"user-{i:D2}"); var successCount = 0; var failCount = 0; var rateLimitCount = 0; await Task.WhenAll(requests.Select(async sessionId => { // 1️⃣ 入口限流 if (!await rateLimitingService.TryAcquireAsync(sessionId)) { Interlocked.Increment(ref rateLimitCount); Console.WriteLine($"[Session {sessionId}] ❌ 入口限流拒绝"); return; } // 2️⃣ 系统级并发控制 await systemSemaphore.WaitAsync(); try { // 3️⃣ 获取 Session 隔离的 Kernel(带过滤器) var kernel = kernelFactory.GetOrCreateKernel(sessionId); // 4️⃣ 执行对话(封装方法) var response = await ExecuteChatAsync(kernel, sessionId); Console.WriteLine($"[Session {sessionId}] ✅ {response}"); // 5️⃣ 存储到 Qdrant(封装方法) await SaveMemoryAsync(kernelFactory, sessionId, response); Interlocked.Increment(ref successCount); } catch (BrokenCircuitException) { Interlocked.Increment(ref failCount); Console.WriteLine($"[Session {sessionId}] ⚡ 熔断器打开"); } catch (TimeoutRejectedException) { Interlocked.Increment(ref failCount); Console.WriteLine($"[Session {sessionId}] ⏱️ 任务超时"); } catch (Exception ex) { Interlocked.Increment(ref failCount); Console.WriteLine($"[Session {sessionId}] ❌ 错误:{ex.Message}"); } finally { systemSemaphore.Release(); } })); // ========== 9️⃣ 统计结果 ========== Console.WriteLine("\n╔═══════════════════════════════════════════════════════════╗"); Console.WriteLine("║ 📊 执行统计 ║"); Console.WriteLine("╚═══════════════════════════════════════════════════════════╝"); Console.WriteLine($" 总请求数:{requests.Count()}"); Console.WriteLine($" ✅ 成功:{successCount}"); Console.WriteLine($" ❌ 失败:{failCount}"); Console.WriteLine($" 🚫 限流:{rateLimitCount}"); Console.WriteLine($" 成功率:{(double)successCount / requests.Count() * 100:F1}%\n"); Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } /// <summary> /// 【返回前封装】执行 LLM 对话 /// </summary> static async Task<string> ExecuteChatAsync(Kernel kernel, string sessionId) { var chatService = kernel.GetRequiredService<IChatCompletionService>(); var chatHistory = new ChatHistory(); chatHistory.AddSystemMessage($"你是{sessionId}的专属客服,编号{sessionId}"); chatHistory.AddUserMessage("请如实回答你的客服编号?"); var response = await chatService.GetChatMessageContentAsync(chatHistory); return response.Content ?? "无回复"; } /// <summary> /// 【返回后封装】存储记忆到 Qdrant /// </summary> static async Task SaveMemoryAsync(KernelFactory kernelFactory, string sessionId, string response) { var kernel = kernelFactory.GetOrCreateKernel(sessionId); var memoryStore = kernelFactory.GetMemoryStore(kernel); var embeddingService = new EmbeddingService( ConstParm.apiKey ); var vector = await embeddingService.GenerateEmbeddingAsync($"你好 {response}"); await memoryStore.UpsertAsync(Guid.NewGuid().ToString(), vector, "你好", response); } } public class BingSearchPlugin { private readonly HttpClient _http = new(); private readonly string _apiKey = "你的 Bing API Key"; [KernelFunction("Search")] [Description("使用 Bing 搜索网络信息")] public async Task<string> SearchAsync([Description("搜索关键词")] string query) { // 模拟搜索(实际调用 Bing API) await Task.Delay(100); return $"Bing 搜索结果:关于\"{query}\"的相关信息..."; } } public class DatabasePlugin { [KernelFunction("Query")] [Description("查询数据库")] public async Task<string> QueryAsync([Description("SQL 查询")] string sql) { // 模拟查询(实际调用数据库) await Task.Delay(50); return $"数据库查询结果:{sql}"; } [KernelFunction("Insert")] [Description("插入数据")] public async Task<string> InsertAsync([Description("表名")] string table, [Description("数据")] string data) { await Task.Delay(50); return $"插入成功:{table} - {data}"; } } }
配置文件:
{ "DashScope": { "ApiKey": "sk-adc9xxxxxxxxxxxxxxxxxxxxxxx01d", "Endpoint": "https://dashscope.aliyuncs.com/compatible-mode/v1" }, "Qdrant": { "Host": "1.1.11.1", "Port": 6334, "QdrantApiKey": "xxxxxxxxxxxxx", "VectorSize": 1536 }, "RateLimiting": { "TokenLimit": 10, "TokensPerPeriodCount": 2 //补充周期内,每次补充令牌的数量 } }

浙公网安备 33010602011771号