Nginx流量拷贝ngx_http_mirror_module模块使用方法详解


Posted in Servers onApril 07, 2022

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

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编译安装,要加上ngx_http_mirror_module模块

下面是Nginx解压后,编译安装的示例

# ./configure
    --sbin-path=/usr/local/nginx/nginx
    --conf-path=/usr/local/nginx/nginx.conf
    --pid-path=/usr/local/nginx/nginx.pid
    --with-http_ssl_module
    --without-http_limit_req_module
    --without-http_mirror_module
    --with-pcre=../pcre-8.43
    --with-zlib=../zlib-1.2.11
    --add-module=/path/to/ngx_devel_kit
    --add-module=/path/to/lua-nginx-module

# make & make install

三、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/order、http://kevin.com/customer的流量分别复制到172.16.60.230服务器的8088和8089端口。

四、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(AbstractHandlerMethodAdapter.java:85)
Servers 相关文章推荐
Nginx 根据URL带的参数转发的实现
Apr 01 Servers
nginx配置虚拟主机的详细步骤
Jul 21 Servers
使用 Apache 反向代理的设置技巧
Jan 18 Servers
在容器中使用nginx搭建上传下载服务器
May 11 Servers
nginx实现多geoserver服务的负载均衡
May 15 Servers
apache ftpserver搭建ftp服务器
May 20 Servers
项目中Nginx多级代理是如何获取客户端的真实IP地址
May 30 Servers
教你如何用cmd快速登录服务器
Jun 10 Servers
Windows Server 2022 超融合部署(图文教程)
Jun 25 Servers
Linux下搭建SFTP服务器的命令详解
Jun 25 Servers
nginx代理实现静态资源访问的示例代码
Jul 07 Servers
Windows server 2003卸载和安装IIS的图文教程
Jul 15 Servers
忘记Grafana不要紧2种Grafana重置admin密码方法详细步骤
Apr 07 #Servers
Linux、ubuntu系统下查看显卡型号、显卡信息详解
Nginx速查手册及常见问题
从零开始在Centos7上部署SpringBoot项目
CentOS7和8下安装Maven3.8.4
CentOS下安装Jenkins的完整步骤
基于Apache Hudi在Google云构建数据湖平台的思路详解
You might like
百度地图API应用之获取用户的具体位置
2014/06/10 PHP
PHP中的socket_read和socket_recv区别详解
2015/02/09 PHP
php微信开发之上传临时素材
2016/06/24 PHP
PHP如何使用array_unshift()在数组开头插入元素
2020/09/01 PHP
Javascript 中的 call 和 apply使用介绍
2012/02/22 Javascript
JS实现将人民币金额转换为大写的示例代码
2014/02/13 Javascript
js判断为空Null与字符串为空简写方法
2014/02/24 Javascript
jqeury-easyui-layout问题解决方法
2014/03/24 Javascript
JavaScript中的函数(二)
2015/12/23 Javascript
基于JavaScript实现跳转提示页面
2016/09/24 Javascript
微信小程序 sha1 实现密码加密实例详解
2017/07/06 Javascript
angular $watch 一个变量的变化(实例讲解)
2017/08/02 Javascript
jquery+ajaxform+springboot控件实现数据更新功能
2018/01/22 jQuery
Vue2.5 结合 Element UI 之 Table 和 Pagination 组件实现分页功能
2018/01/26 Javascript
关于vue中watch检测到不到对象属性的变化的解决方法
2018/02/08 Javascript
浅谈vue限制文本框输入数字的正确姿势
2019/09/02 Javascript
详解vue中多个有顺序要求的异步操作处理
2019/10/29 Javascript
Javascript如何实现双指控制图片功能
2020/02/25 Javascript
ES5 模拟 ES6 的 Symbol 实现私有成员功能示例
2020/05/06 Javascript
vue 解决addRoutes多次添加路由重复的操作
2020/08/04 Javascript
Python调用C语言开发的共享库方法实例
2015/03/18 Python
OpenCV实现人脸识别
2017/04/07 Python
Python实现的求解最大公约数算法示例
2018/05/03 Python
浅谈python中真正关闭socket的方法
2018/12/18 Python
对python多线程中互斥锁Threading.Lock的简单应用详解
2019/01/11 Python
详解python多线程之间的同步(一)
2019/04/03 Python
Python使用matplotlib绘制Logistic曲线操作示例
2019/11/28 Python
OLEDBConnection和SQLConnection有什么区别
2013/05/31 面试题
高级护理实习生自荐信
2013/09/28 职场文书
教育实践活动对照检查材料
2014/09/23 职场文书
领导班子整改方案和个人整改措施
2014/10/25 职场文书
正规欠条模板
2015/07/03 职场文书
优秀毕业生主要事迹材料
2015/11/04 职场文书
62句有关感恩节文案(推荐收藏)
2019/11/28 职场文书
Python字符串的转义字符
2022/04/07 Python
详解Mysql事务并发(脏读、不可重复读、幻读)
2022/04/29 MySQL