想让线上流量在真实环境下回放,又不能影响用户?Nginx 的 mirror 模块干的就是这件事:每个传入的请求除了发往主后端,还会被复制一份发到一个或多个镜像后端,镜像请求的响应直接丢弃,客户端完全无感。本文整理 mirror 模块的用途、配置写法和常见的坑。

mirror 模块的用途

mirror 模块把客户请求镜像到一组后端服务器:请求不仅传递到主要后端,还会复制并发送到额外的后端。典型需求有三类:

  • 测试和调试:把生产流量镜像到测试环境,用真实流量做调试和性能测试。
  • 数据分析:镜像流量到专门的分析后端做实时收集和分析,不影响主服务性能。
  • 迁移和更新:迁移新系统或升级现有系统时,验证新系统能扛住相同的流量和负载。

使用场景

  • 安全测试:在不影响生产系统的情况下做安全测试和漏洞分析。
  • 流量监控:实时监控和分析生产流量。
  • 性能优化:在测试环境对不同配置做性能对比。

实验环境

  • nginx/1.31.5(nginx:alpine 容器)
  • 主后端 127.0.0.1:8081、镜像后端 127.0.0.1:8082(均为容器内 nginx 直接 return,access_log 分开记录)
  • 验证方法:客户端发一次请求,对比两个后端的日志——流量副本到没到、URI 变成什么样,日志里一目了然

核心机制一句话:mirror 指令让 Nginx 对每个命中的请求额外发起一个子请求到指定 location,子请求的响应直接丢弃,客户端只看到主后端的响应。

先说注意事项

  • 资源消耗:镜像流量会增加网络和后端服务器的负载,上线前评估性能影响。
  • 数据隐私:镜像流量里可能带敏感数据,确保不违反隐私政策和数据保护法规。
  • 结果可靠性:镜像请求的响应不会返回给客户端,不影响用户体验;但镜像侧如果出错,别把它误读成主链路的问题。

基本示例

默认构建的 Nginx 已启用 mirror 模块;如果是自定义编译,先确认模块已带上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
http {

# 定义镜像后端服务器
upstream mirror_backend {
server mirror.example.com;
}

server {
listen 80;
server_name example.com;

location / {
# 配置主要后端
proxy_pass http://main_backend;

# 根据条件进行请求镜像
if ($http_mirror_enabled = "true") { # 此处进行HTTP头匹配
mirror /mirror;
}
}

# 镜像位置
location /mirror {
internal; # 该指令指定此location只能被内部调用

# 将镜像请求发送到镜像后端服务器
proxy_pass http://mirror_backend;
}
}
}

注:原文此处变量写作 $http_mirror-enabled,Nginx 变量名不含连字符,已按保守原则修正为 $http_mirror_enabled(对应 Mirror-Enabled 头,连字符映射为下划线)。

逐块解释:

  • upstream mirror_backend:定义接收镜像请求的后端服务器。
  • location /:主请求处理位置,按 Mirror-Enabled 头的值决定是否镜像。
  • mirror:指定镜像请求的处理路径,头值为 true 时请求被镜像到 /mirror。
  • location /mirror:声明为 internal,只能被 Nginx 内部调用;镜像请求在这里转发给 mirror_backend。

这里做了一次 HTTP 头匹配,保证只有满足条件的请求才会被镜像,而不是无脑全量复制。

配置行为实测验证——客户端对主入口发一次请求,两个后端的日志各自记录了什么:

mirror 基本行为实测:一次请求,两份日志

两个细节值得注意:客户端拿到的响应来自主后端(MAIN-BACKEND),镜像侧的响应被丢弃;镜像子请求的 URI 是 mirror 指令指向的路径(/mirror)而不是原始 URI(/api/test),但 query 参数原样保留(?q=1)——镜像后端想知道原始路径,要从请求头或日志里取,不能靠 $uri。

internal 指令的作用

internal 在 mirror 配置里是关键一环:指定某个 location 为 internal 后,它只能被 Nginx 内部调用,外部客户端无法直接访问。这保证了镜像请求只由 Nginx 内部产生,既提升安全性,也避免 /mirror 路径被恶意直接访问。实测验证:

internal 生效实测:外部直访 /mirror 被拒

扩展:按路径和参数镜像

除了按头匹配,还可以限定路径并检查 URL 参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
http {

upstream main_backend {
server main.example.com;
}

upstream mirror_backend {
server mirror.example.com;
}

server {
listen 80;
server_name example.com;

location /api/v1/ { # 限定特定路径的匹配
proxy_pass http://main_backend;

# 仅当请求路径中带有 `mirror=true` 参数时,执行镜像
if ($arg_mirror = "true") {
mirror /mirror-api;
}
}

# 另外的路径不进行镜像,只进行普通代理
location / {
proxy_pass http://main_backend;
}

location /mirror-api {
internal;
proxy_pass http://mirror_backend;
}
}
}

只有访问 /api/v1/ 且带 mirror=true 参数的请求才触发镜像,其余路径照常代理。条件粒度可以按业务需要继续细化。

镜像日志

镜像流量要有日志可查,否则镜像侧出问题无从分析。给镜像请求单独配一份日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
http {
log_format mirror '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/mirror_access.log mirror;

upstream main_backend {
server main.example.com;
}

upstream mirror_backend {
server mirror.example.com;
}

server {
listen 80;
server_name example.com;

location / {
proxy_pass http://main_backend;

if ($http_x_mirror_enabled = "true") {
mirror /mirror;
}
}

location /mirror {
internal;
proxy_pass http://mirror_backend;
}
}
}

镜像请求的日志会记录在 /var/log/nginx/mirror_access.log。注意这里匹配的是 X-Mirror-Enabled 头,对应变量 $http_x_mirror_enabled。

高级镜像条件

按请求方法镜像

只镜像 POST/PUT 这类写请求,忽略 GET:

1
2
3
4
5
6
7
8
9
10
11
12
location / {
proxy_pass http://main_backend;

if ($request_method = POST) {
mirror /mirror-post;
}
}

location /mirror-post {
internal;
proxy_pass http://mirror_backend;
}

(http 块的 upstream 定义与前例相同,此处省略。)

按用户代理镜像

配合 map 模块识别移动端,只镜像来自移动设备的请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
http {

upstream main_backend {
server main.example.com;
}

upstream mirror_backend {
server mirror.example.com;
}

map $http_user_agent $is_mobile {
default 0;
"~*Mobile" 1;
}

server {
listen 80;
server_name example.com;

location / {
proxy_pass http://main_backend;

if ($is_mobile) {
mirror /mirror-mobile;
}
}

location /mirror-mobile {
internal;
proxy_pass http://mirror_backend;
}
}
}

按请求路径镜像

用正则匹配特定路径的流量:

1
2
3
4
5
6
7
8
9
10
11
12
location / {
proxy_pass http://main_backend;

if ($request_uri ~* "^/special-path/") {
mirror /mirror-special;
}
}

location /mirror-special {
internal;
proxy_pass http://mirror_backend;
}

监控和报警

把镜像流量接入监控体系:

  1. 日志分析:用 Logstash 解析 Nginx 访问日志(含镜像日志),存入 Elasticsearch 检索分析。
  2. 监控报警:Prometheus 采集 Nginx metrics,Grafana 出面板和报警规则,镜像请求量超出预期即触发报警。

性能优化和负载管理

镜像会放大后端负载,以下手段可以控住成本。

对镜像路径启用缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
http {
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m use_temp_path=off;

upstream main_backend {
server main.example.com;
}

upstream mirror_backend {
server mirror.example.com;
}

server {
listen 80;
server_name example.com;

location / {
proxy_pass http://main_backend;

if ($http_mirror_enabled = "true") {
mirror /mirror;
}
}

location /mirror {
internal;
proxy_cache my_cache;
proxy_pass http://mirror_backend;
}
}
}

动态调整镜像条件

利用变量和条件语句,可以按实时需求动态控制镜像——例如基于系统负载动态开关镜像。

用 limit_req 限制镜像流量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
http {
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

upstream main_backend {
server main.example.com;
}

upstream mirror_backend {
server mirror.example.com;
}

server {
listen 80;
server_name example.com;

location / {
proxy_pass http://main_backend;

if ($http_mirror_enabled = "true") {
mirror /mirror;
}
}

location /mirror {
internal;
limit_req zone=one;
proxy_pass http://mirror_backend;
}
}
}

常见问题与解决

镜像服务器被高并发打挂:用 limit_req 控制每秒镜像请求数;upstream 里配置多台镜像服务器分担负载。

镜像结果与原请求不一致:确保镜像请求和原请求的环境配置一致;用日志做比对分析,定位不一致的原因。

镜像流量拖慢生产环境:只在必要时镜像,用条件语句做精细化控制;用缓存减少镜像请求的负载。

最佳实践

  1. 提前规划和评估:实施镜像前评估镜像流量对系统整体性能的影响。
  2. 精细化控制:按请求方法、路径、用户代理等条件控制,避免无意义的镜像流量。
  3. 安全性:镜像请求可能包含敏感数据,妥善处理并遵循数据隐私及合规要求。
  4. 监控和日志:实时监控镜像流量、分析日志,提早发现潜在问题。
  5. 逐步实施:重大改动时逐步增加镜像流量,验证有效性和稳定性,不要一次性大规模变更。

注意事项

  • 镜像路径必须配 internal,否则外部可以直接访问到镜像入口。
  • 镜像请求的响应不会回给客户端,别用镜像侧的响应做任何影响主链路的判断。
  • 变量名书写要小心:Nginx 变量没有连字符,Mirror-Enabled 头对应 $http_mirror_enabled。
  • 镜像是放大器,条件、缓存、限流三件套至少配一样,否则流量高峰时镜像后端先扛不住。

本文由作者 2020-2024 年间的 CSDN 博客文章重构而来,原发布于 CSDN。