限流与配额
EnerOS 通过 eneros-gateway 实施分层限流,保护内核稳定并实现多租户公平调度。限流策略覆盖 IP、租户、用户、端点四个维度,可在配置文件中调整,运行时通过 eneros-tenant 动态下发配额。所有限流响应都附带标准 X-RateLimit-* 响应头,便于客户端实现自适应降速。
限流架构
EnerOS 采用四层限流模型,每一层都有独立的速率与突发上限:
请求 → ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ IP 限流 │ → │ 租户限流 │ → │ 用户限流 │ → │ 端点限流 │ → 内核
└─────────┘ └─────────┘ └─────────┘ └─────────┘
令牌桶 令牌桶 令牌桶 令牌桶
| 层级 | 维度 | 算法 | 作用 |
|---|---|---|---|
| L1 | 客户端 IP | 令牌桶 | 防恶意刷接口、DDoS 缓解 |
| L2 | 租户(Tenant) | 令牌桶 | 多租户公平调度、配额管理 |
| L3 | 用户/Agent | 令牌桶 | 单用户限流、防滥用 |
| L4 | 端点(Endpoint) | 漏桶 | 保护计算密集型端点 |
每层独立计数,任意一层触发即返回 429,响应头中携带触发层的详细信息。
限流策略
1. 全局 IP 限流
按 IP 维度统计,防止恶意刷接口与 DDoS 攻击。所有 IP 共享同一策略,黑名单 IP 直接拒绝。
| 接口类型 | 速率 | 突发 | 说明 |
|---|---|---|---|
公开端点(/health) | 60 req/min | 10 | 无需鉴权 |
认证端点(/auth/token) | 30 req/min | 5 | 防爆破 |
| 认证端点(其他) | 600 req/min | 50 | 默认 |
计算类端点(/powerflow、/opf) | 30 req/min | 5 | 保护算力 |
时序写入(/timeseries/batch) | 1000 req/min | 100 | 高吞吐 |
| WebSocket 连接建立 | 10 req/min | 2 | 防连接洪水 |
| SSE 连接建立 | 20 req/min | 5 | 防连接洪水 |
2. 租户配额
按租户维度分配资源配额,超限返回 429 或排队等待。配额可在 eneros.toml 中配置,也可通过 eneros-tenant 动态调整。
| 资源 | 默认配额 | 超限行为 | 是否可调 |
|---|---|---|---|
| API 请求 | 5000 req/h | 排队等待(最多 100) | 是 |
| WebSocket 连接 | 20 并发 | 拒绝新连接 | 是 |
| SSE 连接 | 50 并发 | 拒绝新连接 | 是 |
| 潮流计算任务 | 100 并发 | 排队(FIFO) | 是 |
| 时序写入 | 1M 点/天 | 拒绝写入 | 是 |
| 时序存储 | 10 GB/天 | 拒绝写入 | 是 |
| 审计日志保留 | 365 天 | 自动归档 | 否 |
| Agent 数量 | 100 | 拒绝创建 | 是 |
| 网络模型数量 | 50 | 拒绝创建 | 是 |
3. 用户/Agent 限流
针对单个用户或 Agent 的限流,防止单一实体占用过多资源。
| 资源 | 默认限制 | 说明 |
|---|---|---|
| 单用户 API 请求 | 1200 req/min | 普通用户 |
| 单 Agent 指令频率 | 10 cmd/s | 防止 Agent 失控 |
| 单用户 WebSocket | 5 并发 | — |
| 单用户 SSE | 10 并发 | — |
4. 端点限流
针对特定端点的细粒度限流,保护计算密集型或高资源消耗操作。
| 端点 | 速率 | 突发 | 说明 |
|---|---|---|---|
POST /networks/{id}/powerflow | 30 req/min | 5 | 潮流计算 |
POST /networks/{id}/opf | 10 req/min | 2 | 最优潮流 |
POST /networks/{id}/validate | 60 req/min | 10 | 拓扑校验 |
POST /agents/{id}/command | 100 req/min | 20 | Agent 指令 |
POST /timeseries/batch | 1000 req/min | 100 | 批量写入 |
DELETE /networks/{id} | 10 req/min | 2 | 删除保护 |
5. 指令限流
Agent 指令下发受安全网关二次校验:
| 指令类型 | 限制 | 说明 |
|---|---|---|
| 普通指令 | ≤ 10 cmd/s | 单 Agent |
| 控制类指令(开关/调节) | 100ms 最小间隔 + 双因子确认 | 高危操作 |
| 紧急停机指令 | 绕过限流,立即执行 | 应急场景 |
| 批量指令 | ≤ 50 cmd/batch | 单次批量上限 |
响应头说明
所有限流相关的响应头遵循 IETF 草案 draft-ietf-httpapi-ratelimit-headers:
| 响应头 | 类型 | 说明 |
|---|---|---|
X-RateLimit-Limit | int | 当前窗口内允许的最大请求数 |
X-RateLimit-Remaining | int | 当前窗口内剩余请求数 |
X-RateLimit-Reset | int | 窗口重置时间(Unix 时间戳,秒) |
X-RateLimit-Policy | string | 限流策略描述,如 60;w=60 表示 60 秒窗口内 60 次 |
Retry-After | int | 建议重试等待秒数(仅 429 响应) |
X-RateLimit-Tenant-Limit | int | 租户配额上限 |
X-RateLimit-Tenant-Remaining | int | 租户配额剩余 |
X-RateLimit-Concurrent | int | 当前并发连接数(仅 WS/SSE) |
成功响应示例:
HTTP/1.1 200 OK
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 598
X-RateLimit-Reset: 1783277460
X-RateLimit-Policy: 600;w=60
X-RateLimit-Tenant-Limit: 5000
X-RateLimit-Tenant-Remaining: 4998
Content-Type: application/json
限流响应
触发限流时返回 HTTP 429,响应体包含详细的限流信息:
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded: 600 req/min",
"details": {
"limit": 600,
"window_seconds": 60,
"retry_after": 12,
"reset_at": "2026-07-06T08:31:00Z",
"scope": "ip",
"ip": "192.168.1.10"
},
"trace_id": "trace_abc123"
},
"request_id": "req_20260706_001"
}
| 字段 | 类型 | 说明 |
|---|---|---|
error.code | string | 固定为 RATE_LIMITED |
error.details.limit | int | 当前窗口允许的最大请求数 |
error.details.window_seconds | int | 限流窗口(秒) |
error.details.retry_after | int | 建议重试等待秒数 |
error.details.reset_at | string | 窗口重置时间(RFC 3339) |
error.details.scope | string | 触发层级:ip/tenant/user/endpoint |
error.details.ip | string | 触发 IP(仅 scope=ip) |
error.details.tenant_id | string | 触发租户(仅 scope=tenant) |
配额管理
查询当前配额
通过 GET /api/v1/tenant/quota 查询当前租户的配额与使用情况:
请求参数:无
响应字段:
| 字段 | 类型 | 说明 |
|---|---|---|
tenant_id | string | 租户 ID |
plan | string | 套餐:free/pro/enterprise |
quotas | object | 各项配额上限 |
usage | object | 当前使用量 |
reset_at | string | 配额重置时间 |
cURL 示例:
curl http://localhost:8080/api/v1/tenant/quota \
-H "Authorization: Bearer <token>"
响应示例:
{
"data": {
"tenant_id": "tenant_default",
"plan": "pro",
"quotas": {
"api_requests_per_hour": 5000,
"ws_connections": 20,
"sse_connections": 50,
"concurrent_powerflow": 100,
"timeseries_daily_gb": 10,
"agent_count": 100,
"network_count": 50
},
"usage": {
"api_requests_this_hour": 1234,
"ws_connections": 3,
"sse_connections": 12,
"concurrent_powerflow": 2,
"timeseries_today_gb": 2.3,
"agent_count": 15,
"network_count": 8
},
"reset_at": "2026-07-06T09:00:00Z"
},
"request_id": "req_20260706_002",
"timestamp": "2026-07-06T08:30:00Z"
}
配额申请
当默认配额不足时,可通过以下方式申请提升:
| 套餐 | API 请求 | WS 连接 | SSE 连接 | 时序存储 | 适用场景 |
|---|---|---|---|---|---|
free | 1000/h | 5 | 10 | 1 GB/天 | 试用、开发 |
pro | 5000/h | 20 | 50 | 10 GB/天 | 小型生产 |
enterprise | 50000/h | 100 | 200 | 100 GB/天 | 大型生产 |
custom | 定制 | 定制 | 定制 | 定制 | 特殊需求 |
申请流程:
- 联系 EnerOS 商务团队(sales@openeneros.com)
- 提供租户 ID、预期用量、业务场景
- 签署补充协议
- 运维通过
eneros-tenant动态下发新配额,无需重启
动态调整配额
管理员可通过 API 动态调整租户配额:
curl -X PUT http://localhost:8080/api/v1/admin/tenants/tenant_default/quota \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{
"api_requests_per_hour": 10000,
"ws_connections": 50,
"concurrent_powerflow": 200,
"reason": "promotion_campaign",
"expires_at": "2026-07-13T00:00:00Z"
}'
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
api_requests_per_hour | int | 否 | — | API 请求配额 |
ws_connections | int | 否 | — | WebSocket 连接配额 |
sse_connections | int | 否 | — | SSE 连接配额 |
concurrent_powerflow | int | 否 | — | 并发潮流任务 |
timeseries_daily_gb | int | 否 | — | 时序存储配额 |
reason | string | 是 | — | 调整原因(审计) |
expires_at | datetime | 否 | — | 临时配额过期时间 |
配置示例
在 eneros.toml 中配置限流策略:
[gateway.rate_limit]
# 全局 IP 限流
global_rpm = 600 # 每分钟最大请求数
global_burst = 50 # 突发上限
auth_rpm = 30 # 认证端点
auth_burst = 5
compute_rpm = 30 # 计算类端点
compute_burst = 5
timeseries_rpm = 1000 # 时序写入
timeseries_burst = 100
# 租户配额默认值
[gateway.tenant_default]
api_requests_per_hour = 5000
ws_connections = 20
sse_connections = 50
concurrent_powerflow = 100
timeseries_daily_gb = 10
agent_count = 100
network_count = 50
# 端点级限流
[gateway.endpoint_limits]
"/networks/{id}/powerflow" = { rpm = 30, burst = 5 }
"/networks/{id}/opf" = { rpm = 10, burst = 2 }
"/agents/{id}/command" = { rpm = 100, burst = 20 }
# IP 黑白名单
[gateway.ip_filter]
whitelist = ["127.0.0.1", "10.0.0.0/8"]
blacklist = []
blacklist_ttl_seconds = 3600
# WebSocket 限流
[gateway.ws]
max_connections_per_tenant = 20
max_connections_per_user = 5
max_subscriptions_per_connection = 50
message_rate_per_second = 100
# SSE 限流
[gateway.sse]
max_connections_per_tenant = 50
max_connections_per_user = 10
max_topics_per_connection = 10
客户端最佳实践
1. 指数退避重试
import time
import random
import requests
def request_with_retry(url, headers, max_retries=5):
for attempt in range(max_retries):
response = requests.get(url, headers=headers)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
# 添加抖动,避免重试风暴
jitter = random.uniform(0, 0.5)
sleep_time = retry_after + jitter
print(f"限流,{sleep_time:.1f}s 后重试(第 {attempt + 1} 次)")
time.sleep(sleep_time)
continue
return response
raise Exception(f"达到最大重试次数: {max_retries}")
2. 自适应降速
import time
import requests
class AdaptiveClient:
def __init__(self, base_url, token):
self.base_url = base_url
self.headers = {"Authorization": f"Bearer {token}"}
self.min_interval = 0.01 # 10ms
self.max_interval = 1.0 # 1s
self.current_interval = self.min_interval
def request(self, path):
time.sleep(self.current_interval)
response = requests.get(f"{self.base_url}{path}", headers=self.headers)
remaining = int(response.headers.get("X-RateLimit-Remaining", 100))
if remaining < 10:
# 剩余配额不足,降速
self.current_interval = min(self.current_interval * 1.5, self.max_interval)
else:
# 配额充足,加速
self.current_interval = max(self.current_interval * 0.9, self.min_interval)
return response
3. 批量请求减少调用次数
# 错误:逐条写入
for point in points:
client.timeseries.write(point) # 1000 次请求
# 正确:批量写入
client.timeseries.batch_write(points) # 1 次请求
4. 长连接复用
# 错误:反复建立 WebSocket 连接
for alert in alerts:
ws = connect_websocket()
ws.send(alert)
ws.close()
# 正确:复用长连接
ws = connect_websocket()
for alert in alerts:
ws.send(alert)
# 连接保持,订阅事件
5. 监控剩余配额
response = client.get("/networks")
remaining = response.headers.get("X-RateLimit-Remaining")
tenant_remaining = response.headers.get("X-RateLimit-Tenant-Remaining")
if int(remaining) < 50:
logger.warning(f"IP 限流即将触发,剩余: {remaining}")
if int(tenant_remaining) < 500:
logger.warning(f"租户配额即将耗尽,剩余: {tenant_remaining}")
Rust 客户端示例
use std::time::{Duration, Instant};
use tokio::time::sleep;
pub struct RateLimitedClient {
client: reqwest::Client,
base_url: String,
token: String,
last_request: Instant,
min_interval: Duration,
}
impl RateLimitedClient {
pub fn new(base_url: &str, token: &str, rpm: u32) -> Self {
Self {
client: reqwest::Client::new(),
base_url: base_url.to_string(),
token: token.to_string(),
last_request: Instant::now() - Duration::from_secs(1),
min_interval: Duration::from_secs(60) / rpm,
}
}
pub async fn get(&mut self, path: &str) -> anyhow::Result<reqwest::Response> {
let elapsed = self.last_request.elapsed();
if elapsed < self.min_interval {
sleep(self.min_interval - elapsed).await;
}
self.last_request = Instant::now();
let resp = self.client
.get(format!("{}{}", self.base_url, path))
.bearer_auth(&self.token)
.send()
.await?;
if resp.status() == 429 {
let retry_after = resp.headers()
.get("Retry-After")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.parse::<u64>().ok())
.unwrap_or(60);
sleep(Duration::from_secs(retry_after)).await;
return self.get(path).await;
}
Ok(resp)
}
}
监控与告警
EnerOS 暴露以下限流指标,可通过 Prometheus 抓取:
| 指标 | 类型 | 说明 |
|---|---|---|
eneros_rate_limit_requests_total | counter | 请求总数(按 status、scope 维度) |
eneros_rate_limit_rejected_total | counter | 被限流拒绝的请求数 |
eneros_rate_limit_remaining | gauge | 当前剩余配额 |
eneros_ws_connections | gauge | 当前 WebSocket 连接数 |
eneros_sse_connections | gauge | 当前 SSE 连接数 |
eneros_powerflow_running | gauge | 运行中的潮流任务数 |
eneros_tenant_quota_usage_ratio | gauge | 租户配额使用率(0-1) |
推荐告警规则:
| 告警 | 条件 | 严重级别 |
|---|---|---|
| 限流拒绝率高 | rate(rejected_total[5m]) / rate(requests_total[5m]) > 0.1 | warn |
| 租户配额即将耗尽 | quota_usage_ratio > 0.9 | warn |
| WebSocket 连接数高 | ws_connections > 0.8 * limit | warn |
| 潮流任务排队 | powerflow_running > 0.8 * limit | warn |
相关文档
- REST API — 错误码定义
- WebSocket — 连接数限制
- SSE 实时推送 — 连接数限制
- eneros-gateway crate — 限流实现
- eneros-tenant crate — 多租户配额