高并发 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>

 

image

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 
}
View Code

image

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}";
        }
    }
}
View Code

配置文件:

{
  "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 //补充周期内,每次补充令牌的数量 
  }
}

 

posted @ 2026-08-14 17:28  天才卧龙  阅读(8)  评论(0)    收藏  举报