nginx TCP 和 UDP 负载平衡

  • 自 version 1.9.0 以来新增了 ngx_stream_core_module 模块,使 nginx 支持四层负载均衡。这个模块不仅能实现 TCP 和 UDP 转发,还能支持负载均衡 upstream 配置。默认编译的时候该模块并未编译进去,需要编译的时候添加 --with-stream 参数使其支持 stream 代理。
  • 由于协议的不同,UDP 转发仅会将请求包转发至目标,UDP 回复包将由目标机器直接发出给你的请求端。这个回复包是不会经过转发端的,所以在你转发目标端 IP 已经被不明设备给屏蔽无法连接的话,这个 UDP 回复包你还是收不到,这并不是 Nginx 的问题。
  • 请注意,stream 配置不能放到 http 内,因为stream是通过tcp层转发,而不是 http 转发
使用Nginx实现TCP反向代理
使用Nginx实现TCP反向代理
  • 下图是 nginx upstream keepalive 长连接的实现原理。首先每个进程需要一个 connection pool,里面都是长连接,多进程之间是不需要共享这个连接池的。 一旦与后端服务器建立连接,则在当前请求连接结束之后不会立即关闭连接,而是把用完的连接保存在一个 keepalive connection pool 里面,以后每次需要建立向后连接的时候,只需要从这个连接池里面找。

  • 如果找到合适的连接的话,就可以直接来用这个连接,不需要重新创建 socket 或者发起 connect()。这样既省下建立连接时在握手的时间消耗,又可以避免 TCP 连接的 slow start 慢启动。如果在 keepalive 连接池找不到合适的连接,那就按照原来的步骤重新建立连接。

  • 如果你的连接池的数控制在 128 个,总共线程池内的线程数是 128 * nginx worker 个,但因为你要应对更多的并发请求,所以临时又加了很多的连接,但这临时的连接是短连接和长连接要看你的 Nginx 版本。那他如何被收回,两地保证,一点是他会主动去释放,另一点是 keepalive timeout 的时间。

使用Nginx实现TCP反向代理 使用Nginx实现TCP反向代理

1. 手动编译

需要注意的是,使用 DockerHub 中的 Nginx 镜像是不用二次编译的。

# 下载1.9版本以上的nginx
wget http://nginx.org/download/nginx-1.20.2.tar.gz

# 安装依赖包
yum install -y gcc glibc gcc-c++ prce-devel openssl-devel pcre-devel
useradd -s /sbin/nologin nginx -M

# 加压目录
tar xf nginx-1.10.3.tar.gz && cd nginx-1.20.2

# 手动编译
./configure \
    --prefix=/usr/local/nginx-1.20.2 \
    --user=nginx \
    --group=nginx \
    --with-http_ssl_module \
    --with-http_stub_status_module \
    --with-stream

# 安装到系统上
make && make install

# 检查配置文件
/usr/local/nginx/sbin/nginx -t

2. Nginx TCP 转发

以下的配置就是 TCP 转发的最简配置

  • 我们能很明显的发现,stream 模块的配置其实跟 http 模块很类似。但实际上 stream 模块与 http 模块上完全是两套不同的处理流程。用最简单的说法就是,http 模块是基于 Layer7 层的应用层处理流程,而 Stream 仅在 Layer4 层上对连接进行处理。

  • 所以 stream 模块无法像 http 模块那样能区分 vhost 主机名,然而这在 stream 模块在引入了 ssl 配置之后又能支持了。而且 stream 模块还能引入 ssl/tls 来对 TCP 连接进行加密。由于 TLS 标准内对 SNI 提供了支持,所以又能识别主机名了。在理论上,stream 模块的端口转发效率实际上相比 http 模块的反向代理效率更高。

 
user  nginx;
worker_processes  1;

events {
    worker_connections  1024;
}

stream {
    # 全局配置
    preread_timeout        120s;
    proxy_connect_timeout  120s;
    proxy_protocol_timeout 120s;
    resolver_timeout       120s;
    proxy_timeout          120s;
    tcp_nodelay            on;

    # 设置日志格式
    log_format proxy '$remote_addr [$time_local] '
                  '$protocol $status $bytes_sent $bytes_received '
                  '$session_time "$upstream_addr" "$upstream_bytes_sent"'
                  '"$upstream_bytes_received" "$upstream_connect_time"';

    access_log /var/log/nginx/stream.access.log proxy;
    error_log  /var/log/nginx/stream.error.log error;

    upstream app_pg {
        hash $remote_addr consistent;
        server 192.168.100.60:5432;
        server 192.168.100.61:5432;
        server 192.168.100.62:5432;
    }

    server {
        # 不指定协议默认是TCP协议
        listen 127.0.0.1:5432 so_keepalive=on;
        proxy_pass app_pg;
    }

    server{
        # keepalive的可配置参数差不多有以下几个:keepidle,keepintvl,keepcnt
        # keepidle为连接保持时间;keepintvl为连接的间隔时间;keepcnt是连接的个数

        # 下示将idle超时设置为30分钟,探测间隔为系统默认值,并将探测计数设置为10个探测器
        # 实际配置的格式为:so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]
        listen *:3306 so_keepalive=30m::10;
        proxy_connect_timeout 10s;
        proxy_timeout 20s;
        proxy_buffer_size 512k;
        proxy_pass 192.168.100.60:8000;
    }
}

 

  • Nginx实现 SSH 转发
 
user  nginx;
worker_processes  1;

events {
    worker_connections  1024;
}

stream {
    upstream ssh {
            hash $remote_addr consistent;
            server 192.168.1.42:22 weight=5;
       }

    server {
        listen 2222;
        proxy_pass ssh;
       }
}

3. Nginx UDP 转发

以下的配置就是 UDP 转发的最简配置

  • UDP 转发并不是 stream 模块一开始就支持的,而是在 1.9.3 版本之后的 stream 模块才追加了 UDP 转发支持,所以要配置 UDP 转发前必须得先确定自己的 Nginx 版本是否达到了要求。
user  nginx;
worker_processes  1;

events {
    worker_connections  1024;
}

stream {
    # 全局配置
    proxy_timeout 120s;
    tcp_nodelay on;

    # 设置日志格式
    log_format proxy '$remote_addr [$time_local] '
                 '$protocol $status $bytes_sent $bytes_received '
                 '$session_time "$upstream_addr" '
                 '"$upstream_bytes_sent" "$upstream_bytes_received" "$upstream_connect_time"';

    access_log /var/log/nginx/stream.access.log proxy;
    error_log /var/log/nginx/stream.error.log error;

    # 配置dns负载均衡
    upstream dns_upstreams {
        server 1.1.1.1:53 weight=1;
        server 1.0.0.1:53 weight=1;        # weight负载均衡权重
        server 8.8.8.8:53 weight=1 backup; # backup标记为备用服务器
    }

    server{
        listen 53 udp;
        proxy_responses 1; # UDP协议专用;期望后端返回给客户端数据包的数量
        proxy_timeout 20s; # 超时时间
        proxy_pass dns_upstreams;
    }
}