1. 项目概述为什么需要Nginx来做数据库端口转发在分布式系统架构和日常运维中数据库的访问管理是个绕不开的话题。直接暴露数据库的默认端口比如MySQL的3306、PostgreSQL的5432到公网无异于敞开大门邀请不速之客安全风险极高。而传统的做法比如在防火墙上做端口映射或者让应用服务器直连数据库内网IP又常常面临灵活性不足、IP变动麻烦、无法做精细控制等问题。这时候Nginx这个“瑞士军刀”就派上用场了。很多人对Nginx的印象还停留在Web服务器和HTTP反向代理其实它的Stream模块ngx_stream_core_module从1.9.0版本开始就提供了强大的四层TCP/UDP流量代理能力。用Nginx做数据库端口转发本质上就是利用这个Stream模块在应用服务器和数据库服务器之间建立一个安全的、可控的“中间层”。这么做能解决几个核心痛点第一是安全你可以把数据库放在一个完全没有公网IP的私有网络只让Nginx这台有公网IP或处于DMZ区的机器能访问它外部请求全部打到Nginx上由Nginx进行转发相当于加了一道坚固的闸门。第二是灵活与透明对于前端应用来说它连接的就是Nginx的IP和端口完全感知不到后端数据库的真实地址。以后数据库迁移、扩容、IP变更你只需要改动Nginx的配置所有应用服务无需任何修改维护成本大大降低。第三是具备扩展潜力虽然Stream模块本身功能不如HTTP模块丰富但你依然可以基于此实现简单的负载均衡轮询连接、连接限制、基于客户端IP的访问控制等为后续架构演进留出空间。我经历过好几次因为数据库直连导致的应用发布僵局改一个数据库IP得协调好几个团队同时更新配置文件。自从在关键路径上引入了Nginx作为数据库的端口转发代理这类问题就再也没出现过。下面我就把从原理到配置再到踩坑经验的完整流程拆解给你。2. 核心原理与方案选型考量2.1 四层转发 vs 七层代理为什么是Stream模块首先要厘清一个关键概念数据库协议如MySQL、Redis协议运行在TCP层之上属于四层网络模型。Nginx处理这类流量必须使用其四层代理能力即ngx_stream_core_module模块通常我们称之为Stream模块。这与我们更熟悉的HTTP反向代理七层代理有本质区别七层代理HTTP模块Nginx能解析HTTP协议理解URL、Header、Method等信息。因此可以做基于域名、路径的路由修改请求头/响应头做缓存、限流等非常复杂的业务逻辑。四层代理Stream模块Nginx不解析应用层协议它只工作在传输层TCP/UDP。它看到的就是原始的二进制数据流。它的工作很简单从客户端接收一个TCP连接然后建立一个到后端服务器的TCP连接接着在两者之间透明地转发数据包。对于数据库端口转发我们需要的正是这种“透明转发”。我们不希望Nginx去解析SQL语句它也做不到只希望它做一个可靠的数据搬运工。因此Stream模块是我们的不二之选。2.2 自建Nginx转发 vs 云厂商负载均衡器在决定动手之前另一个需要权衡的选择是自己用Nginx搭建还是直接使用云服务商提供的负载均衡器如AWS的ELB/ALB/NLB、阿里云的SLB、腾讯云的CLB自建Nginx方案的优势在于完全可控配置灵活度极高你可以编写复杂的access_by_lua脚本如果编译了OpenResty实现动态路由、精细化的ACL控制。成本可能更低对于流量不大但连接数较多的内部服务一台配置不错的ECS上部署Nginx成本可能低于云上按小时计费的LB实例。环境一致如果你已经有一个成熟的Nginx运维体系配置管理、监控、告警纳入新的Stream服务会非常顺滑。云厂商负载均衡器的优势在于高可用与弹性云LB天生就是高可用架构无需自己操心Nginx本身的高可用如KeepalivedVIP方案。免运维不需要管理Nginx服务器不用担心系统漏洞、性能调优。深度集成通常与云上的监控、日志服务如CloudWatch、SLS集成更好也更容易与云防火墙、WAF等安全产品联动。注意这里需要特别提一下有些云厂商的负载均衡器如AWS的ALB主要针对HTTP/HTTPS七层对于数据库TCP转发应选择其网络负载均衡器如AWS NLB、阿里云SLB的TCP监听或传统型负载均衡器。文首热词中提到的“elb是什么?该怎么构建?elb后面是2个nginx服务器,可以吗?”这个问题其场景通常是ELB七层后面接Web服务器Nginx这是经典架构。但如果是数据库转发ELB如果是ALB可能不适用需要选择NLB或直接使用Nginx。对于大多数追求灵活性和可控性的团队或者混合云、私有化部署的场景自建Nginx是一个性价比极高且技能通用的方案。接下来我们就聚焦于自建方案。3. 环境准备与Nginx Stream模块配置3.1 Nginx安装与Stream模块确认首先你需要一台服务器作为转发代理机。这台机器需要能同时被客户端你的应用和后端数据库访问。操作系统以CentOS 7/8或Ubuntu 20.04/22.04为例。通过包管理器安装推荐给大多数用户对于CentOS需要先配置EPEL仓库对于Ubuntu默认仓库通常就有。安装后Nginx默认可能不包含Stream模块但主流发行版的官方包现在一般都包含了。# CentOS 7/8 sudo yum install epel-release sudo yum install nginx # Ubuntu 20.04/22.04 sudo apt update sudo apt install nginx安装后使用以下命令检查Stream模块是否已编译进去nginx -V 21 | grep -o with-stream如果输出--with-stream则说明支持。如果未找到你可能需要从源码编译安装。源码编译安装用于获取最新版本或自定义模块以安装Nginx 1.24.x为例# 1. 安装编译依赖 sudo yum groupinstall -y Development Tools # CentOS sudo yum install -y pcre-devel openssl-devel zlib-devel # Ubuntu: sudo apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev libssl-dev # 2. 下载源码并解压 wget https://nginx.org/download/nginx-1.24.0.tar.gz tar -zxvf nginx-1.24.0.tar.gz cd nginx-1.24.0 # 3. 配置、编译、安装。这里显式启用stream模块。 ./configure --prefix/usr/local/nginx --with-stream --with-http_ssl_module make sudo make install源码安装后可执行文件在/usr/local/nginx/sbin/nginx。3.2 核心配置解析nginx.conf与stream块Nginx的主配置文件通常是/etc/nginx/nginx.conf包管理安装或/usr/local/nginx/conf/nginx.conf源码安装。我们需要在http块的同级配置stream块。一个最基础、完整的数据库端口转发配置示例如下# /etc/nginx/nginx.conf user nginx; worker_processes auto; error_log /var/log/nginx/error.log; pid /run/nginx.pid; # 加载动态模块如果需要 # load_module modules/ngx_stream_module.so; events { worker_connections 1024; } # 关键HTTP模块配置块用于Web服务与stream同级 http { ... # 你原有的HTTP相关配置 } # 关键Stream模块配置块用于TCP/UDP转发 stream { # 定义一个上游服务器组名为 mysql_backend upstream mysql_backend { # 后端真实的MySQL服务器地址和端口 server 192.168.1.100:3306 max_fails3 fail_timeout30s; # 可以添加多个server做负载均衡例如 # server 192.168.1.101:3306; } # 定义一个上游服务器组名为 pgsql_backend upstream pgsql_backend { server 192.168.1.200:5432; } # 配置一个监听服务器 server { # 监听本机的13306端口转发到mysql_backend组 listen 13306 so_keepaliveon; # so_keepalive用于开启TCP keepalive proxy_pass mysql_backend; # 连接超时设置 proxy_connect_timeout 5s; proxy_timeout 3600s; # 长连接超时根据业务设置 # 可选记录访问日志日志格式需要单独在stream块外定义见下文 access_log /var/log/nginx/mysql_proxy.access.log stream_proxy; } # 再配置一个监听服务器给PostgreSQL server { listen 15432; proxy_pass pgsql_backend; proxy_connect_timeout 5s; proxy_timeout 3600s; } # 可以配置一个默认server处理不匹配的连接通常关闭 server { listen 0.0.0.0:9000; return NGINX STREAM PROXY; # 或者直接关闭连接proxy_pass 127.0.0.1:9; (discard port) } } # 定义Stream模块专用的日志格式在events和stream块之外 log_format stream_proxy $remote_addr [$time_local] $protocol $status $bytes_sent $bytes_received $session_time $upstream_addr;配置关键点解读stream块与http块同级这是最容易出错的地方。stream不能放在http块里面它们是两个独立的模块。upstream块定义后端服务器组。可以包含多个serverNginx会以轮询方式分发TCP连接这是Stream模块默认的负载均衡方式。max_fails和fail_timeout参数用于健康检查当在fail_timeout时间内连续失败max_fails次该后端会被暂时标记为不可用。server块在stream内每个server块定义一个监听入口。listen指令指定监听的端口。proxy_pass指向要转发的上游组。超时参数proxy_connect_timeoutNginx与后端服务器建立连接的超时时间必须设置避免前端一直等待。proxy_timeout客户端或后端服务器连续不活动时间超过此值连接将被关闭。对于数据库长连接这个值要设置得足够大比如几小时否则连接会被意外断开。so_keepalive这个参数非常有用它启用TCP层的keepalive探测有助于及时发现并清理半开half-open的死连接对于维护连接池健康很重要。3.3 配置检查、重载与防火墙设置配置完成后务必进行语法检查sudo nginx -t如果显示syntax is ok和test is successful就可以安全地重载配置了sudo nginx -s reload # 如果nginx正在运行 # 或者 sudo systemctl reload nginx防火墙设置 确保你的防火墙如firewalld或iptables允许客户端访问Nginx服务器上监听的端口如13306, 15432。# 使用firewalld (CentOS/RHEL) sudo firewall-cmd --permanent --add-port13306/tcp sudo firewall-cmd --permanent --add-port15432/tcp sudo firewall-cmd --reload # 使用ufw (Ubuntu) sudo ufw allow 13306/tcp sudo ufw allow 15432/tcp同时确保Nginx服务器能够访问后端数据库的端口3306, 5432。如果数据库有白名单限制如云数据库的安全组需要将Nginx服务器的IP地址加入白名单。4. 高级配置与安全加固实践基础转发搭起来后为了更安全、更稳定地用于生产环境还需要进行一系列加固和优化。4.1 访问控制限制客户端IP允许任何IP连接你的转发端口仍然是危险的。Stream模块支持简单的访问控制虽然不如HTTP模块的allow/deny指令直观但可以通过$remote_addr变量配合geo和map模块或者使用ngx_stream_access_module默认编译来实现。方法一使用ngx_stream_access_module(推荐)这个模块默认通常已编译。可以在stream的server块内使用allow和deny指令。stream { server { listen 13306; proxy_pass mysql_backend; # 只允许特定IP段访问 allow 10.0.0.0/8; allow 192.168.1.0/24; # 拒绝所有其他IP deny all; ... # 其他配置 } }方法二使用geo和map模块实现更复杂的逻辑geo模块可以根据客户端IP创建变量。我们可以创建一个变量比如$allowed然后通过if指令判断Stream模块中需谨慎使用if。stream { geo $client_allowed { default 0; 10.0.0.0/8 1; 192.168.1.0/24 1; 172.16.0.100 1; # 允许单个IP } server { listen 13306; # 如果IP不在白名单则转发到一个无效地址如本机的关闭端口 if ($client_allowed 0) { set $upstream_group 127.0.0.1:9; # port 9 是discard端口 } if ($client_allowed 1) { set $upstream_group mysql_backend; } proxy_pass $upstream_group; } }注意在Stream模块中使用if是有条件的它仅在proxy_pass指令的“server”上下文中有效且可能带来一些副作用。对于简单的IP黑白名单优先使用access_module。4.2 连接数限制与负载均衡连接数限制 使用ngx_stream_limit_conn_module模块可以限制同一时间来自单个客户端IP的连接数防止某个客户端耗尽连接资源。stream { limit_conn_zone $remote_addr zoneper_ip:10m; # 定义共享内存区 limit_conn_log_level error; server { listen 13306; proxy_pass mysql_backend; limit_conn per_ip 20; # 每个IP同时最多20个连接 ... # 其他配置 } }负载均衡 在upstream块中配置多个server即可实现最简单的轮询负载均衡。Stream模块还支持least_conn最少连接算法。upstream db_cluster { least_conn; # 使用最少连接算法 server 192.168.1.101:3306; server 192.168.1.102:3306; server 192.168.1.103:3306 backup; # backup服务器仅当主服务器全挂时启用 }对于数据库来说简单的轮询或最少连接可能不是最优的因为数据库连接是有状态的连接上可能绑定事务、临时表等。因此数据库层面的负载均衡和高可用通常由数据库中间件如MyCat、ProxySQL或数据库集群自身如MySQL Group Replication, PostgreSQL HA with Patroni来处理。Nginx在这里更适合做“入口网关”或“故障转移路由”而不是复杂的负载均衡。4.3 SSL/TLS终端代理加密传输如果你的应用与Nginx之间需要通过公网或不安全网络通信可以在Nginx上终止SSL/TLS实现加密传输。这要求Nginx编译时包含--with-stream_ssl_module。配置示例stream { upstream mysql_secure_backend { server 192.168.1.100:3306; } server { listen 23306 ssl; # 监听带SSL的端口 proxy_pass mysql_secure_backend; ssl_certificate /etc/nginx/ssl/nginx.crt; # 你的证书 ssl_certificate_key /etc/nginx/ssl/nginx.key; # 你的私钥 ssl_protocols TLSv1.2 TLSv1.3; # 指定协议版本 ssl_ciphers HIGH:!aNULL:!MD5; # 指定加密套件 ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; } }这样客户端如MySQL客户端需要使用SSL方式连接到Nginx的23306端口。Nginx解密后再以明文或另一段SSL转发给后端数据库。注意这保护的是“客户端到Nginx”这段链路。如果Nginx到数据库也在不安全网络需要考虑在数据库端也启用SSL或者确保它们处于同一安全内网。4.4 日志与监控配置清晰的日志对于排查问题至关重要。前面我们已经定义了一个stream_proxy日志格式。为了让日志更有效建议按服务分开记录stream { log_format mysql_proxy $remote_addr - $remote_user [$time_local] $protocol $status $bytes_sent $bytes_received $upstream_addr $upstream_connect_time; server { listen 13306; proxy_pass mysql_backend; access_log /var/log/nginx/stream-mysql.access.log mysql_proxy buffer32k flush5s; error_log /var/log/nginx/stream-mysql.error.log; } }buffer32k flush5s为了提高性能可以设置日志缓冲。日志先写入内存缓冲区达到32k或超过5秒再刷入磁盘。监控方面除了查看日志还可以利用Nginx的stub_status模块仅HTTP或第三方模块如ngx_stream_status_module来暴露监控指标然后由Prometheus等监控系统采集。更简单直接的方法是监控Nginx进程状态、系统网络连接数netstat或ss命令以及错误日志中的异常信息。5. 生产环境部署与高可用架构单点Nginx会成为故障的瓶颈。生产环境必须考虑高可用HA方案。5.1 基于Keepalived的VIP方案经典方案这是最经典的高可用方案。使用两台或多台Nginx服务器通过Keepalived软件虚拟出一个VIPVirtual IP。客户端始终连接这个VIP。主备模式正常情况下VIP绑定在主Nginx上。主Nginx宕机后Keepalived通过VRRP协议选举VIP漂移到备机实现自动切换。配置要点两台Nginx服务器的配置nginx.conf需要保持一致。确保后端数据库允许来自两台Nginx服务器IP的连接。切换时已建立的TCP连接会中断客户端需要具备重连机制。这是所有基于IP漂移方案的共同特点。Keepalived基础配置示例/etc/keepalived/keepalived.conf 在主节点上vrrp_instance VI_DB_PROXY { state MASTER # 备用机设为 BACKUP interface eth0 # 网络接口名根据实际情况修改 virtual_router_id 51 # 虚拟路由器ID主备必须相同同一网段内唯一 priority 100 # 优先级备用机设为较低值如90 advert_int 1 # 心跳间隔秒数 authentication { auth_type PASS auth_pass your_secure_password # 主备密码一致 } virtual_ipaddress { 192.168.1.250/24 # 虚拟VIP } }部署后应用连接192.168.1.250:13306即可。5.2 基于DNS的负载均衡与故障转移另一种思路是使用DNS轮询。为你的Nginx代理服务配置一个域名如db-proxy.yourcompany.com在DNS解析记录中设置多条A记录指向多台Nginx服务器的真实IP。优点实现简单成本低。缺点DNS缓存导致故障切换延迟大TTL时间无法感知后端Nginx健康状态连接断开问题同样存在。可以结合云解析商的“健康检查”功能来增强当健康检查失败时自动从DNS记录中移除故障IP。5.3 容器化部署Docker在容器化环境中部署Nginx Stream代理也非常方便有利于配置管理和水平扩展。# Dockerfile FROM nginx:latest # 确保使用的nginx镜像包含stream模块官方镜像默认包含。 COPY nginx.conf /etc/nginx/nginx.conf COPY ssl/ /etc/nginx/ssl/ EXPOSE 13306 15432# docker-compose.yml 示例 version: 3.8 services: db-proxy: build: . container_name: nginx-db-proxy ports: - 13306:13306 - 15432:15432 volumes: - ./logs:/var/log/nginx # 挂载日志 restart: unless-stopped networks: - backend-network networks: backend-network: driver: bridge在K8s中可以通过Deployment部署多副本并通过Service类型为LoadBalancer或NodePort对外暴露服务。需要特别注意每个Pod的配置要完全一致并且后端数据库需要允许来自整个K8s Pod网段的连接。6. 实战排坑常见问题与解决方案在实际使用中你会遇到各种各样的问题。下面是我总结的一些典型坑点和解决方法。6.1 连接超时与中断问题现象客户端偶尔报连接超时或连接建立后空闲一段时间突然断开。排查proxy_timeout这是首要怀疑对象。默认值可能是10分钟或更短。对于数据库长连接需要根据业务最大空闲时间设置例如设置为proxy_timeout 24h;。检查TCP Keepalive在listen指令中添加so_keepaliveon参数让操作系统帮忙探测死连接。检查后端数据库的超时设置MySQL的wait_timeout、interactive_timeout参数如果小于Nginx的proxy_timeout数据库会先断开连接导致Nginx报错。需要确保数据库的超时时间 Nginx的proxy_timeout。防火墙/安全组策略检查中间网络设备防火墙、云安全组是否有会话超时设置可能会主动断开长时间空闲的TCP连接。6.2 性能瓶颈与调优现象并发连接数上去后Nginx代理机CPU或网络流量很高出现延迟。调整worker_processes和worker_connectionsevents { # 设置为CPU核心数或auto worker_connections 65535; # 每个worker进程允许的最大连接数 use epoll; # Linux高效事件模型 }调整系统内核参数对于需要处理大量并发连接如数万的场景需要调整Linux内核参数例如# /etc/sysctl.conf net.core.somaxconn 65535 net.ipv4.tcp_max_syn_backlog 65535 net.core.netdev_max_backlog 65535 fs.file-max 1000000修改后执行sysctl -p生效。监控与定位使用top、htop查看CPU使用ss -s查看连接统计使用iftop或nethogs查看网络流量。如果CPU是瓶颈考虑升级机器或分散代理到多台如果是网络带宽瓶颈考虑升级带宽或压缩流量Stream层无法压缩需从业务端考虑。6.3 配置错误排查流程当Nginx无法启动或代理不生效时按以下顺序排查语法检查nginx -t确保配置无误。错误日志第一时间查看error_log配置中指定的路径默认为/var/log/nginx/error.log。这里会记录启动失败、权限错误、连接后端失败等关键信息。进程与端口ps aux | grep nginx # 查看Nginx进程是否存在 sudo netstat -tlnp | grep nginx # 或 ss -tlnp | grep nginx查看端口是否监听网络连通性从Nginx服务器测试是否能连通后端数据库telnet 数据库内网IP 3306 # 或使用 nc nc -zv 数据库内网IP 3306访问日志查看配置的access_log看是否有请求进来以及转发的上游地址是否正确。6.4 与特定数据库的兼容性问题MySQL基本兼容。注意proxy_timeout设置。如果客户端使用特定的认证插件如caching_sha2_password需要确保Nginx到MySQL的整个链路支持。通常没问题因为Nginx只是转发TCP包。PostgreSQL也基本兼容。注意PostgreSQL的连接启动报文startup packet可能包含一些参数Nginx会透明转发。Redis同样适用。但Redis协议可能包含大量短连接需要调整系统net.ipv4.tcp_tw_reuse等参数来优化TIME_WAIT状态连接。Oracle / 达梦 / 人大金仓等商业数据库原理上TCP转发都支持。但需要特别注意连接字符串某些数据库客户端驱动可能对连接字符串格式有严格要求或者依赖特定的服务名解析。确保应用连接Nginx时使用的端口、主机名或IP在数据库驱动看来是有效的。工具兼容性像dbx数据库工具、idea导出数据库脚本这类图形化工具或插件它们连接时可能附带额外的参数或使用特定的协议扩展。用Nginx做透明转发一般没问题但最好先用telnet或命令行客户端测试通过。安全与授权数据库自身的用户权限体系是基于客户端IP的。当使用Nginx转发后数据库看到的所有连接都来自Nginx服务器的IP。因此数据库的授权需要授予Nginx服务器的IP或者使用数据库自身的用户名/密码认证与IP无关。这可能会影响某些基于IP的精细权限控制策略。最后再分享一个我个人的小技巧在stream块的server配置中为每个服务单独设置一个error_log这样在排查问题时日志不会混在一起能快速定位。例如server { listen 13306; proxy_pass mysql_backend; error_log /var/log/nginx/stream_mysql_error.log warn; # 只记录warn及以上级别 }这个习惯在管理多个转发规则时能为你节省大量时间。