共计 2665 个字符,预计需要花费 7 分钟才能阅读完成。
背景痛点
在直接调用 ChatGPT API 时,开发者通常会遇到以下三个主要问题:

- 响应延迟问题 :由于服务器物理距离和网络路由的影响,直接从国内调用海外 API 时延高达 300-500ms
- 并发限制问题 :官方 API 对免费账户有严格的 RPM(每分钟请求数)限制,付费账户也存在 TPM(每分钟 token 数)约束
- 成本控制问题 :当用户量激增时,API 调用费用会呈指数级增长,特别是处理长文本对话场景
技术选型
方案对比
- Nginx/OpenResty 方案
- 优点:成熟的 HTTP 服务器、灵活的路由配置、支持 Lua 扩展
-
缺点:需要自行维护缓存策略和集群管理
-
Cloudflare Workers 方案
- 优点:无需管理基础设施、全球边缘网络
-
缺点:无法深度定制缓存逻辑、存在冷启动延迟
-
自建 CDN 方案
- 优点:完全可控的缓存策略
- 缺点:需要专业运维团队和大量带宽成本
最终选择 Nginx+OpenResty 组合,因其在灵活性和可控性之间达到最佳平衡。
核心实现
静态内容缓存配置
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=chatgpt_cache:10m
inactive=60m use_temp_path=off;
server {
location /api {
proxy_pass https://api.openai.com;
proxy_cache chatgpt_cache;
proxy_cache_valid 200 5m; # 成功响应缓存 5 分钟
proxy_cache_key "$scheme$request_method$host$request_uri$args";
add_header X-Cache-Status $upstream_cache_status;
}
}
动态请求限流实现
采用 Redis+Lua 实现令牌桶算法:
local tokens_key = KEYS[1] -- 令牌桶 key
local timestamp_key = KEYS[2] -- 最后刷新时间 key
local rate = tonumber(ARGV[1]) -- 令牌生成速率 / 秒
local capacity = tonumber(ARGV[2]) -- 桶容量
local now = tonumber(ARGV[3]) -- 当前时间戳
local requested = tonumber(ARGV[4]) -- 请求令牌数
local last_refreshed = redis.call("get", timestamp_key)
if last_refreshed == false then
last_refreshed = 0
else
last_refreshed = tonumber(last_refreshed)
end
local delta = math.max(0, now - last_refreshed)
local new_tokens = math.min(capacity,
(redis.call("get", tokens_key) or 0) + delta * rate)
local enough_tokens = new_tokens >= requested
if enough_tokens then
new_tokens = new_tokens - requested
redis.call("setex", tokens_key, 60, new_tokens)
redis.call("setex", timestamp_key, 60, now)
end
return enough_tokens and 1 or 0
Docker 集群编排
version: '3.8'
services:
nginx:
image: openresty/openresty:alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./lua:/etc/nginx/lua
ports:
- "80:80"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/status"]
interval: 30s
timeout: 10s
retries: 3
redis:
image: redis:6-alpine
command: redis-server --save 60 1 --loglevel warning
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
性能测试
Locust 测试脚本
from locust import HttpUser, task, between
class ChatGPTUser(HttpUser):
wait_time = between(1, 3)
@task
def ask_question(self):
self.client.post("/v1/chat/completions",
json={"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": "Hello"}]})
性能对比数据
| 场景 | QPS | TP99 | 错误率 |
|---|---|---|---|
| 直接调用 API | 15 | 680ms | 12% |
| 缓存命中 | 1200 | 32ms | 0% |
| 缓存未命中 | 80 | 450ms | 5% |
安全合规
内容过滤正则
import re
sensitive_pattern = re.compile(r'\b( 暴力 | 色情 | 政治敏感词)\b',
flags=re.IGNORECASE)
def sanitize_content(text):
return sensitive_pattern.sub('[REDACTED]', text)
数据脱敏方案
-- 用户对话记录脱敏存储示例
INSERT INTO conversations
SET user_id = :user_id,
question = REGEXP_REPLACE(:question, '\b\d{4}\b', '****'),
answer = :answer,
created_at = NOW();
避坑指南
- 缓存雪崩预防
- 对缓存过期时间增加随机抖动(±10%)
-
实现多级缓存(内存→Redis→源站)
-
Keepalive 优化
upstream openai_backend { server api.openai.com:443; keepalive 32; # 连接池大小 keepalive_timeout 60s; }
开放问题
当用户请求包含敏感词时,应该:
– 直接返回 403 禁止访问?
– 替换为预设的合规内容?
– 记录日志并人工审核?
不同的业务场景可能需要不同的处理策略,值得深入探讨。
正文完
发表至: 未分类
近三天内
