7f1edfb2fd
- 新增 scripts/rate_limiter.py 核心模块 - TokenBucket: 令牌桶限流器(40 RPM 上限) - RequestScheduler: 四级优先级请求队列调度器 - CacheManager: 查询结果缓存(分 TTL 策略) - retry_with_backoff: 指数退避重试装饰器 - CoordinatedPoller: COO 统一轮询器 - 新增 scripts/test_rate_limiter.py 测试套件 - 覆盖令牌桶、缓存、队列、重试、轮询、压力测试 - 所有测试通过 ✅ - 新增 docs/BIZ-26-限流器使用文档.md - API 参考、使用示例、集成指南 - 缓存策略、降级策略、监控调试 实现参考:plans/BIZ-13_运行稳定性保障方案.md Co-authored-by: multica-agent <github@multica.ai>
306 lines
8.5 KiB
Python
306 lines
8.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
BIZ-26 限流器测试脚本
|
|
|
|
测试场景:
|
|
1. 令牌桶限流功能
|
|
2. 优先级队列调度
|
|
3. 缓存管理器
|
|
4. 重试机制
|
|
5. 429 错误模拟
|
|
|
|
运行方式:
|
|
python3 scripts/test_rate_limiter.py
|
|
"""
|
|
|
|
import sys
|
|
import time
|
|
import threading
|
|
from datetime import datetime
|
|
|
|
# 添加脚本目录到路径
|
|
sys.path.insert(0, "/home/vincent/.openclaw/workspace/costcodev/EnterpriseArchitect/scripts")
|
|
|
|
from rate_limiter import (
|
|
TokenBucket,
|
|
CacheManager,
|
|
RequestScheduler,
|
|
Priority,
|
|
retry_with_backoff,
|
|
CoordinatedPoller,
|
|
)
|
|
|
|
|
|
def test_token_bucket():
|
|
"""测试令牌桶限流器"""
|
|
print("=" * 60)
|
|
print("测试 1: 令牌桶限流器")
|
|
print("=" * 60)
|
|
|
|
# 创建限流器:40 RPM = 0.67 令牌/秒
|
|
bucket = TokenBucket(rate=40/60, capacity=40)
|
|
|
|
print(f"\n初始状态:{bucket.get_status()}")
|
|
|
|
# 快速消费 10 个令牌
|
|
print("\n快速消费 10 个令牌...")
|
|
success_count = 0
|
|
for i in range(10):
|
|
if bucket.consume():
|
|
success_count += 1
|
|
|
|
print(f"成功消费:{success_count}/10")
|
|
print(f"消费后状态:{bucket.get_status()}")
|
|
|
|
# 测试等待获取令牌
|
|
print("\n测试等待获取令牌...")
|
|
start = time.time()
|
|
got_token = bucket.wait_for_token(timeout=2.0)
|
|
elapsed = time.time() - start
|
|
|
|
print(f"等待耗时:{elapsed:.3f}s, 获取成功:{got_token}")
|
|
print(f"等待后状态:{bucket.get_status()}")
|
|
|
|
print("\n✅ 令牌桶测试完成\n")
|
|
|
|
|
|
def test_cache_manager():
|
|
"""测试缓存管理器"""
|
|
print("=" * 60)
|
|
print("测试 2: 缓存管理器")
|
|
print("=" * 60)
|
|
|
|
cache = CacheManager()
|
|
|
|
# 测试 WorkBoard 缓存(TTL 5 分钟)
|
|
print("\n1. 设置 WorkBoard 缓存(TTL 5 分钟)")
|
|
cache.set("workboard", {"query": "status=todo"}, [{"id": "card1", "title": "Test"}])
|
|
|
|
# 立即读取
|
|
result = cache.get("workboard", {"query": "status=todo"})
|
|
print(f" 立即读取:{result is not None}")
|
|
|
|
# 测试配置缓存(TTL 1 小时)
|
|
print("\n2. 设置配置缓存(TTL 1 小时)")
|
|
cache.set("config", "agent_list", ["costcodev", "secretary", "coo"])
|
|
result = cache.get("config", "agent_list")
|
|
print(f" 读取配置:{result}")
|
|
|
|
# 测试缓存统计
|
|
print("\n3. 缓存统计")
|
|
stats = cache.get_stats()
|
|
print(f" 总条目数:{stats['total_entries']}")
|
|
print(f" 按类别:{stats['by_category']}")
|
|
|
|
# 测试缓存删除
|
|
print("\n4. 删除缓存")
|
|
deleted = cache.delete("workboard", {"query": "status=todo"})
|
|
print(f" 删除成功:{deleted}")
|
|
result = cache.get("workboard", {"query": "status=todo"})
|
|
print(f" 删除后读取:{result is None}")
|
|
|
|
print("\n✅ 缓存管理器测试完成\n")
|
|
|
|
|
|
def test_priority_queue():
|
|
"""测试优先级队列调度"""
|
|
print("=" * 60)
|
|
print("测试 3: 优先级队列调度(简化版,不启动工作线程)")
|
|
print("=" * 60)
|
|
|
|
scheduler = RequestScheduler(rate=40/60, capacity=40, enable_cache=True)
|
|
|
|
# 模拟请求处理结果
|
|
results = []
|
|
|
|
def record_result(data):
|
|
results.append((time.time(), data))
|
|
return data
|
|
|
|
# 提交不同优先级的请求(不启动工作线程,只测试队列)
|
|
print("\n提交请求(按顺序):")
|
|
scheduler.submit(
|
|
payload={"task": "normal_1"},
|
|
priority=Priority.NORMAL,
|
|
callback=record_result
|
|
)
|
|
print(" 1. 正常优先级:normal_1")
|
|
|
|
scheduler.submit(
|
|
payload={"task": "urgent_1"},
|
|
priority=Priority.URGENT,
|
|
callback=record_result
|
|
)
|
|
print(" 2. 紧急优先级:urgent_1")
|
|
|
|
scheduler.submit(
|
|
payload={"task": "low_1"},
|
|
priority=Priority.LOW,
|
|
callback=record_result
|
|
)
|
|
print(" 3. 低优先级:low_1")
|
|
|
|
scheduler.submit(
|
|
payload={"task": "high_1"},
|
|
priority=Priority.HIGH,
|
|
callback=record_result
|
|
)
|
|
print(" 4. 高优先级:high_1")
|
|
|
|
# 查看队列大小
|
|
print(f"\n队列大小:{scheduler.get_queue_size()}")
|
|
|
|
# 查看状态
|
|
status = scheduler.get_status()
|
|
print(f"初始令牌数:{status['token_bucket']['tokens']}")
|
|
|
|
print("\n✅ 优先级队列测试完成(仅提交,未处理)\n")
|
|
|
|
|
|
def test_retry_decorator():
|
|
"""测试重试装饰器"""
|
|
print("=" * 60)
|
|
print("测试 4: 重试装饰器")
|
|
print("=" * 60)
|
|
|
|
attempt_count = [0]
|
|
|
|
@retry_with_backoff(max_retries=3, base_delay=0.1, jitter=False)
|
|
def flaky_function():
|
|
attempt_count[0] += 1
|
|
if attempt_count[0] < 3:
|
|
raise Exception(f"模拟失败 (尝试 {attempt_count[0]})")
|
|
return f"成功 (尝试 {attempt_count[0]})"
|
|
|
|
print("\n调用易失败函数(前 2 次失败,第 3 次成功)...")
|
|
start = time.time()
|
|
result = flaky_function()
|
|
elapsed = time.time() - start
|
|
|
|
print(f"结果:{result}")
|
|
print(f"总尝试次数:{attempt_count[0]}")
|
|
print(f"总耗时:{elapsed:.3f}s")
|
|
|
|
print("\n✅ 重试装饰器测试完成\n")
|
|
|
|
|
|
def test_coordinated_poller():
|
|
"""测试统一轮询器"""
|
|
print("=" * 60)
|
|
print("测试 5: COO 统一轮询器(简化版,短间隔测试)")
|
|
print("=" * 60)
|
|
|
|
scheduler = RequestScheduler(rate=40/60, capacity=40)
|
|
poller = CoordinatedPoller(scheduler, poll_interval=2) # 2 秒轮询一次(测试用)
|
|
|
|
received_results = []
|
|
|
|
def on_poll_result(result):
|
|
received_results.append((datetime.now().strftime("%H:%M:%S"), result))
|
|
print(f" [{datetime.now().strftime('%H:%M:%S')}] 收到轮询结果")
|
|
|
|
poller.subscribe(on_poll_result)
|
|
|
|
print("\n启动轮询器(轮询间隔 2 秒,运行 5 秒后停止)...")
|
|
poller.start()
|
|
|
|
# 等待 5 秒
|
|
time.sleep(5)
|
|
|
|
poller.stop()
|
|
|
|
print(f"\n收到结果次数:{len(received_results)}")
|
|
for ts, result in received_results:
|
|
print(f" {ts}: {result['timestamp'][:19]}")
|
|
|
|
print("\n✅ 统一轮询器测试完成\n")
|
|
|
|
|
|
def test_rate_limit_stress():
|
|
"""压力测试:快速提交大量请求"""
|
|
print("=" * 60)
|
|
print("测试 6: 压力测试(40 RPM 限制下提交 50 个请求)")
|
|
print("=" * 60)
|
|
|
|
scheduler = RequestScheduler(rate=40/60, capacity=40, enable_cache=True)
|
|
scheduler.start()
|
|
|
|
completed = []
|
|
failed = []
|
|
lock = threading.Lock()
|
|
|
|
def callback(data):
|
|
with lock:
|
|
completed.append(data)
|
|
return data
|
|
|
|
print("\n快速提交 50 个请求...")
|
|
start_time = time.time()
|
|
|
|
for i in range(50):
|
|
priority = Priority.NORMAL if i % 10 != 0 else Priority.URGENT
|
|
scheduler.submit(
|
|
payload={"index": i},
|
|
priority=priority,
|
|
callback=callback
|
|
)
|
|
|
|
print("提交完成,等待处理...")
|
|
|
|
# 等待 10 秒
|
|
time.sleep(10)
|
|
|
|
elapsed = time.time() - start_time
|
|
|
|
# 查看统计
|
|
status = scheduler.get_status()
|
|
print(f"\n耗时:{elapsed:.2f}s")
|
|
print(f"队列大小:{status['queue_size']}")
|
|
print(f"已完成:{status['stats']['completed_requests']}")
|
|
print(f"失败:{status['stats']['failed_requests']}")
|
|
print(f"降级:{status['stats']['fallback_requests']}")
|
|
print(f"令牌桶状态:{status['token_bucket']}")
|
|
|
|
scheduler.stop()
|
|
|
|
print("\n✅ 压力测试完成\n")
|
|
|
|
|
|
def main():
|
|
"""运行所有测试"""
|
|
print("\n")
|
|
print("╔" + "=" * 58 + "╗")
|
|
print("║" + " " * 58 + "║")
|
|
print("║" + " BIZ-26 限流器测试套件".center(58) + "║")
|
|
print("║" + " API 请求优先级队列 + 令牌桶限流".center(58) + "║")
|
|
print("║" + " " * 58 + "║")
|
|
print("╚" + "=" * 58 + "╝")
|
|
print()
|
|
|
|
try:
|
|
test_token_bucket()
|
|
test_cache_manager()
|
|
test_priority_queue()
|
|
test_retry_decorator()
|
|
test_coordinated_poller()
|
|
test_rate_limit_stress()
|
|
|
|
print("\n")
|
|
print("╔" + "=" * 58 + "╗")
|
|
print("║" + " " * 58 + "║")
|
|
print("║" + " ✅ 所有测试完成".center(58) + "║")
|
|
print("║" + " " * 58 + "║")
|
|
print("╚" + "=" * 58 + "╝")
|
|
print()
|
|
|
|
except KeyboardInterrupt:
|
|
print("\n\n⚠️ 测试被用户中断\n")
|
|
except Exception as e:
|
|
print(f"\n\n❌ 测试出错:{e}\n")
|
|
import traceback
|
|
traceback.print_exc()
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |