代码改变世界

nginx流量复制与放大

2022-08-15 18:44  曾左  阅读(564)  评论(0编辑  收藏  举报

一、需求

1. 功能方面

在不影响真实业务前提下,能满足:

(1)流量复制,用于线故障分析、系统迁移评估等

(2)流量放大,通过多倍复制,实现放大流量,用于性能压测

2. 配置方面

(1)支持或禁止 post 请求复制

(2)记录镜像请求的访问日志

二、解决方案

nginx 1.13.4 版本,内置 ngx_http_mirror_module 模块,能满足上述需求

1. ngx_http_mirror_module 模块特性

(1)相比 tcp-copy 的优势:无需录制流量,实时可用,配置相当简单

(2)源站请求,直接原路返回

(3)复制请求不影响源站请求,源站 nginx-server 将流量复制到 mirror 站后,两者不再有任何交集

三、配置方法

下面配置在 nginx 1.14.1 验证通过,具体配置说明,请看注释信息

1. 复制请求

server {
        listen       80;
        server_name  web1.www.com;
        # 源站配置
        location / {
                access_log  /data/nginx/1.14.1/logs/web1/access.log  accesslog;
                mirror /mirror;
                mirror_request_body on;# Indicates whether the client request body is mirrored. default value is on.
                proxy_pass http://web1.upstream.name;
        }
        # 镜像站点配置
        location /mirror {
                internal; # 内部配置
                proxy_pass http://mirror.web1.upstream.name$request_uri;
                proxy_pass_request_body on; # Indicates whether the original request body is passed to the proxied server. default value is on
                proxy_set_header X-Original-URI $request_uri; # 使用真实的 uri 重置 uri
        }
}

2. 不允许复制 post 请求

默认支持 post 请求,禁止需要将 mirror_request_body 修改为 off,并判断$request_method

server {
        listen       80;
        server_name  web1.www.com;

        # 源站配置
        location / {
                access_log  /data/nginx/1.14.1/logs/web1/access.log  accesslog;
                mirror /mirror;
                mirror_request_body off;# Indicates whether the client request body is mirrored. default value is on.
                proxy_pass http://web1.upstream.name;
        }

        # 镜像站点配置
        location /mirror {
                # 判断请求方法,不是 GET 返回 403
                if ($request_method != GET) {
                    return 403;
                }
                internal; # 内部配置
                proxy_pass http://mirror.web1.upstream.name$request_uri;
                proxy_pass_request_body off; # Indicates whether the original request body is passed to the proxied server. default value is on
                proxy_set_header Content-Length ""; # mirror_request_body/proxy_pass_request_body 都设置为 off,则 Conten-length 需要设置为"",否则有坑
                proxy_set_header X-Original-URI $request_uri; # 使用真实的 uri 重置 uri
        }
}

3. 流量放大

配置多份 mirror

server {
        listen       80;
        server_name  web1.www.com;
        # 源站配置
        location / {
                access_log  /data/nginx/1.14.1/logs/web1/access.log  accesslog;
                mirror /mirror;
                # 多加一份 mirror,流量放大一倍
                mirror /mirror;
                mirror_request_body on;# Indicates whether the client request body is mirrored. default value is on.
                proxy_pass http://web1.upstream.name;
        }
        # 镜像站点配置
        location /mirror {
                internal; # 内部配置
                proxy_pass http://mirror.web1.upstream.name$request_uri;
                proxy_pass_request_body on; # Indicates whether the original request body is passed to the proxied server. default value is on
                proxy_set_header X-Original-URI $request_uri; # 使用真实的 uri 重置 uri
        }
}

4. 添加 mirror 日志

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

server {
        listen       80;
        server_name  web1.www.com;

        # 源站配置
        location / {
                access_log  /data/nginx/1.14.1/logs/web1/access.log  accesslog;
                mirror /mirror;
                mirror_request_body off;# Indicates whether the client request body is mirrored. default value is on.
                proxy_pass http://web1.upstream.name;
        }

        # 镜像站点配置
        location /mirror {
                internal; # 内部配置
                # 跳转到下面的内部 server
                proxy_pass http://127.0.0.1:10901$request_uri;
                proxy_set_header X-Original-URI $request_uri; # 使用真实的 uri 重置 uri
        }
}

server {
    # server 没法设置为内部
    listen 127.0.0.1:10901;
    location / {
        access_log /data/nginx/1.14.1/logs/web1/access.log  accesslog;
        proxy_pass http://mirror.web1.upstream.name;
    }
}

四、性能影响评估

测试前提:使用 jemeter,在相同环境,使用 30 个并发,各请求 3000 次 get 或 post 方法,参数一样,一组为有 mirror 配置,另一组为没 mirror 配置。

测试结果:mirror 性能损失在 5%以内,具体如下:

性能影响评估

五、遇到问题

1. 镜像配置错误确时,无日志

镜像配置不正确,导致复制操作没正常执行,但是 nginx 没有相应的错误日志,严重影响调试。建议配置镜像日志,配置方法如 4. 添加 mirror 日志

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

如果 mirror_request_body 或者 proxy_pass_request_body 设置为 off,则 Content-Length 必须设置为""。因为 nginx/tomcat 处理 post 请求时,会根据 Content-Length 获取 request_body。如果 Content-Length 不为空,mirror_request_body、proxy_pass_request_body 设置为 off ,导致提交内容为空,但是nginx/tomcat 会以为 post 有内容,实际上 request_body 没有内容。此时 nginx 会报 upstream 请求超时,tomcat 会报如下错误:

"2018-11-08T17: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)

九、参考资料

Module ngx_http_mirror_module

流量搬运工,Nginx 模块 ngx_http_mirror_module

How to log nginx mirror requests