OpenAPI计费网关项目简介(技术篇)

162 阅读7分钟

引言

本文从技术和产品角度深入介绍我们开发的 OpenAPI 系统,这是一个基于Koa.js构建的高性能API计费解决方案,它不仅支持多种计费模式,还提供了强大的缓存机制、安全防护和灵活的请求处理能力。

系统架构

OpenAPI 系统采用了现代化的技术栈和架构设计:

  • 后端框架:基于Koa.js构建,利用其异步特性实现高并发处理
  • 数据存储:MongoDB用于持久化存储,Redis用于缓存和速率限制
  • 前端界面:Vue.js构建的管理界面,包含Monaco编辑器支持 请求模板 编辑

系统的核心组件包括:

  • API代理中间件
  • 计费服务
  • 速率限制器
  • 缓存系统
  • 安全认证模块

产品核心优势

1. 高性能设计

OpenAPI 系统在性能优化方面做了大量工作:

1.1 高效的HTTP连接管理

// 创建 keepAlive Agent
const httpsAgent = new https.Agent({
  keepAlive: true,
  keepAliveMsecs: 1000, // 连接保持时间
  maxSockets: 100,      // 并发连接数
  maxFreeSockets: 10,   // 允许的空闲连接数
  timeout: TIMEOUT,     // 超时时间
  rejectUnauthorized: false // 忽略 SSL 证书错误
});

通过配置HTTP和HTTPS的Agent,系统实现了连接复用,大幅减少了TCP连接建立的开销。这在高并发场景下能显著提升性能,减少服务器资源消耗。

1.2 智能的重试机制

// 带重试的请求函数
async function retryableRequest(config, retries = RETRY_COUNT) {
  try {
    return await axiosInstance(config);
  } catch (error) {
    if (retries > 0 && (
      error.code === 'ETIMEDOUT' ||
      error.code === 'ECONNRESET' ||
      error.code === 'ECONNREFUSED' ||
      (error.response && error.response.status >= 500)
    )) {
      console.warn(`Retry attempt ${RETRY_COUNT - retries + 1} for ${config.url}`);
      await delay(RETRY_DELAY * (RETRY_COUNT - retries));
      return retryableRequest(config, retries - 1);
    }
    throw error;
  }
}

系统实现了智能重试机制,可以自动处理临时性网络故障和上游服务暂时不可用的情况。重试策略采用了指数退避算法,避免了重试风暴,提高了系统的可靠性。

1.3 异步处理计费记录

系统将API调用和计费处理分离,API调用完成后异步处理计费记录,避免了计费处理对API响应时间的影响。

2. 高效缓存实现

缓存系统是 OpenAPI 的一大亮点,它极大地提升了系统性能和用户体验:

2.1 内存缓存实现

class MemoryCache {
  constructor() {
    this.cache = new Map();
    this.timeouts = new Map();
  }
  set(key, value, ttlSeconds) {
    this.cache.set(key, value);
    if (ttlSeconds) {
      // 清除旧的超时
      if (this.timeouts.has(key)) {
        clearTimeout(this.timeouts.get(key));
      }
      // 设置新的超时
      const timeout = setTimeout(() => {
        this.cache.delete(key);
        this.timeouts.delete(key);
      }, ttlSeconds * 1000);
      this.timeouts.set(key, timeout);
    }
  }
  // ...其他方法
}

系统实现了高效的内存缓存,使用Map数据结构存储缓存数据,并通过定时器实现缓存过期机制。这种设计比传统的数组遍历检查过期项更高效。

2.2 智能缓存策略

// 判断请求是否可缓存
function isCacheable(method, headers) {
  // 只缓存GET请求,并且请求头没有指定no-cache
  return method === 'GET' &&
          (!headers['cache-control'] || !headers['cache-control'].includes('no-cache'));
}
// 获取缓存TTL(秒)
function getCacheTTL(resource) {
  return resource.cacheTTL || 300; // 通过cacheTTL来自定义缓存时间, 默认5分钟
}

系统根据HTTP方法和请求头智能判断请求是否可缓存,并支持通过API资源配置自定义缓存时间。这种灵活的缓存策略能够适应不同API的缓存需求。

2.3 缓存键生成

// 生成缓存键
function generateCacheKey(ctx, url) {
  const data = `${ctx.method}:${url}:${JSON.stringify(ctx.request.body)}:${JSON.stringify(ctx.request.querystring)}`;
  return crypto.createHash('md5').update(data).digest('hex');
}

缓存键的生成考虑了HTTP方法、URL、请求体和查询参数,确保了缓存的精确匹配。使用MD5哈希算法生成固定长度的缓存键,提高了缓存系统的效率。

3. 安全性设计

OpenAPI 系统在安全性方面做了全面考量:

3.1 API密钥认证

系统实现了基于API密钥的认证机制,每个用户都有唯一的API密钥,用于身份验证和访问控制。

3.2 速率限制

exports.rateLimiter = async (ctx, next) => {
  const endpoint = ctx.path.replace('/api/', '');
  const apiKey = ctx.state.apiKey;
  if (!apiKey) {
    ctx.status = 401;
    ctx.body = { error: 'API key required' };
    return;
  }
  const maxRequests = objLimit[endpoint] || DEFAULT_MAX_REQUESTS;
  const key = `ratelimit:${endpoint}/${apiKey}`;
  try {
    const isAllowed = await cache.rateLimit(key, maxRequests, RATE_LIMIT_WINDOW);
    const currentCount = cache.get(key) || 0;
    ctx.set('X-RateLimit-Limit', maxRequests);
    ctx.set('X-RateLimit-Remaining', Math.max(0, maxRequests - currentCount));
    if (!isAllowed) {
      ctx.status = 429;
      ctx.body = { error: `访问频率超限(${maxRequests}req/m)...` };
      return;
    }
    return next();
  } catch (error) {
    logger.error('Rate limiter error:', error);
    return next(); // Proceed even if rate limiting fails
  }
};

系统实现了基于内存缓存的速率限制器,可以针对不同的API端点设置不同的速率限制,有效防止了API滥用和DoS攻击。速率限制器还会自动从数据库加载配置并定期更新,确保限制策略的及时生效。

3.3 输入验证和错误处理

系统对所有输入进行严格验证,防止注入攻击和其他安全威胁。同时,系统实现了全面的错误处理机制,确保在出现异常情况时能够优雅地处理并返回适当的错误信息。

4. 灵活的请求处理

4.1 函数式请求模板

系统最近升级了请求模板功能,支持使用JavaScript函数定义请求转换逻辑:

// 检查是否是函数字符串(以 function 开头)
if (typeof resource.requestTemplate === 'string' && resource.requestTemplate.trim().startsWith('function')) {
  try {
    // 将函数字符串转换为可执行函数
    // 支持匿名函数和命名函数
    const templateCode = resource.requestTemplate.trim();
    let templateFunction = new Function('request', `
      try {
        return (${templateCode})(request);
      } catch (err) {
        console.error('Template function execution error:', err);
        return {};
      }
      `);
    // 执行函数并获取结果
    const result = templateFunction(ctx.request);
    if (result && typeof result === 'object') {
      if (result.headers) {
        headers = result.headers;
      }
      if (result.body) {
        body = result.body
        // 如果是FormData请求但模板返回了body,我们将转换为JSON请求
        if (isMultipart) {
          headers['content-type'] = 'application/json';
        }
      }
    }
  } catch (error) {
    logger.error(`Error executing request template function: ${error.message}`);
  }
}

这种函数式模板比传统的JSON模板更加灵活,可以实现复杂的请求转换逻辑,如条件判断、数据转换等。前端还配置了Monaco编辑器,提供JavaScript语法高亮,提升了模板编辑体验。

4.2 流式响应支持

系统支持流式响应,特别适用于处理大模型响应或需要实时数据的场景:

async function handleStreamProxy(ctx, apiResource, upstream, opt) {
  const stream = new PassThrough();
  ctx.body = stream;
  // 设置 SSE headers
  ctx.set({
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });
  try {
    // 处理特殊请求头
    const headers = {
      ...opt.headers,
      'api-key': opt.headers['api-key'] || '',
      'Content-Type': 'application/json',
      'Accept': 'text/event-stream'
    };
    // 移除不需要的头
    delete headers.host;
    delete headers['x-api-key'];
    delete headers['content-length'];
    const optA = {
      method: ctx.method,
      url: upstream,
      data: {
        ...opt.body,
        stream: true
      },
      headers,
      responseType: 'stream'
    };
    // 转发请求到上游服务
    const response = await axiosInstance(optA);
    // 直接转发响应
    response.data.pipe(stream);
  } catch (error) {
    console.error('Stream proxy error:', error);
    stream.write(`data: ${JSON.stringify({ error: error.message })}\n\n`);
    stream.end();
  }
}

流式响应处理使系统能够支持现代AI API的流式输出,如OpenAI的流式聊天完成API。

5. 精确的Token计费

系统最近升级了token计费功能,支持更精确的计费方式:

  • 输入输出分离计费:系统可以分别计算输入和输出token的费用,并设置不同的价格
  • 智能识别输入token:对于常见的LLM API格式(如包含prompt或messages字段的请求),系统能够智能识别并计算输入token
  • 灵活的计费模式:系统支持根据API资源的billingMode属性决定使用哪种计费模式

这种精确的token计费方式特别适合AI API提供商,可以更公平地反映API使用成本。

产品设计考量

1. 用户体验至上

OpenAPI 系统的设计始终以用户体验为中心:

  • 简洁的API设计:遵循RESTful原则,API接口简洁明了,支持浏览器调用调试
  • 详细的使用统计:提供实时使用量统计,帮助用户了解API使用情况
  • 灵活的计费策略:支持多种计费模式,满足不同用户的需求

2. 可扩展性设计

系统在设计时充分考虑了可扩展性:

  • 模块化架构:系统分为多个独立模块,如认证、代理、计费等,便于扩展和维护
  • 插件化设计:关键功能如缓存、速率限制等都设计为可插拔的组件
  • 配置驱动:大部分功能通过配置驱动,无需修改代码即可调整系统行为

3. 运维友好

系统设计了完善的日志和监控机制,便于运维人员排查问题和优化系统性能。同时,系统支持通过环境变量配置关键参数,便于在不同环境中部署。

技术实现对比

缓存实现对比

实现方式优点缺点
内存缓存 (当前实现)速度快,实现简单不支持分布式,重启后缓存丢失
Redis缓存支持分布式,持久化需要额外维护Redis服务
文件缓存简单,持久化I/O开销大,性能较低

我们选择内存缓存作为主要实现,同时为分布式部署场景预留了Redis缓存的接口。

速率限制实现对比

实现方式优点缺点
内存计数器 (当前实现)速度快,实现简单不支持分布式
滑动窗口更精确的限流实现复杂,资源消耗较大
令牌桶允许突发流量实现复杂

我们选择内存计数器作为速率限制的实现,在单机部署场景下提供了良好的性能和足够的准确性。

未来发展方向

  • 支持多种货币:计划添加多币种支持,满足国际用户需求
  • 计费预警功能:当用户接近月度限额时发送通知
  • 自定义计费规则:允许用户定义更复杂的计费规则
  • 计费报表导出:支持导出详细的计费报表
  • 集成支付网关:直接在系统中完成充值和支付

结论

OpenAPI 系统通过高性能设计、高效缓存实现、全面的安全考量和灵活的请求处理能力,独立开发者提供了一个强大的计费解决方案。系统不仅满足了基本的计费需求,还通过精确的token计费、函数式请求模板等创新功能,提升了用户体验和系统灵活性。

作为独立开发者,更应追求技术卓越和产品完美, OpenAPI 系统正是这种追求的体现。我们相信,随着系统的不断发展和完善,它将为更多的API提供者创造价值。

本文由 OpenAPI 系统开发团队撰写,旨在分享我们在API计费领域的技术实践和产品思考。

正如一位用户所说:

" OpenAPI 让我第一次感受到,作为一个独立开发者,我也可以像大公司一样专业地提供API服务,并从中获得应有的回报。"

这或许正是 OpenAPI 最大的价值 —— 它让技术创造的价值得到了公正的评估和回报,让靠"爱发电"的开发者们看到了一条可持续发展的道路。

基础框架已开源,欢迎star和fork~ 评论区留言获取项目地址。