Nginx支持TCP代理和负载均衡-stream模块

个人专题目录


nginx安装手册

LVS,Nginx和HAProxy负载均衡对比

Nginx支持TCP代理和负载均衡-stream模块

ngx_stream_core_module

ngx_stream_core_module模块自1.9.0版开始提供。默认情况下不构建此模块,应使用--with-stream 配置参数启用它。

官网:http://nginx.org/en/docs/stream/ngx_stream_core_module.html

示例配置

worker_processes auto;

error_log /var/log/nginx/error.log info;

#事件
events {
    worker_connections 1024;
}

#流模块
stream {
    #上游后端
    upstream backend {
        hash $remote_addr consistent;

        server backend1.example.com:12345 weight = 5;
        server 127.0.0.1:12345 max_fails = 3 fail_timeout = 30s;
        server unix:/ tmp / backend3;
    }
    #上游后端
    upstream dns {
       server 192.168.0.1:53535;
       server dns.example.com:53;
    }

    server {
        listen 12345;
        proxy_connect_timeout 1s;
        proxy_timeout 3s;
        proxy_pass backend;
    }

    server {
        listen 127.0.0.1:53 udp reuseport;
        proxy_timeout 20s;
        proxy_pass dns;
    }

    server {
        listen [::1]:12345;
        proxy_pass unix:/tmp/stream.socket;
    }
}

指令

listen address:port [ssl] [udp] [proxy_protocol] [backlog=number] [rcvbuf=size] [sndbuf=size] [bind] [ipv6only=on|off] [reuseport] [so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]];

设置服务器将接受连接的套接字addressport。可以仅指定端口。地址也可以是主机名,例如:

listen 127.0.0.1:12345;
listen *:12345;
listen 12345;     # same as *:12345
listen localhost:12345;

IPv6地址在方括号中指定:

listen [::1]:12345;
listen [::]:12345;

UNIX域套接字使用“ unix:”前缀指定:

listen unix:/var/run/nginx.sock;

ssl参数允许指定此端口上接受的所有连接都应在SSL模式下工作。

udp参数配置一个侦听套接字以处理数据报(1.9.13)。

proxy_protocol参数(1.11.4)允许指定这个端口上接受的所有连接应使用 代理服务器协议

自版本1.13.11起支持PROXY协议版本2。

listen指令可以有几个特定于与套接字相关的系统调用的附加参数。

  • backlog=number

    设置调用中的backlog参数,该参数 listen()限制挂起连接队列的最大长度(1.9.2)。默认情况下, backlog在FreeBSD,DragonFly BSD和macOS上设置为-1,在其他平台上设置为511。

  • rcvbuf=size

    设置SO_RCVBUF侦听套接字的接收缓冲区大小(选项)(1.11.13)。

  • sndbuf=size

    设置SO_SNDBUF侦听套接字的发送缓冲区大小(选项)(1.11.13)。

  • bind

    此参数指示对bind() 给定地址进行单独调用 addressport。事实是,如果有多个listen指令具有相同的端口但地址不同,并且其中一个 listen指令侦听给定端口(*:port)的所有地址,则nginx将 bind()仅执行*:port。应该注意,getsockname()在这种情况下将进行系统调用以确定接受连接的地址。如果使用ipv6onlyso_keepalive参数,那么对于给定的 addressportbind()将始终进行单独的调用。

  • ipv6only= on|off

    此参数确定(通过IPV6_V6ONLY套接字选项)侦听通配符地址的IPv6套接字是[::] 仅接受IPv6连接还是仅接受IPv6和IPv4连接。默认情况下,此参数处于启用状态。它只能在开始时设置一次。

  • reuseport

    此参数(1.9.1)指示为每个工作进程创建一个单独的侦听套接字(使用SO_REUSEPORTLinux 3.9+和DragonFly BSD上的 套接字选项,或者SO_REUSEPORT_LB在FreeBSD 12+上),允许内核在工作进程之间分配传入连接。目前仅适用于Linux 3.9 +,DragonFly BSD和FreeBSD 12+(1.15.1)。不恰当地使用此选项可能会产生安全 隐患

  • so_keepalive= on| off| [ keepidle]:[ keepintvl]:[ keepcnt]

    此参数配置侦听套接字的“TCP keepalive”行为。如果省略此参数,则操作系统的设置将对套接字有效。如果将其设置为值“ on”, SO_KEEPALIVE则为套接字打开选项。如果将其设置为值“ off”,SO_KEEPALIVE则为套接字关闭该选项。在每个插槽的基础使用的TCP保活参数某些操作系统支持设置TCP_KEEPIDLETCP_KEEPINTVLTCP_KEEPCNT套接字选项。在这样的系统(目前,Linux的2.4 +,NetBSD的5+和FreeBSD 9.0-STABLE),它们可以使用配置的keepidlekeepintvlkeepcnt参数。可以省略一个或两个参数,在这种情况下,相应套接字选项的系统默认设置将生效。例如,SO_KEEPALIVE =30m::10将idle timeout(TCP_KEEPIDLE)设置为30分钟,将探测间隔(TCP_KEEPINTVL)保留为系统默认值,并将探测count(TCP_KEEPCNT)设置为10个探测器。

不同的服务器必须监听不同的 address:port对。

语法

Syntax Default Context Description
preread_buffer_size size 16k stream,server 指定size了的 预读缓冲区。
preread_timeout timeout 30s stream,server 指定timeout了的 预读阶段。
proxy_protocol_timeout timeout 30s stream,server 指定timeout用于读取PROXY协议标头以完成。如果在此时间内未传输整个标头,则关闭连接。
resolver address ... [valid=time][ipv6=on off - stream,server 将用于解析上游服务器名称的名称服务器配置到地址中,例如:resolver 127.0.0.1 [:: 1]:5353;
resolver_timeout time 30s stream,server 设置名称解析的超时
server { ... } - stream 设置服务器的配置
stream { ... } - main 提供指定流服务器指令的配置文件上下文
tcp_nodelay on | off on stream,server 启用或禁用该TCP_NODELAY选项的使用。为客户端和代理服务器连接启用该选项
variables_hash_bucket_size size 64 stream 设置变量哈希表的桶大小。设置哈希表的详细信息在单独的文档中提供 。
variables_hash_max_size size 1024 stream 设置size变量哈希表的最大值。设置哈希表的详细信息在单独的文档中提供。

可以将地址指定为域名或IP地址,以及可选端口。如果未指定端口,则使用端口53。以循环方式查询名称服务器。

默认情况下,nginx将在解析时查找IPv4和IPv6地址。如果不需要查找IPv6地址,则ipv6=off可以指定参数。

默认情况下,nginx使用响应的TTL值缓存答案。可选valid参数允许覆盖它:

resolver 127.0.0.1 [::1]:5353 valid=30s;

嵌入式变量

ngx_stream_core_module模块支持自1.11.2以来的变量。

  • $binary_remote_addr

    客户端地址采用二进制形式,值的长度始终为IPv4地址的4个字节或IPv6地址的16个字节

  • $bytes_received

    从客户端收到的字节数(1.11.4)

  • $bytes_sent

    发送到客户端的字节数

  • $connection

    连接序列号

  • $hostname

    主机名

  • $msec

    以毫秒为单位的当前时间(以毫秒为单位)

  • $nginx_version

    nginx版本

  • $pid

    工作进程的PID

  • $protocol

    用于与客户端沟通的协议: TCPUDP(1.11.4)

  • $proxy_protocol_addr

    来自PROXY协议头的客户端地址,否则为空字符串(1.11.4)必须先通过proxy_protocollisten指令中设置参数来启用PROXY协议 。

  • $proxy_protocol_port

    来自PROXY协议头的客户端端口,否则为空字符串(1.11.4)必须先通过proxy_protocollisten指令中设置参数来启用PROXY协议 。

  • $remote_addr

    客户端地址

  • $remote_port

    客户端端口

  • $server_addr

    接受连接的服务器的地址计算此变量的值通常需要一次系统调用。为避免系统调用,listen指令必须指定地址并使用该bind参数。

  • $server_port

    接受连接的服务器的端口

  • $session_time

    会话持续时间(以秒为单位),分辨率为毫秒(1.11.4);

  • $status

    会话状态(1.11.4),可以是以下之一:200会话成功完成400无法解析客户端数据,例如PROXY协议403例如,当某些客户端地址的访问受限时,禁止访问500内部服务器错误502坏网关,例如,如果无法选择或到达上游服务器。503服务不可用,例如,当访问受连接数限制时

  • $time_iso8601

    当地时间采用ISO 8601标准格式

  • $time_local

    通用日志格式的本地时间

测试nginx代理TCP协议的配置

realserver : 10.111.17.89:8080

nginx :10.111.16.75

客户端:10.100.34.198

TCP监听端口:2018

配置nginx

nginx1.90对TCP协议的代理并不是默认开启的,需要在编译的时候配置 --with-stream 参数:

./configure --add-module=/root/nginx_upstream_check_module-master --with-stream

make && make install

nginx.config文件参照官网:

stream {
    
    upstream tcpend {
        hash $remote_addr consistent;

        server 10.111.17.89:8080 weight=5 max_fails=3 fail_timeout=30s;
    }
    
    server {
        listen 2018;
        proxy_connect_timeout 1s;
        proxy_timeout 180s;
        proxy_pass tcpend;
    }  
}

启动nginx,发现nginx已经开始监听2018端口了

cd usr/local/nginx/sbin
#启动
./nginx
#重启
./nginx -s reload
#判断配置文件是否正确
./nginx -t
netstat -anp|grep :2018

[root@vm10-111-16-75 ~]# netstat -anp|grep :2018

tcp 0 0 0.0.0.0:2018 0.0.0.0:* LISTEN 18457/nginx

测试客户端连realserver

在客户端通过telnet连接realserver的2018端口:

telnet
open 10.111.17.89 8080

[root@vm10-65-140-249 ~]# telnet 
telnet> open 10.111.17.89 8080
Trying 10.111.17.89...
Connected to 10.111.17.89.
Escape character is '^]'.
Connection closed by foreign host.
[root@vm10-65-140-249 ~]# telnet 
telnet> open 10.111.17.89 8080
Trying 10.111.17.89...
Connected to 10.111.17.89.
Escape character is '^]'

在realserver上查看网络连接:

netstat -anpl|grep :8080

[root@vm10-111-17-89 ~]# netstat -anpl|grep :8080
tcp        0      0 :::8080                     :::*                        LISTEN      5182/java           
tcp        0      0 ::ffff:10.111.17.89:8080    ::ffff:10.100.34.198:14470  ESTABLISHED 5182/java 

可以正常连接

测试客户端连接nginx

在客户端通过telnet连接nginx所在服务器的2018端口

telnet
open 10.111.16.75 2018

[root@vm10-65-140-249 ~]# telnet
telnet> open 10.111.16.75 2018
Trying 10.111.16.75...
Connected to 10.111.16.75.
Escape character is '^]'.

在nginx机器上查看网络连接

[root@vm10-111-16-75 ~]# netstat -anp|grep :2018
tcp        0      0 0.0.0.0:2018                0.0.0.0:*                   LISTEN      18457/nginx         
tcp        0      0 10.111.16.75:2018           10.100.34.198:51539         ESTABLISHED 18458/nginx 

在realserver上查看网络连接

[root@vm10-111-17-89 ~]# netstat -anpl|grep :8080
tcp        0      0 :::8080                     :::*                        LISTEN      5182/java           
tcp        0      0 ::ffff:10.111.17.89:8080    ::ffff:10.111.16.75:40794   ESTABLISHED 5182/java  

nginx是给做了一个TCP连接的中转。

client和nginx有一个tcp长连接,nginx和realserver有一个tcp长连接,但是client和realserver之间并没有tcp长连接,仅由nginx服务器负责数据中转。

四层七层负载的区别

1. 什么是负载均衡

负载均衡 建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。

2. 负载均衡分类

负载均衡根据所采用的设备对象(软/硬件负载均衡),应用的OSI网络层次(网络层次上的负载均衡),及应用的地理结构(本地/全局负载均衡)等来分类。根据应用的 OSI 网络层次来分类的两个负载均衡类型。

网络模型图,包含了 OSI 模型及 TCP/IP 模型,两个模型虽然有一点点区别,但主要的目的是一样的,模型图描述了通信是怎么进行的。它解决了实现有效通信所需要的所有过程,并将这些过程划分为逻辑上的层。层可以简单地理解成数据通信需要的步骤。

OSI_TCP/IP

根据负载均衡所作用在 OSI 模型的位置不同,负载均衡可以大概分为以下几类:

  • 二层负载均衡(mac)

    根据OSI模型分的二层负载,一般是用虚拟mac地址方式,外部对虚拟MAC地址请求,负载均衡接收后分配后端实际的MAC地址响应。

  • 三层负载均衡(ip)

    一般采用虚拟IP地址方式,外部对虚拟的ip地址请求,负载均衡接收后分配后端实际的IP地址响应。

  • 四层负载均衡(tcp)

    在三层负载均衡的基础上,用ip+port接收请求,再转发到对应的机器。

  • 七层负载均衡(http)

    根据虚拟的url或IP,主机名接收请求,再转向相应的处理服务器。

在实际应用中,比较常见的就是四层负载及七层负载。这里也重点说下这两种负载。

3. 四层负载均衡(基于IP+端口的负载均衡)

所谓四层负载均衡,也就是主要通过报文中的目标地址和端口,再加上负载均衡设备设置的服务器选择方式,决定最终选择的内部服务器。

layer4
  1. 在三层负载均衡的基础上,通过发布三层的IP地址(VIP),然后加四层的端口号,来决定哪些流量需要做负载均衡,对需要处理的流量进行NAT处理,转发至后台服务器,并记录下这个TCP或者UDP的流量是由哪台服务器处理的,后续这个连接的所有流量都同样转发到同一台服务器处理。
  2. 以常见的TCP为例,负载均衡设备在接收到第一个来自客户端的SYN 请求时,即通过上述方式选择一个最佳的服务器,并对报文中目标IP地址进行修改(改为后端服务器IP),直接转发给该服务器。TCP的连接建立,即三次握手是客户端和服务器直接建立的,负载均衡设备只是起到一个类似路由器的转发动作。在某些部署情况下,为保证服务器回包可以正确返回给负载均衡设备,在转发报文的同时可能还会对报文原来的源地址进行修改。
  3. 对应的负载均衡器称为四层交换机(L4 switch),主要分析IP层及TCP/UDP层,实现四层负载均衡。此种负载均衡器不理解应用协议(如HTTP/FTP/MySQL等等)
    要处理的流量进行NAT处理,转发至后台服务器,并记录下这个TCP或者UDP的流量是由哪台服务器处理的,后续这个连接的所有流量都同样转发到同一台服务器处理。
  4. 实现四层负载均衡的软件有:
    • F5:硬件负载均衡器,功能很好,但是成本很高。
    • lvs:重量级的四层负载软件
    • nginx:轻量级的四层负载软件,带缓存功能,正则表达式较灵活
    • haproxy:模拟四层转发,较灵活

4. 七层的负载均衡(基于虚拟的URL或主机IP的负载均衡)

所谓七层负载均衡,也称为“内容交换”,也就是主要通过报文中的真正有意义的应用层内容,再加上负载均衡设备设置的服务器选择方式,决定最终选择的内部服务器。

layer7
  1. 在四层负载均衡的基础上(没有四层是绝对不可能有七层的),再考虑应用层的特征,比如同一个Web服务器的负载均衡,除了根据VIP加80端口辨别是否需要处理的流量,还可根据七层的URL、浏览器类别、语言来决定是否要进行负载均衡。举个例子,如果你的Web服务器分成两组,一组是中文语言的,一组是英文语言的,那么七层负载均衡就可以当用户来访问你的域名时,自动辨别用户语言,然后选择对应的语言服务器组进行负载均衡处理。
  2. 以常见的TCP为例,负载均衡设备如果要根据真正的应用层内容再选择服务器,只能先代理最终的服务器和客户端建立连接(三次握手)后,才可能接受到客户端发送的真正应用层内容的报文,然后再根据该报文中的特定字段,再加上负载均衡设备设置的服务器选择方式,决定最终选择的内部服务器。负载均衡设备在这种情况下,更类似于一个代理服务器。负载均衡和前端的客户端以及后端的服务器会分别建立TCP连接。所以从这个技术原理上来看,七层负载均衡明显的对负载均衡设备的要求更高,处理七层的能力也必然会低于四层模式的部署方式。
  3. 对应的负载均衡器称为七层交换机(L7 switch),除了支持四层负载均衡以外,还有分析应用层的信息,如HTTP协议URI或Cookie信息,实现七层负载均衡。此种负载均衡器能理解应用协议。
  4. 实现七层负载均衡的软件有:
    • haproxy:天生负载均衡技能,全面支持七层代理,会话保持,标记,路径转移;
    • nginx:只在http协议和mail协议上功能比较好,性能与haproxy差不多;
    • apache:功能较差
    • Mysql proxy:功能尚可。

5. 区别

区别 四层负载均衡(layer 4) 七层负载均衡(layer 7)
基于 基于IP+Port的 基于虚拟的URL或主机IP等。
类似于 路由器 代理服务器
握手次数 1 次 2次
复杂度
性能 高;无需解析内容 中;需要算法识别 URL,Cookie 和 HTTP head 等信息
安全性 低,无法识别 DDoS等攻击 高, 可以防御SYN cookie以SYN flood等
额外功能 会话保持,图片压缩,防盗链等

推荐阅读更多精彩内容