下图是 nginx upstream keepalive 长连接的实现原理。首先每个进程需要一个 connection pool,里面都是长连接,多进程之间是不需要共享这个连接池的。 一旦与后端服务器建立连接,则在当前请求连接结束之后不会立即关闭连接,而是把用完的连接保存在一个 keepalive connection pool 里面,以后每次需要建立向后连接的时候,只需要从这个连接池里面找。
如果找到合适的连接的话,就可以直接来用这个连接,不需要重新创建 socket 或者发起 connect()。这样既省下建立连接时在握手的时间消耗,又可以避免 TCP 连接的 slow start 慢启动。如果在 keepalive 连接池找不到合适的连接,那就按照原来的步骤重新建立连接。
如果你的连接池的数控制在 128 个,总共线程池内的线程数是 128 * nginx worker 个,但因为你要应对更多的并发请求,所以临时又加了很多的连接,但这临时的连接是短连接和长连接要看你的 Nginx 版本。那他如何被收回,两地保证,一点是他会主动去释放,另一点是 keepalive timeout 的时间。
需要注意的是,使用 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
以下的配置就是 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; } }
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; } }
以下的配置就是 UDP 转发的最简配置
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; } }