速率限制

本文档详细介绍 Zanbara API 的速率限制机制,包括限制规则、响应头信息、超限处理以及优化建议。

速率限制概述

Zanbara 实施速率限制以确保:

  • 系统稳定性: 防止单个用户占用过多资源

  • 公平性: 保证所有用户获得公平的服务质量

  • 安全性: 防止 DDoS 攻击和恶意请求

速率限制同时基于 IP 地址API Key 进行计算。

限制规则

基础限制

端点类型
限制维度
IP 限制
API Key 限制
时间窗口

公开端点 - 市场数据

IP

60 请求

N/A

1 分钟

公开端点 - 系统信息

IP

20 请求

N/A

1 分钟

私有端点 - 查询类

IP + Key

120 请求

600 请求

1 分钟

私有端点 - 交易类

IP + Key

30 请求

120 请求

1 分钟

私有端点 - 批量操作

IP + Key

10 请求

30 请求

1 分钟

端点分类详情

公开端点 - 市场数据

限制: 60 请求/分钟 (IP)

包含以下端点:

GET  /v1/market/ticker/{symbol}
GET  /v1/market/orderbook/{symbol}
GET  /v1/market/trades/{symbol}
GET  /v1/market/klines/{symbol}
GET  /v1/market/funding-rate/{symbol}
GET  /v1/market/symbols

建议: 使用 WebSocket API 订阅实时数据,避免频繁轮询。

公开端点 - 系统信息

限制: 20 请求/分钟 (IP)

包含以下端点:

GET  /v1/system/time
GET  /v1/system/status
GET  /v1/health

私有端点 - 查询类

限制:

  • IP: 120 请求/分钟

  • API Key: 600 请求/分钟

包含以下端点:

GET  /v1/account/info
GET  /v1/account/balance
GET  /v1/account/positions
GET  /v1/order/list
GET  /v1/order/{order_id}
GET  /v1/position/list
GET  /v1/position/{symbol}
GET  /v1/trade/history

私有端点 - 交易类

限制:

  • IP: 30 请求/分钟

  • API Key: 120 请求/分钟

包含以下端点:

POST  /v1/order/place
POST  /v1/order/cancel
POST  /v1/order/amend
POST  /v1/position/close
POST  /v1/position/set-leverage
POST  /v1/position/set-margin-mode
POST  /v1/position/add-margin

私有端点 - 批量操作

限制:

  • IP: 10 请求/分钟

  • API Key: 30 请求/分钟

包含以下端点:

POST  /v1/order/cancel-all
POST  /v1/order/cancel-batch
POST  /v1/order/place-batch
POST  /v1/position/close-all

提示: 批量操作虽然限制更严格,但单次请求可处理多个对象,总体效率更高。

VIP 用户限制

VIP 用户享有更高的速率限制:

VIP 等级
查询类倍数
交易类倍数
批量操作倍数

VIP 1

2x

2x

2x

VIP 2

5x

3x

3x

VIP 3

10x

5x

5x

VIP 做市商

20x

10x

10x

示例: VIP 2 用户的查询类限制为 3000 请求/分钟 (600 × 5)。

如需升级 VIP 等级,请联系 [email protected]

速率限制响应头

每个 API 响应都包含速率限制信息的响应头:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 115
X-RateLimit-Reset: 1696752060
X-RateLimit-Type: api_key

响应头详解

X-RateLimit-Limit

当前时间窗口的请求上限。

示例:

X-RateLimit-Limit: 120

表示当前端点在 1 分钟内最多允许 120 次请求。

X-RateLimit-Remaining

当前时间窗口剩余可用请求次数。

示例:

X-RateLimit-Remaining: 95

表示当前时间窗口内还可以发送 95 次请求。

监控建议:

if (response.headers['x-ratelimit-remaining'] < 10) {
  console.warn('接近速率限制,降低请求频率');
  await sleep(1000);
}

X-RateLimit-Reset

速率限制重置时间 (Unix 时间戳,秒)。

示例:

X-RateLimit-Reset: 1696752060

表示速率限制将在 1696752060 (Unix 时间戳) 重置。

计算剩余时间:

const resetTime = parseInt(response.headers['x-ratelimit-reset']);
const now = Math.floor(Date.now() / 1000);
const remainingSeconds = resetTime - now;

console.log(`速率限制将在 ${remainingSeconds} 秒后重置`);

X-RateLimit-Type

当前限制类型,可能值:

  • ip - 基于 IP 地址的限制

  • api_key - 基于 API Key 的限制

示例:

X-RateLimit-Type: api_key

说明: 当同时存在 IP 和 API Key 限制时,返回更严格的那个限制类型。

超过限制的处理

当超过速率限制时,API 返回 HTTP 429 Too Many Requests

响应示例

{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded: 120 requests per minute. Please retry after 45 seconds.",
    "retry_after": 45,
    "limit": 120,
    "reset_at": 1696752060
  },
  "timestamp": 1696752000000
}

响应头

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1696752060
Retry-After: 45

Retry-After: 建议的重试等待时间 (秒)。

错误字段说明

  • code: 错误代码 RATE_LIMIT_EXCEEDED

  • message: 人类可读的错误描述

  • retry_after: 建议的重试等待时间 (秒)

  • limit: 当前限制值

  • reset_at: 限制重置时间 (Unix 时间戳)

重试策略

基础重试

async function callAPIWithRetry(endpoint, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(endpoint, options);

      if (response.status === 429) {
        const data = await response.json();
        const retryAfter = data.error.retry_after || 60;

        console.log(`速率限制,等待 ${retryAfter} 秒后重试...`);
        await sleep(retryAfter * 1000);
        continue;
      }

      return response;
    } catch (error) {
      if (i === maxRetries - 1) throw error;
    }
  }

  throw new Error('Max retries exceeded');
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

指数退避重试

async function callAPIWithExponentialBackoff(endpoint, options, maxRetries = 5) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(endpoint, options);

      if (response.status === 429) {
        // 指数退避: 2^i 秒,加上随机抖动
        const backoffTime = Math.pow(2, i) * 1000;
        const jitter = Math.random() * 1000;
        const waitTime = backoffTime + jitter;

        console.log(`重试 ${i + 1}/${maxRetries},等待 ${(waitTime / 1000).toFixed(2)} 秒`);
        await sleep(waitTime);
        continue;
      }

      return response;
    } catch (error) {
      if (i === maxRetries - 1) throw error;
    }
  }

  throw new Error('Max retries exceeded');
}

智能重试 (推荐)

class RateLimiter {
  constructor(requestsPerMinute) {
    this.limit = requestsPerMinute;
    this.tokens = requestsPerMinute;
    this.lastRefill = Date.now();
  }

  async acquire() {
    this.refill();

    if (this.tokens > 0) {
      this.tokens--;
      return;
    }

    // 等待令牌补充
    const waitTime = 60000 / this.limit;
    await sleep(waitTime);
    return this.acquire();
  }

  refill() {
    const now = Date.now();
    const elapsed = now - this.lastRefill;

    if (elapsed >= 60000) {
      this.tokens = this.limit;
      this.lastRefill = now;
    }
  }
}

// 使用示例
const limiter = new RateLimiter(100); // 100 请求/分钟

async function callAPI(endpoint, options) {
  await limiter.acquire();
  return fetch(endpoint, options);
}

优化建议

1. 使用 WebSocket 代替轮询

不推荐 (浪费速率限制):

// 每秒轮询订单簿
setInterval(async () => {
  const orderbook = await fetch('/v1/market/orderbook/SOL-PERP');
}, 1000);

推荐 (使用 WebSocket):

const ws = new WebSocket('wss://ws.zanbarax.com');

ws.send(JSON.stringify({
  type: 'subscribe',
  channel: 'orderbook',
  symbol: 'SOL-PERP'
}));

ws.onmessage = (event) => {
  const orderbook = JSON.parse(event.data);
  updateUI(orderbook);
};

2. 批量操作

不推荐 (多次请求):

for (const orderId of orderIds) {
  await fetch(`/v1/order/cancel`, {
    method: 'POST',
    body: JSON.stringify({ order_id: orderId })
  });
}

推荐 (批量请求):

await fetch('/v1/order/cancel-batch', {
  method: 'POST',
  body: JSON.stringify({ order_ids: orderIds })
});

3. 本地缓存

缓存不经常变化的数据:

class CachedAPIClient {
  constructor() {
    this.cache = new Map();
  }

  async getSymbolInfo(symbol) {
    // 缓存 1 小时
    const cacheKey = `symbol:${symbol}`;
    const cached = this.cache.get(cacheKey);

    if (cached && Date.now() - cached.timestamp < 3600000) {
      return cached.data;
    }

    const data = await fetch(`/v1/market/symbols/${symbol}`).then(r => r.json());

    this.cache.set(cacheKey, {
      data,
      timestamp: Date.now()
    });

    return data;
  }
}

4. 请求合并

合并短时间内的多个相同请求:

class RequestDeduplicator {
  constructor() {
    this.pending = new Map();
  }

  async fetch(url, options) {
    const key = `${url}:${JSON.stringify(options)}`;

    if (this.pending.has(key)) {
      return this.pending.get(key);
    }

    const promise = fetch(url, options)
      .finally(() => this.pending.delete(key));

    this.pending.set(key, promise);
    return promise;
  }
}

// 使用示例
const dedup = new RequestDeduplicator();

// 即使同时发起 10 个相同请求,实际只会发送 1 个
const promises = Array(10).fill().map(() =>
  dedup.fetch('/v1/account/balance', { headers: authHeaders })
);

const results = await Promise.all(promises);

5. 监控剩余配额

class RateLimitMonitor {
  constructor() {
    this.stats = {
      limit: 0,
      remaining: 0,
      resetAt: 0
    };
  }

  updateFromHeaders(headers) {
    this.stats.limit = parseInt(headers['x-ratelimit-limit'] || 0);
    this.stats.remaining = parseInt(headers['x-ratelimit-remaining'] || 0);
    this.stats.resetAt = parseInt(headers['x-ratelimit-reset'] || 0);

    const usage = ((this.stats.limit - this.stats.remaining) / this.stats.limit * 100).toFixed(2);
    console.log(`速率限制使用: ${usage}% (${this.stats.remaining}/${this.stats.limit} 剩余)`);

    if (this.stats.remaining < this.stats.limit * 0.1) {
      console.warn('⚠️  速率限制即将耗尽,请降低请求频率');
    }
  }

  async callAPI(url, options) {
    const response = await fetch(url, options);
    this.updateFromHeaders(response.headers);
    return response;
  }
}

6. 错峰请求

避免在整点发送大量请求:

function getRandomDelay(baseDelay = 0) {
  // 添加 0-5 秒的随机延迟
  return baseDelay + Math.random() * 5000;
}

async function scheduledTask() {
  await sleep(getRandomDelay(0));
  await performTask();
}

// 每分钟执行,但时间随机化
setInterval(scheduledTask, 60000);

企业级解决方案

分布式速率限制

使用 Redis 实现多实例速率限制:

const Redis = require('ioredis');
const redis = new Redis();

class DistributedRateLimiter {
  constructor(key, limit, windowMs) {
    this.key = key;
    this.limit = limit;
    this.windowMs = windowMs;
  }

  async acquire() {
    const now = Date.now();
    const windowStart = now - this.windowMs;

    // 使用 Redis sorted set 实现滑动窗口
    const multi = redis.multi();
    multi.zremrangebyscore(this.key, 0, windowStart);
    multi.zadd(this.key, now, `${now}-${Math.random()}`);
    multi.zcard(this.key);
    multi.expire(this.key, Math.ceil(this.windowMs / 1000));

    const results = await multi.exec();
    const count = results[2][1];

    if (count > this.limit) {
      throw new Error('Rate limit exceeded');
    }

    return true;
  }
}

// 使用示例
const limiter = new DistributedRateLimiter('api:user:123', 100, 60000);
await limiter.acquire();

多 API Key 负载均衡

class APIKeyPool {
  constructor(apiKeys) {
    this.keys = apiKeys.map(key => ({
      key,
      remaining: Infinity,
      resetAt: 0
    }));
    this.currentIndex = 0;
  }

  selectKey() {
    // 选择剩余配额最多的 Key
    const sorted = [...this.keys].sort((a, b) => b.remaining - a.remaining);
    const best = sorted[0];

    if (best.remaining === 0 && Date.now() < best.resetAt * 1000) {
      throw new Error('All API keys exhausted');
    }

    return best;
  }

  updateKey(apiKey, headers) {
    const key = this.keys.find(k => k.key === apiKey);
    if (key) {
      key.remaining = parseInt(headers['x-ratelimit-remaining'] || Infinity);
      key.resetAt = parseInt(headers['x-ratelimit-reset'] || 0);
    }
  }

  async callAPI(endpoint, options) {
    const { key } = this.selectKey();

    const response = await fetch(endpoint, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${key}`
      }
    });

    this.updateKey(key, response.headers);
    return response;
  }
}

// 使用示例
const pool = new APIKeyPool([API_KEY_1, API_KEY_2, API_KEY_3]);
const response = await pool.callAPI('/v1/account/balance', {});

特殊场景处理

做市商高频交易

做市商需要频繁下单/撤单,建议:

  1. 申请 VIP 做市商权限: 获得 10-20 倍速率限制

  2. 使用批量 API: 单次请求处理多个订单

  3. WebSocket 推送: 接收实时成交、订单簿更新

  4. 本地订单簿维护: 减少查询请求

class MarketMaker {
  constructor() {
    this.localOrderbook = new Map();
    this.pendingOrders = new Set();
  }

  async placeOrders(orders) {
    // 批量下单
    const response = await fetch('/v1/order/place-batch', {
      method: 'POST',
      body: JSON.stringify({ orders })
    });

    const data = await response.json();
    data.data.orders.forEach(order => {
      this.pendingOrders.add(order.id);
    });

    return data;
  }

  async cancelAllOrders() {
    // 批量撤单
    await fetch('/v1/order/cancel-all', {
      method: 'POST'
    });

    this.pendingOrders.clear();
  }

  onWebSocketUpdate(update) {
    // 更新本地订单簿,无需 HTTP 请求
    if (update.type === 'orderbook') {
      this.localOrderbook.set(update.symbol, update.data);
    }
  }
}

数据分析与回测

大量历史数据查询时:

  1. 使用数据导出 API: 一次性导出大量数据

  2. 分页查询: 避免单次请求数据量过大

  3. 本地缓存: 已查询的数据缓存到本地

async function fetchAllHistoricalTrades(symbol, startTime, endTime) {
  const trades = [];
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const response = await fetch(
      `/v1/market/trades/${symbol}?start=${startTime}&end=${endTime}&page=${page}&limit=1000`
    );

    const data = await response.json();
    trades.push(...data.data.items);

    hasMore = data.data.has_next;
    page++;

    // 尊重速率限制
    await sleep(1000);
  }

  return trades;
}

监控与告警

Prometheus 指标

Zanbara 提供 Prometheus 格式的速率限制指标:

# 请求总数
zanbara_api_requests_total{endpoint="/v1/order/place",status="200"} 1250

# 速率限制触发次数
zanbara_api_rate_limit_exceeded_total{endpoint="/v1/order/place"} 5

# 剩余配额
zanbara_api_rate_limit_remaining{endpoint="/v1/order/place",user="user_123"} 95

告警规则

groups:
  - name: api_rate_limit
    rules:
      - alert: RateLimitNearExhaustion
        expr: zanbara_api_rate_limit_remaining < 10
        for: 1m
        annotations:
          summary: "API 速率限制即将耗尽"
          description: "用户 {{ $labels.user }} 在端点 {{ $labels.endpoint }} 的剩余配额低于 10"

      - alert: RateLimitExceededFrequently
        expr: rate(zanbara_api_rate_limit_exceeded_total[5m]) > 0.1
        for: 5m
        annotations:
          summary: "频繁触发速率限制"
          description: "端点 {{ $labels.endpoint }} 在过去 5 分钟内频繁触发速率限制"

常见问题

Q: 为什么我的 VIP 等级没有生效?

A: VIP 等级仅对 API Key 限制生效,IP 限制保持不变。确认您使用的是通过 API Key 认证的请求。

Q: 测试环境有速率限制吗?

A: 测试环境的速率限制相对宽松:

  • 公开端点: 200 请求/分钟

  • 私有端点: 500 请求/分钟

但仍建议合理使用,避免影响其他开发者。

Q: 可以申请提高速率限制吗?

A: 可以。请通过 [email protected] 联系我们,说明您的使用场景和需求。我们将评估后调整您的限制。

Q: 速率限制是全局的还是按端点的?

A: 按端点分类计算。不同类型的端点有不同的限制,互不影响。

相关文档


文档版本: v1.0.0 最后更新: 2025-10-07 维护: Zanbara API Team

Last updated