一、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解压后,编译安装的示例

 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
35
36
37
38
39


# ./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流量拷贝的配置示例

  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186


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请求流量

 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54


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流量复制的,需要通过下面配置来禁止。

 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78


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镜像点

 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60


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。

 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99


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会报如下错误:

 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


"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)