文章目录
介绍
Nginx 从 1.13.4 版本开始引入了 ngx_mirror_module 模块:
The ngx_http_mirror_module module (1.13.4) implements mirroring of an original request by creating background mirror subrequests. Responses to mirror subrequests are ignored.
利用 mirror 模块,业务可以将线上实时访问流量拷贝至其他环境,基于这些流量可以做版本发布前的预先验证,进行流量放大后的压测。
官方文档:https://nginx.org/en/docs/http/ngx_http_mirror_module.html
Nginx 如何实现流量镜像
ngx_mirror_module模块会对原请求进行复制,产生一个镜像的子请求,镜像出来的子请求的响应会被忽略。有了镜像请求后,我们可以将请求引流到其他的服务当中去,比如线上请求引到测试环境,这样,当线上环境出现了问题时,我们就能对其进行排查。
- Nginx 的镜像功能使用的是 mirror 指令,通过 mirror 指令实现请求的监听和拷贝。
Nginx 的镜像功能并不只是简单的复制,复制的镜像请求和原始请求是相关联的,若镜像请求没有处理完成,原始请求就会被阻塞。也就是说,如果镜像的子请求出现问题,也会影响原始的请求,所以,开启流量镜像模式做为测试流量时,要监控 Nginx 性能指标,发现请求反馈异常要即时处理。
安装Nginx
[root@abcdocker tmp]# bash https://d.frps.cn/file/tools/nginx/nginx_install.sh
[root@abcdocker tmp]# bash nginx_install.sh
当然也可以自己yum或者采用其它方式安装Nginx,我这里为编译安装
查看Nginx版本信息
[root@abcdocker tmp]# /opt/nginx-1.22.1/sbin/nginx -V
nginx version: nginx/1.22.1
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC)
built with OpenSSL 1.1.1t 7 Feb 2023
TLS SNI support enabled
configure arguments: --prefix=/opt/nginx-1.22.1 --with-openssl=/usr/local/src/openssl-1.1.1t --with-pcre=/usr/local/src/pcre-8.45 --with-zlib=/usr/local/src/zlib-1.2.13 --with-http_ssl_module --with-http_stub_status_module --with-stream --with-http_stub_status_module --with-http_gzip_static_module
安装mirror模块
mirror
模块在 Nginx 1.13.4 以后的版本中默认是启用,不需要额外模块安装
为了模拟后端我这里docker run 2个mirror-backed镜像
# 8081为正常后端节点
docker run -d --name web-01 --restart always -p 8081:80 nginx
#8082为模拟流量复制节点
docker run -d --name web-02 --restart always -p 8082:80 nginx
进入nginx容器配置静态页面
web-01
配置
docker exec -it web-01 bash
echo "abcdocker web" >/usr/share/nginx/html/index.html
[root@abcdocker ~]# curl localhost:8081
abcdocker web
web-02
配置
docker exec -it web-02 bash
echo "nginx mirror" >/usr/share/nginx/html/index.html
[root@abcdocker ~]# curl localhost:8082
nginx mirror
配置mirror模块
向后端添加web-01服务,并将发往源后端的流量复制一份到web-02后端。
Nginx复制所有请求流量
不做任何过滤以及相关限制,只是将web-01节点流量复制一份到web-02节点中
nginx.conf配置文件如下
upstream backend {
server 127.0.0.1:8081;
}
upstream mirr_backend {
server 127.0.0.1:8082;
}
server {
server_name localhost;
listen 80;
location / {
mirror /nginx-mirror;
mirror_request_body on;
proxy_pass http://backend;
}
location = /nginx-mirror {
internal;
proxy_pass http://mirr_backend$request_uri;
proxy_pass_header Set-Cookie;
proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Original-URI $request_uri;
}
}
参数解释
- mirror /nginx-mirror; #指定镜像uri,也可以修改为其它地址,需要和localtion = /nginx-mirror 匹配
- mirror_request_body on; #指定是否镜像请求body部分,请求自动缓存;
- internal #指定此location只能被“内部的”请求调用,外部的调用请求会返回”Not found” (404)
访问测试
[root@abcdocker ~]# curl localhost
abcdocker web
[root@abcdocker ~]# curl localhost
abcdocker web
[root@abcdocker ~]# curl localhost
abcdocker web
查看web-01日志
查看web-02日志
Nginx后端节点响应慢
当mirror节点down掉后,Nginx会丢弃mirror响应,但是如果镜像请求响应很缓慢,原始请求就会被阻塞,这时会影响主请求的响应速度的。我们要考虑使用Nginx复制流量分流
来限制发送的流量
Nginx复制流量分流
生产环境中流量会很大,某些场景下我们只想获取部分流量到mirror节点中,但是mirror 模块无法进行流量分流,所以我们需要使用 Split Client
模块来实现
Split Client 模块是 Nginx 的一个插件,它通过识别客户端的特定属性,例如 IP 地址、用户代理或 Cookie 等,将请求分发到不同的后端服务器。这种分流的方式可以根据客户端的特征将请求导向不同的服务集群,从而实现更精细化的流量控制和管理。
upstream backend {
server 127.0.0.1:8081;
}
upstream mirr_backend {
server 127.0.0.1:8082;
}
split_clients $remote_addr $mirror_backend {
50% mirr_backend;
* "";
}
server {
server_name localhost;
listen 80;
location / {
mirror /nginx-mirror;
mirror_request_body on;
proxy_pass http://backend;
}
location = /nginx-mirror {
internal;
if ($mirror_backend = "") {
return 400;
}
proxy_pass http://mirr_backend$request_uri;
proxy_pass_header Set-Cookie;
proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Original-URI $request_uri;
}
}
实现只复制部分流量到镜像后端
- split_clients 会将左边的变量 $remote_addr(requests remote address)经过 MurmurHash2 算法进行哈希,
- 得出的值如果在前
50%
(从 0 到 2147483500),那么 $mirror_backend 的值为 test_backend - 如果不在前 50%,那么 $mirror_backend 的值为空字符 ""
- 50% test_backend; # 将流量复制到镜像后端
* ""
; 如果$mirror_backend
变量的值为空字符串,就不复制流量- 因为镜像请求的错误响应并不会影响原始请求,所以丢弃镜像请求并返回错误响应是很安全的
Nginx不允许复制POST请求流量
upstream backend {
server 127.0.0.1:8081;
}
upstream mirr_backend {
server 127.0.0.1:8082;
}
server {
server_name localhost;
listen 80;
location / {
mirror /nginx-mirror;
mirror_request_body off;
proxy_pass http://backend;
}
location = /nginx-mirror {
internal;
# 判断请求方法,不是GET返回403
if ($request_method !~* GET) {
return 403;
}
proxy_pass http://mirr_backend$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
}
}
测试流量
curl -H "Content-Type: application/json" -X POST -d '{"msg":"OK!" }' "http://192.168.0.5"
说明: 需要使用动态的服务后端,静态文件不支持post请求,Nginx会返回405
Nginx拷贝流量放大
upstream backend {
server 127.0.0.1:8081;
}
upstream mirr_backend {
server 127.0.0.1:8082;
}
server {
server_name localhost;
listen 80;
location / {
mirror /nginx-mirror;
# 多加一份mirror,流量放大一倍
mirror /nginx-mirror;
mirror_request_body on;
proxy_pass http://backend;
}
location = /nginx-mirror {
internal;
proxy_pass http://mirr_backend$request_uri;
proxy_pass_request_body on;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
}
我们请求一次流量
[root@abcdocker ~]# curl localhost?abcdocker
abcdocker web
web-01容器响应日志如下
web-01容器只响应了一个正常的请求
172.17.0.1 - - [09/Oct/2023:09:00:26 +0000] "GET /?abcdocker HTTP/1.0" 200 14 "-" "curl/7.29.0" "-"
mirror容器流量如下
172.17.0.1 - - [09/Oct/2023:09:00:26 +0000] "GET /?abcdocker HTTP/1.0" 200 13 "-" "curl/7.29.0" "-"
172.17.0.1 - - [09/Oct/2023:09:00:26 +0000] "GET /?abcdocker HTTP/1.0" 200 13 "-" "curl/7.29.0" "-"
#影响了2次,来模拟多流量的场景
如果我们需要多台节点来接收消息,只需要将mirror /nginx-mirror修改为mirror /nginx-mirror1,对应的location也修改为location = /nginx-mirror1即可
Nginx后端节点响应超时
我们在Nginx后端配置了mirror节点,我们将它down掉,检查是否会影响正常的流量请求
[root@abcdocker ~]# docker stop web-02
web-02
[root@abcdocker ~]# curl localhost
abcdocker web
测试结果: mirror节点down掉并不影响服务正常访问
Nginx流量拷贝的注意事项
如果mirror_request_body或者proxy_pass_request_body设置为 off,则Content-Length必须设置为""
因为nginx(mirror_request_body)处理post请求时,会根据Content-Length获取请求体, 如果Content-Length不为空,而由于mirror_request_body或者proxy_pass_request_body设置为off, 处理方以为post有内容,当request_body中没有,处理方会一直等待至超时,则前者为off,nginx会报upstream请求超时。
9折
???