TT Lab
开始
学习 学习路径 课程

构建 EAI 中间层

用 nginx 搭建网关 — 密钥、配额、版本

在 TT Lab 中继续学习

目标

用 nginx 构造 API 网关的原理——路径代理、API 密钥认证(401)、按消费者的配额(429)、版本路由与 Sunset、请求 ID 的传播与日志、上游超时(504)。

为什么重要

认证、调用量限制和版本,与业务无关,对所有 API 都同样需要。如果在每台 API 服务器上各自实现,就会渐渐变得各不相同,某个消费者的失控会让所有人变慢,也没有人知道谁在使用旧版本。网关在没有业务逻辑的情况下,在一个地方对这些进行管控。

步骤

  1. 启动两个上游:nohup python3 /opt/lab/fixtures/eaimw/gateway/api.py --port 9601 --version v1 > /root/eaimw/gw/v1.out 2>&1 &,… --port 9602 --version v2 …。用 curl 各调用一次,把两行响应 JSON 保存到 /root/eaimw/gw/up.txt。
  2. 编写 /root/eaimw/gw/nginx.conf(完整的配置文件:pid /root/eaimw/gw/nginx.pid;,日志位于 /root/eaimw/gw/logs/,listen 8090;,上游 127.0.0.1:9601·127.0.0.1:9602)。把 /v1/ 原样路径转给 v1,/v2/ 原样路径转给 v2。用 nginx -p /root/eaimw/gw -c /root/eaimw/gw/nginx.conf 启动。
  3. API 密钥认证:在 /root/eaimw/gw/keys.map 中放置 key-channel-7f3a channel;·key-partner-19c2 partner; 两行,并用 map $http_x_api_key $consumer 引入。如果没有消费者(没有密钥、密钥错误),就返回 401 和 JSON 正文。对上游传递 X-Consumer: <소비자>(占位符为消费者),不传递 X-API-Key。
  4. 配额:limit_req_zone $consumer … rate=5r/s、limit_req … burst=5 nodelay、limit_req_status 429。一个消费者集中发送时返回 429,而其他消费者必须不受影响。
  5. 版本:/api/ 在 X-API-Version: 2 时转给 v2,没有则转给 v1(map $http_x_api_version)。对发往 v1 的响应(/v1/ 和 /api/ 中的 v1)加上 Sunset: Thu, 31 Dec 2026 23:59:59 GMT 头,v2 则不加。
  6. 请求 ID:如果收到了 X-Request-ID,就原样使用,没有则把 $request_id 以 X-Request-ID 传给上游。把访问日志格式按 '<요청ID> <소비자> <상태> "<요청줄>"'(占位符依次为请求 ID、消费者、状态、请求行;例如 log_format gw '$rid $consumer $status "$request"';)记录到 /root/eaimw/gw/logs/access.log。
  7. 通过 proxy_read_timeout 2s,当上游很慢(/v1/slow 需要 5 秒)时,在 2 秒左右返回 504。

参考

启动上游的两个版本

用 api.py 启动 v1(9601)和 v2(9602),并把两行响应 JSON 保存到 /root/eaimw/gw/up.txt。

api.py 会把收到的请求以 JSON 映射出来。用 curl -s 调用两个端口,并用 >> 汇总到一个文件。

按路径分版本转发

通过 /root/eaimw/gw/nginx.conf,在 8090 上把 /v1/→9601、/v2/→9602 按原路径转发。

两个 upstream 块和两个 location。proxy_pass 不加 URI 部分,请求路径就会原样传过去。

用 API 密钥识别消费者

用引入了 /root/eaimw/gw/keys.map 的 map 来确定消费者,没有则返回 401 JSON,有则只传递 X-Consumer,并去掉密钥。

map $http_x_api_key $consumer { default ""; include …; } 之后,在 server 中 if ($consumer = "") { return 401 '…'; }。要去掉请求头,就给 proxy_set_header 一个空值。

为每个消费者设置配额

用 limit_req_zone $consumer rate=5r/s、burst=5 nodelay、limit_req_status 429,按消费者设置配额。

关键全在于 zone 的键是什么。超出限额的默认状态码是 503,必须改成 429,调用方才知道“是我发得太多了”。

用请求头选择版本,并告知即将下线的版本

/api/ 在 X-API-Version: 2 时转给 v2,没有则转给 v1。给 v1 响应加上 Sunset 头。

用 map $http_x_api_version $api_ver 选择上游名称,再 proxy_pass http://$api_ver; 即可。Sunset 也用 map,只在 v1 时给出值,值为空的 add_header 就不会附加。

串联请求 ID 并记入日志

如果有 X-Request-ID 就原样传递,没有则传递 $request_id,并以 '<请求 ID> <消费者> <状态> "<请求行>"' 的格式留下访问日志。

像 map $http_x_request_id $rid { "" $request_id; default $http_x_request_id; } 这样,map 的值中可以使用变量。定义 log_format,并把它的名称用在 access_log 上。

缓慢的上游要尽快断开

用 proxy_read_timeout 2s,在 2 秒左右以 504 断开 /v1/slow(5 秒)。

默认值是 60 秒。如果是调用方先放弃的 API,网关就没有理由抓着更久。请在每个 location 中统一加入。