Apache APISIX proxy-mirror 插件详解:流量镜像的配置、采样与超时控制实战
【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix
流量镜像是将线上真实请求复制一份转发到镜像服务,用于在不影响线上业务的前提下进行请求分析、回归验证、灰度观测等场景。本文以 Apache APISIX 的proxy-mirror插件为核心,完整讲解其参数模型、路由启用方式、镜像子请求的超时控制机制,并结合当前仓库的源码实现(插件主体、nginx 模板)与测试用例(proxy-mirror.t)说明其底层工作方式。读完本文,你将能够独立完成流量镜像的配置、采样率调优与超时防护,并理解镜像请求的实现原理。
功能概述
proxy-mirror插件为 APISIX 提供镜像客户端请求的能力:网关在正常转发请求到真实上游的同时,会将同一份请求(含请求头、URI、参数)复制一份发送到配置的镜像服务地址。
典型应用场景包括:
- 将线上真实流量拷贝到预发或测试环境,验证新版本服务的兼容性;
- 对线上请求内容做抽样分析、审计或数据采集,而不影响线上服务;
- 在压测、演练时复用真实流量特征。
需要特别注意的是,镜像请求返回的响应会被 APISIX 忽略,镜像结果不会回传给客户端,因此镜像服务的可用性不会影响主链路(但镜像子请求的延迟会影响主请求,详见下文「超时控制」章节)。
参数详解
插件支持四个配置项,其中仅host为必填,完整参数如下表所示:
| 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 |
|---|---|---|---|---|---|
| host | string | 是 | 指定镜像服务的地址,地址中需要包含schema(http(s)或grpc(s)),但不能包含path部分。例如http://127.0.0.1:9797。 | ||
| path | string | 否 | 指定镜像请求的路径。如果不指定,则默认会使用当前路径。如果是为了镜像 grpc 流量,这个选项不再适用。 | ||
| path_concat_mode | string | 否 | replace | ["replace", "prefix"] | 当指定镜像请求的路径时,设置请求路径的拼接模式。replace模式将会直接使用path作为镜像请求的路径。prefix模式将会使用path+来源请求 URI作为镜像请求的路径。如果是为了镜像 grpc 流量,这个选项也不再适用。 |
| sample_ratio | number | 否 | 1 | [0.00001, 1] | 镜像请求的采样率。当设置为1时为全采样。 |
从源码看参数约束
在 apisix/plugins/proxy-mirror.lua 中,插件的 JSON Schema 对参数做了严格约束,理解这些约束有助于避免配置报错:
host必须匹配正则^(http(s)?|grpc(s)?):\/\/([\da-zA-Z.-]+|\[[\da-fA-F:]+\])(:\d+)?$,即:- 必须携带
http、https、grpc或grpcs协议前缀; - 主机部分支持域名、IPv4 以及
[::1]形式的 IPv6 字面量; - 端口为可选项。
- 由 proxy-mirror.t 的 TEST 1/2/3 可见,使用
ftp://前缀、http://127.0.0.1::1999这类非法端口格式、或完全不带schema的127.0.0.1:1999都会被 schema 校验拒绝并返回 400; - 而不带端口号的
http://127.0.0.1是合法配置(TEST 4,返回 200passed); - 在
host中携带路径(如http://127.0.0.1:1999/invalid_uri)也会校验失败(TEST 5),这正是文档中「host 不能包含 path 部分」的源码依据。
- 必须携带
path必须匹配正则^/[^?&]+$,即必须以/开头且不能包含?与&字符——参数(query string)需要由 APISIX 自动附加,不能写在path中(TEST 21 验证了"a"与"/a?a=c"均为非法取值)。sample_ratio取值范围为[0.00001, 1],最小采样率不能低于十万分之一;超过 1 会被拒绝(TEST 13 中sample_ratio: 10报错expected 10 to be at most 1)。
此外,插件的priority为1010(见 config.yaml.example 中的插件列表注释),version为0.1,在插件执行链中处于较高优先级,保证镜像逻辑在路由匹配后尽早生效。
在路由上启用插件
以下示例演示如何在指定路由上启用proxy-mirror插件。示例中真实上游为127.0.0.1:1999,镜像服务地址为http://127.0.0.1:9797,当客户端请求/hello时,两份请求会分别发往这两个地址。
首先,可以从config.yaml中获取admin_key并存入环境变量,用于调用 Admin API:
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')然后通过 Admin API 创建(或更新)路由并挂载插件:
curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "plugins": { "proxy-mirror": { "host": "http://127.0.0.1:9797" } }, "upstream": { "nodes": { "127.0.0.1:1999": 1 }, "type": "roundrobin" }, "uri": "/hello" }'配置中的关键点:
host为镜像服务地址,必须携带协议前缀,且不能包含路径;- 插件挂载在路由的
plugins下,APISIX 会通过 schema 校验后写入配置中心(默认 etcd)并热加载,无需重启网关。
镜像子请求的超时控制
为什么需要超时控制
镜像请求在 APISIX 内部是以Nginx 子请求(subrequest)的方式实现的。由于子请求与主请求共享事件循环,子请求的延迟会阻塞原始请求——如果镜像服务响应缓慢,主请求必须等到子请求完成或超时后才能正常返回。因此,为镜像请求配置合理的超时时间,可以避免镜像服务故障拖垮线上主链路。
从 nginx 模板 apisix/cli/ngx_tpl.lua 可以看到,镜像子请求的 location 会依据plugin_attr中的配置生成对应的超时指令:
- HTTP 镜像使用
proxy_connect_timeout、proxy_read_timeout、proxy_send_timeout; - gRPC 镜像(location /proxy_mirror_grpc)则对应生成
grpc_connect_timeout、grpc_read_timeout、grpc_send_timeout。
在 plugin_attr 中配置超时
我们可以在conf/config.yaml文件的plugin_attr中指定镜像子请求的超时时间:
| 名称 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| connect | string | 60s | 镜像请求到上游的连接超时时间。 |
| read | string | 60s | APISIX 与镜像服务器维持连接的时间;如果在该时间内,APISIX 没有收到镜像服务器的响应,则关闭连接。 |
| send | string | 60s | APISIX 与镜像服务器维持连接的时间;如果在该时间内,APISIX 没有发送请求,则关闭连接。 |
配置示例(conf/config.yaml):
plugin_attr: proxy-mirror: timeout: connect: 2000ms read: 2000ms send: 2000ms在 conf/config.yaml.example 中同样保留了该配置的默认形态(三个超时均为60s),说明这是插件出厂默认的防护基线。修改该配置后需要重新生成 nginx 配置并 reload APISIX 才会生效;t/cli/test_proxy_mirror_timeout.sh 这个测试脚本验证了connect: 2000ms、read: 2s、send: 2000ms会被正确渲染为 nginx.conf 中的proxy_connect_timeout 2000ms;、proxy_read_timeout 2s;指令,可用作配置正确性的回归验证参考。
测试插件是否生效
因为上面示例指定的镜像地址是127.0.0.1:9797,验证插件是否正常工作需要在端口为9797的服务上确认。可以通过python快速启动一个简单的 HTTP 服务作为镜像接收端:
python -m http.server 9797按上述配置启用插件后,使用curl命令请求该路由,请求将被镜像到所配置的主机上:
curl http://127.0.0.1:9080/hello -i返回的 HTTP 响应头中如果带有200状态码,则表示插件生效:
HTTP/1.1 200 OK ... hello world从测试用例看验证要点
仓库中的 t/plugin/proxy-mirror.t 提供了完整的行为级验证,可作为理解插件行为与排查问题的依据:
- 请求头原样透传(TEST 9):镜像请求会保留原始请求的
host、api-key等请求头,并自动附加x-real-ip等网关头,说明镜像请求与原始请求的头部语义一致; - 镜像子请求使用 HTTP/1.1(TEST 11):镜像子请求发往上游时
upstream_http_version为1.1; - 采样率行为(TEST 14-17):
sample_ratio为 1 时全采样;为 0.5 时并发发送 200 个请求,镜像命中数落在[75, 125]区间(TEST 17),验证了随机采样的统计特性; - 自定义路径拼接(TEST 18-20、27-29):
replace模式下镜像 URI 直接变为自定义路径(/hello?a=1镜像为/a?a=1);prefix模式下镜像 URI 为path+ 原始 URI(/hello?a=1镜像为/a/hello?a=1); - gRPC 镜像(TEST 30-31):
host支持grpc://与grpcs://协议,验证了文档中「支持 grpc 流量镜像」的说明。
采样率与镜像路径的进阶用法
按比例采样镜像
sample_ratio默认值为1(全采样)。在流量较大的场景下,全量镜像会成倍放大对镜像服务的压力,此时可以按比例抽样:
curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "plugins": { "proxy-mirror": { "host": "http://127.0.0.1:9797", "sample_ratio": 0.5 } }, "upstream": { "nodes": { "127.0.0.1:1999": 1 }, "type": "roundrobin" }, "uri": "/hello" }'从源码看采样实现位于 rewrite 阶段:当sample_ratio为 1 时直接开启镜像;否则生成math.random()随机值,仅当随机值小于sample_ratio时才开启镜像。也就是说,采样判断在每个请求上独立进行,整体镜像比例在统计学上趋近于配置值。
自定义镜像路径
当镜像服务需要将请求收敛到固定端点,或需要对原始 URI 加统一前缀时,可以配合path与path_concat_mode使用:
replace模式(默认):镜像请求直接使用path作为完整路径,原始 URI 被替换;prefix模式:镜像请求路径为path+ 来源请求 URI,原始路径被保留在自定义前缀之后。
对应的镜像 URI 构造逻辑在 enable_mirror 函数 中实现:未配置path时使用upstream_uri(或uri+ 参数)作为镜像路径;配置后按path_concat_mode拼接,并自动附加原始查询参数。同时该函数会将解析后的镜像地址写入ctx.var.upstream_mirror_host与ctx.var.upstream_mirror_uri,供 nginx 模板中的proxy_pass $upstream_mirror_uri使用。
需要留意的是:grpc 流量镜像不适用path与path_concat_mode,gRPC 镜像仅通过host决定镜像目标(见 nginx 模板中grpc_pass $upstream_mirror_host的实现)。
域名型镜像地址的解析逻辑
host除了支持 IP 直连,也支持填写域名。在 resolver_host 函数 中,插件会先解析host中的域名:
- 若主机部分是 IPv4 / IPv6,则直接使用;
- 若为域名,则调用
core.resolver.parse_domain进行 DNS 解析,并将域名替换为解析出的 IP(保留原端口)后写入镜像变量; - 若 DNS 解析失败,则记录 error 日志并继续使用原始
host,由 Nginx 在转发时自行解析,不会导致主请求失败。
对应测试为 TEST 23-26:http://test.com:1980会被解析为具体 IP(日志中出现test.com is resolved to: http://1.2.3.4形式),而无法解析的域名not-find-domian.notfind则记录dns resolver resolves domain: ... error:日志,主请求仍正常返回。
删除插件
当需要移除该插件时,通过 Admin API 将路由配置中plugins置空即可。APISIX 会自动重新加载相关配置,无需重启服务:
curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "uri": "/hello", "plugins": {}, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1999": 1 } } }'删除后,nginx 模板中的mirror /proxy_mirror;指令也会随插件禁用而从生成的 nginx.conf 中移除(模板中该指令受enabled_plugins["proxy-mirror"]条件控制),镜像流量随即停止。
小结
proxy-mirror插件以「子请求」机制实现了对线上请求的低侵入复制:主请求照常转发,镜像请求携带原始请求头与 URI 发往独立目标,响应被丢弃。配置上只需一个必填的host,辅以path、path_concat_mode、sample_ratio即可覆盖固定路径替换、前缀拼接、按比例采样等常见镜像策略;通过plugin_attr中的connect/read/send超时配置可以防止慢镜像服务阻塞主链路。结合 插件源码、nginx 模板 与 行为测试,可以清晰地理解其参数校验、域名解析、采样判断与超时渲染的完整实现链路,为生产环境的流量镜像方案提供可靠的配置与排障依据。
【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考