以前我介绍过使用Rinetd和iptables来进行各种端口转发,然而这两个玩意来做转发其实都有各种各样的问题。例如Rinetd的转发性能并不好,iptables的配置各种麻烦且在高并发的情况下我还见识过能把kernel带崩的灵异状况。

事实上能做端口转发的工具其实并不少,例如Haproxy和socat。但为什么我最后还是选择使用nginx来转发呢?原因差不多就是,nginx配置较其他的工具更为方便一点。我相信很多人都对nginx的配置文件的语法并不陌生,能有较小的学习曲线。其次就是nginx相较iptables,nginx运行层较为安全,起码不会出现把kernel带崩的情况。

当然很大一部分也有我的个人原因,我崇尚能用nginx解决的事情绝对不用其他东西来解决。

nginx在1.9.0版本之后发布了一个新的官方模块ngx_stream_core_module,这个模块不仅能实现TCP和UDP转发还能支持负载均衡upstream配置。

但在此之前我们必须得先明确一点是,由于协议的不同,UDP转发仅会将请求包转发至目标,UDP回复包将由目标机器直接发出给你的请求端。这个回复包是不会经过转发端的。所以在例如你的转发目标端IP已经被不明设备给屏蔽无法连接的话,这个UDP回复包你还是收不到。当然这并不是nginx的问题。

由于ngx_stream_core_module模块默认并不会启用,所以我们要使用之前得在编译时添加 “--with-stream” 参数来启用这个模块。如果你使用的是军哥的lnmp一键安装包,直接修改lnmp.conf文件在Nginx_Modules_Options配置中添加--with-stream参数后保存之后使用升级脚本输入与显示的nginx当前版本号重新编译一次就能启用stream模块。

在激活模块后,我们需要编辑nginx.conf配置文件来对stream模块的具体配置。


TCP转发

TCP转发是stream一出来就支持的转发协议(啊废话不支持TCP你还转发啥呢?)所以从最开始的版本1.9.0的nginx所安装的模块里就能直接支持TCP转发。

首先我先放出一个最简单的TCP转发配置:

stream{
    
    server{
        listen 443; #如果不指定协议的话默认是TCP协议
        proxy_pass 1.1.1.1:443;
    }
 
}

以上的配置就是TCP转发的最简配置,注意,这里Stream模块的配置与一般的web配置是不一样的。你可以很明显的看出来stream是一块自己独立的配置列。

当然Stream模块能详细配置的东西还非常多,例如stream模块还支持sock数据交换接口,所以我们还可以这样写:

stream{
    
    server{
        listen 443; #如果不指定协议的话默认是TCP协议
        proxy_pass unix:/tmp/mysql.sock; #只是举例别乱填
    }
 
}

实际上Stream模块在对TCP协议的转发上还支持很多配置参数:

stream{
    
    server{
        listen 443; #如果不指定协议的话默认是TCP协议
        proxy_pass 1.1.1.1:443;
        proxy_connect_timeout 10s; #连接超时时间上限
        proxy_timeout 20s; #在没有数据传输的情况下,多久之后关闭连接
        proxy_buffer_size 512k; #设置用于读取被反代目标的数据缓冲区大小
    }
 
}

我们能很明显的发现,stream模块的配置其实跟HTTP模块很类似。但实际上stream模块与http模块上完全是两套不同的处理流程。用最简单的说法就是,HTTP模块是基于Layer7层的应用层处理流程,而Stream仅在Layer4层上对连接进行处理。所以stream模块无法像HTTP模块那样能区分vhost主机名(然而这Stream模块在引入了ssl配置之后又能支持了,这个后面再说。)。在理论上,stream模块的端口转发效率实际上相比HTTP模块的反向代理效率更高。虽然这两个是完全不能同概而论的东西。

刚刚我们还说到了stream模块支持upstream负载均衡配置,以下是使用了负载均衡的示例:

stream{

    upstream mysql_upstreams{
        hash $remote_addr consistent; #负载均衡一致性hash算法
        server 192.168.0.2:3306 weight=1 max_fails=1 fail_timeout=10s; # weight为轮询权重 max_fails最大失败尝试次数
        server 192.168.0.3:3306 weight=1 max_fails=1 fail_timeout=10s; # fail_timeout 失败时间
        server 192.168.0.3:3306 weight=1 max_fails=1 fail_timeout=10s backup; #backup标记为备用服务器
    }
    
    server{
        listen 3306; #如果不指定协议的话默认是TCP协议
        proxy_pass mysql_upstreams;
        proxy_connect_timeout 10s; #连接超时时间上限
        proxy_timeout 20s; #在没有数据传输的情况下,多久之后关闭连接
        proxy_buffer_size 512k; #设置用于读取被反代目标的数据缓冲区大小
    }
 
}

同样,Stream模块的TCP连接同样能设置keepalive来保持长连接。例子:

stream{

    upstream mysql_upstreams{
        hash $remote_addr consistent;
        server 192.168.0.2:3306 weight=1 max_fails=1 fail_timeout=10s;
        server 192.168.0.3:3306 weight=1 max_fails=1 fail_timeout=10s;
        server 192.168.0.4:3306 weight=1 max_fails=1 fail_timeout=10s backup;
    }
    
    server{
        listen 3306 so_keepalive=30m::10;
        # listen 3306 so_keepalive=on; 这个是启用keepalive使用默认参数的配置
        # keepalive的可配置参数差不多有以下几个:keepidle,keepintvl,keepcnt
        # keepidle 为连接保持时间,keepintvl 为连接的间隔时间, keepcnt是连接的个数。
        # 所以实际配置的格式为 so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]
        proxy_pass mysql_upstreams;
        proxy_connect_timeout 10s;
        proxy_timeout 20s;
        proxy_buffer_size 512k;
    }
 
}

然而比较草的是,这Stream模块还能引入ssl/tls来对TCP连接进行加密。由于TLS标准内对SNI提供了支持,所以又能识别主机名了。然而由于SSL这部分的东西日常实在是用不到我也懒得写,有兴趣的自己去翻翻nginx的文档看看就知道了(反正都要用SSL/TLS了肯定也是看的懂文档了。


UDP转发

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

照例先贴出个最简单暴力的UDP转发例子:

stream{
    
    server{
        listen 53 udp;
        proxy_pass 1.1.1.1:53;
    }
 
}

当然,UDP转发也一样支持upstream负载均衡。然而由于UDP协议的特性(我UDP就是只管暴力发发发就是了,没有那么多讲究),很多TCP转发上的参数是无法使用的。

stream{

    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_pass dns_upstreams;
        proxy_responses 1; # nginx版本1.9.13版本以上支持的参数。UDP协议专用,期望后端返回给客户端数据包的数量
        proxy_timeout 20s; # 超时时间
    }
 
}


以上就是nginx的stream模块端口转发功能的一些示例。nginx确实是非常的方便,虽然nginx在超高并发的情况下也是有着资源占用过高的问题,但实际上放在生产环境里并没有什么太大问题。当然可能性能还是达不到Haproxy的水平,但对于我们一般使用而言,nginx足矣胜任大部分需求。