惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
T
Threatpost
Latest news
Latest news
N
News | PayPal Newsroom
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Help Net Security
Help Net Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
AI
AI
Simon Willison's Weblog
Simon Willison's Weblog
TaoSecurity Blog
TaoSecurity Blog
The Last Watchdog
The Last Watchdog
L
LINUX DO - 热门话题
Google DeepMind News
Google DeepMind News
T
Threat Research - Cisco Blogs
O
OpenAI News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
The Exploit Database - CXSecurity.com
NISL@THU
NISL@THU
Application and Cybersecurity Blog
Application and Cybersecurity Blog
S
Securelist
小众软件
小众软件
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Martin Fowler
Martin Fowler
S
SegmentFault 最新的问题
Cisco Talos Blog
Cisco Talos Blog
云风的 BLOG
云风的 BLOG
AWS News Blog
AWS News Blog
GbyAI
GbyAI
N
News and Events Feed by Topic
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
美团技术团队
Engineering at Meta
Engineering at Meta
A
About on SuperTechFans
博客园 - 三生石上(FineUI控件)
S
Schneier on Security
博客园 - 聂微东
V2EX - 技术
V2EX - 技术
T
Troy Hunt's Blog
SecWiki News
SecWiki News
S
Secure Thoughts
B
Blog RSS Feed
Hugging Face - Blog
Hugging Face - Blog
WordPress大学
WordPress大学
腾讯CDC
H
Heimdal Security Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
www.infosecurity-magazine.com
www.infosecurity-magazine.com
P
Privacy International News Feed

博客园 - 哈喽哈喽111111

RockyLinux SSH 跳板转发 3389(Windows 远程桌面)完整方案 MySQL使用自带的logrotate配置日志轮转 yum方式安装redis7 nacos新加用户操作 网络运营商禁止端口信息 Linux开机启动rc.local不生效的一般解决方案 Adobe 修改 hosts 文件 axios 投毒与好莱坞式骗术 Linux系统在使用systemctl启动服务的失败,报错如下:Error No space left on device git submodule 的增、查、改、删 Rocky Linux 安装 Google Chrome 浏览器 “头号玩家”—— 美国技术霸权下的全球虚拟货币资产收割行动深层解析 你是第几级 AI 编程 MySQL中通过关联update将一张表的一个字段更新到另外一张表中 Linux 的 Port Knocking 端口碰撞(端口敲门) MySQL解除死锁 jar文件解压缩操作 设置Windows服务器远程桌面能使用多个桌面 20251024- 使用shell脚本分库定时备份MySQL数据 禁用sentinel 在 Linux 中安装和配置 NTP 服务器和 NTP 客户端 springboot配置文件关系及加载顺序 用自带的Nginx为gitlab做白名单 Rocky9和Ubuntu使用pip安装python的库mysqlclient失败解决方式 在Spring Boot Admin中根据Nacos的命名空间来区分和管理不同的环境
Nginx流量拷贝ngx_http_mirror_module模块使用方法详解
哈喽哈喽111111 · 2026-04-17 · via 博客园 - 哈喽哈喽111111

一、Nginx的ngx_http_mirror_module模块实现流量复制介绍

mirror 模块是 Nginx 的一个非常强大且实用的功能,它主要用于复制传入的请求,并将其发送到另一个地方(镜像位置),而不会影响原始请求的处理。


1. 什么是 Mirror 模块?

  • 核心功能:流量镜像(或称流量影子)。
  • 工作方式:它会把客户端发来的原始请求创建一个“镜像副本”,然后异步地将这个副本请求发送到你指定的“镜像位置”。
  • 关键特性
    • 异步处理:镜像请求的处理与原始请求的处理是完全分离的。原始请求会按照正常流程被处理并返回给客户端,不会等待镜像请求的完成。
    • 无视镜像响应:无论镜像请求返回什么结果(成功、失败、超时),都不会影响原始请求的响应。客户端对镜像请求的存在是完全无感知的。
    • 低延迟影响:由于是异步的,它对原始请求的延迟影响极小。

2. 主要应用场景

mirror 模块是以下场景的理想选择:

  1. 线上流量压测
    • 将生产环境的真实流量复制一份到新的测试服务器或新版本的应用程序上,来观察其在真实负载下的表现,而不会对线上用户产生任何影响。
  2. Bug 复现与诊断
    • 当生产环境出现难以复现的 Bug 时,可以将流量镜像到一个用于调试的隔离环境中,以便捕获和分析请求,而不会干扰正常用户。
  3. 版本验证与金丝雀分析
    • 在发布新版本时,可以将一小部分流量镜像到新版本后端,通过日志或监控系统来分析新版本的错误率、性能等指标,为正式发布提供数据支持。
  4. 日志与审计
    • 将所有请求额外镜像到一个专门的日志收集系统,用于进行更详细的安全审计、数据分析或长期存储。

Nginx专门提供了ngx_http_mirror_module模块,用来实现流量拷贝。将生产环境的流量拷贝到预上线环境或测试环境,这样做有很多好处:

  • 可以验证功能是否正常,以及服务的性能;
  • 用真实有效的流量请求去验证,又不用造数据,不影响线上正常访问;
  • 相比于灰度发布,镜像流量不会影响真实流量;
  • 可以用来排查线上问题;
  • 重构,假如服务做了重构,这也是一种测试方式;

ngx_http_mirror_module模块就像是一个镜像站点一样,将所有的请求都收集起来,这个镜像站点就代表了所有真实有效的原始请求。有了这个镜像站点,后续就可以复现所有的请求,实现把线上的流程复制到别的地方。

ngx_http_mirror_module模块特性:

  • nginx 1.13.4及后续版本内置ngx_http_mirror_module模块,提供流量镜像(复制)的功能。
  • 支持流量放大,做法为:配置多份相同镜像。
  • 相比tcp-copy的优势:无需录制流量,实时可用;配置相当简单。
  • 源站请求,直接原路返回;正常配置下,mirror请求不影响源站请求及响应,源站nginx-server将流量复制到mirror站后,两者不再有任何交集。

注意:这个模块使用nginx -V是无法查看到的,但是在nginx配置文件中可以直接配置有关的参数,且语法检测不会报错,nginx能重启成功

二、Nginx流量拷贝的配置示例

upstream kevin-order {
  server 127.0.0.1:8088;
}
upstream kevin-customer {
  server 127.0.0.1:8089;
}
upstream kevin-mirror1 {
    server 172.16.60.230:8088;
}
upstream kevin-mirror2 {
    server 172.16.60.230:8089;
}
server {
    listen 80;
    server_name  kevin.com;
    access_log  /usr/local/nginx/logs/kevin.com-access.log main;
    error_log   /usr/local/nginx/logs/kevin.com-error.log;
  # 源站点1
    location /order {
        proxy_pass http://kevin-order;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        # 复制请求体
        mirror_request_body on;
        # 流量复制
        mirror /mirror1;
    }
    # 源站点2
    location /customer {
        proxy_pass http://kevin-customer;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        mirror_request_body on;
        mirror /mirror2;
    }
    # 镜像站点1
    location /mirror1 {
        proxy_pass http://kevin-mirror1$request_uri;
        proxy_pass_request_body on;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    # 镜像站点2
    location /mirror2 {
        proxy_pass http://kevin-mirror2$request_uri;
        proxy_pass_request_body on;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

配置说明:上面配置中,将访问 http://kevin.com/orderhttp://kevin.com/customer的流量分别复制到172.16.60.230服务器的8088和8089端口。

镜像部分流量(采样)

使用 Nginx 的内置变量(如 $remote_addr)和 if 指令来实现流量采样,例如只镜像 50% 的请求。

注意:在 Nginx 中使用 if 需要小心,但它在这里是适用的。

location /api/ {
    proxy_pass http://primary_backend;

    # 使用$binary_remote_addr的最后一个比特来决定是否镜像(50%概率)
    if ( $binary_remote_addr ~* "1$" ) {
        mirror /mirror;
    }
    mirror_request_body on;
}

location = /mirror {
    internal;
    proxy_pass http://mirror_backend$request_uri;
    proxy_set_header X-Mirrored-For "sampled";
}

三、Nginx使用ngx_http_mirror_module模块进行流量拷贝的配置技巧

1)Nginx复制GET及POST请求流量

server {
    listen       80;
    server_name  kevin.com;
    # 源站配置
    location / {
        access_log  /usr/local/nginx/logs/access.log  accesslog;
        mirror /mirror;
        mirror_request_body on;
        proxy_pass http://kevin.upstream.name;
    }
    # 镜像站点配置
    location /mirror {
        internal; # 内部配置
        proxy_pass http://mirror.kevin.upstream.name$request_uri;
        proxy_pass_request_body on;
        proxy_set_header X-Original-URI $request_uri; #使用真实的url重置url
    }
}

2)Nginx不允许复制POST请求流量

默认是支持POST流量复制的,需要通过下面配置来禁止。

server {
    listen       80;
    server_name  kevin.com;
    # 源站配置
    location / {
        access_log  /usr/local/nginx/logs/access.log  accesslog;
        mirror /mirror;
        mirror_request_body off;
        proxy_pass http://kevin.upstream.name;
    }
    # 镜像站点配置
    location /mirror {
        # 判断请求方法,不是GET返回403
        if ($request_method != GET) {
            return 403;
        }
        internal;  #内部配置
        proxy_pass http://mirror.kevin.upstream.name$request_uri;
        proxy_pass_request_body off;
        # mirror_request_body和proxy_pass_request_body都设置为off,则Conten-length需要设置为"",否则有坑!
        proxy_set_header Content-Length "";
        proxy_set_header X-Original-URI $request_uri; # 使用真实的url重置url
    }
}

3)拷贝流量放大

配置多份mirror镜像点

server {
    listen       80;
    server_name  kevin.com;
    # 源站配置
    location / {
        access_log  /usr/local/nginx/logs/access.log  accesslog;
        mirror /mirror;
        # 多加一份mirror,流量放大一倍
        mirror /mirror;
        mirror_request_body on;
        proxy_pass http://kevin.upstream.name;
    }
    # 镜像站点配置
    location /mirror {
        internal; # 内部配置
        proxy_pass http://mirror.kevin.upstream.name$request_uri;
        proxy_pass_request_body on;
        proxy_set_header X-Original-URI $request_uri;  #使用真实的url重置url
    }
}

4)配置mirror镜像日志

mirror中不支持配置access_log,解决方法:mirror-location跳转到server,在server中配置accesslog。

server {
    listen       80;
    server_name  kevin.com;
    # 源站配置
    location / {
        access_log  /usr/local/nginx/logs/access.log  accesslog;
        mirror /mirror;
        mirror_request_body on;
        proxy_pass http://kevin.upstream.name;
    }
    # 镜像站点配置
    location /mirror {
        internal; # 内部配置
        # 跳转到下面的内部server
        proxy_pass http://127.0.0.1:10992$request_uri;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header X-Original-URI $request_uri; #使用真实的url重置url
    }
    
server {
    # server没法设置为内部
    listen 127.0.0.1:10992;
    location / {
        # 判断放在server,使得post请求日志可以记录
        if ($request_method != GET) {
            return 403;
        }
        access_log /usr/local/nginx/logs/access.log accesslog;
        proxy_pass http://mirror.kevin.upstream.name;
    }
}

四、Nginx流量拷贝的注意事项

1)mirror镜像配置日志

镜像配置不正确,导致流量复制操作没正常执行。

如果mirror镜像配置缺少日志,会严重影响调试。

所以强烈建议配置镜像日志,配置方法如如上"配置mirror镜像日志"。

部分错误配置的错误信息在在error日志中。

2)mirror_request_body/proxy_pass_request_body与Content-Length需配置一致

如果mirror_request_body或者proxy_pass_request_body设置为off,则Content-Length必须设置为"",因为nginx(mirror_request_body)tomcat(mirror_request_body)处理post请求时,会根据Content-Length获取请求体,如果Content-Length不为空,而由于mirror_request_body或者proxy_pass_request_body设置为off,处理方以为post有内容,当request_body中没有,处理方会一直等待至超时,则前者为off,nginx会报upstream请求超时;后者为off,tomcat会报如下错误

"2020-11-18T17:26:36.803+08:00" "331632b86ec64b829672066a96fc6324"      "department"        "group"   "project_name"        "hostname"    "127.0.0.1"     ""      "/post" "p=11"  "-"     "PostmanRuntime/7.1.1"  "ERROR" "xxx.GlobalControllerAdvice"       "operateExp"    "-"     "26"    "xxxx.GlobalControllerAdvice"       "unknown"       "org.springframework.http.converter.HttpMessageNotReadableException"    "I/O error while reading input message; nested exception is java.net.SocketTimeoutException"    "GlobalControllerAdvice中捕获全局异常"  "org.springframework.http.converter.HttpMessageNotReadableException: I/O error while reading input message; nested exception is java.net.SocketTimeoutException
        at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:229)
        at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:150)
        at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:128)
        at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
        at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:158)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(