本节摘要:stream 模块让 Nginx 在 TCP/UDP 层做代理与负载均衡,不解析 HTTP,开销更低、适用更广——数据库、Redis、消息队列的入口都能管。本节配置 MySQL 读写分离入口,并用一次全链路压测给第三章的扩容画上句号。
http 模块工作在应用层:读完整 HTTP 请求、改写头部、按 URI 调度。stream 模块工作在传输层:只见字节流,不管内容是什么。代价是没有 location、不能改 HTTP 头;收益是通用与轻量。
| 维度 | 七层 http 代理 | 四层 stream 代理 |
|---|---|---|
| 调度依据 | URI、头部、Host | 仅 IP 与端口 |
| 内容改写 | 支持 | 不支持 |
| 协议适配 | HTTP 系 | 任意 TCP/UDP |
| 适用 | Web 应用 | 数据库、缓存、MQ、TLS 直通 |
stream { upstream mysql_write { server 10.0.2.21:3306; # 主库 } upstream mysql_read { least_conn; server 10.0.2.22:3306; # 从库1 server 10.0.2.23:3306; # 从库2 } server { listen 13306; proxy_pass mysql_write; proxy_connect_timeout 3s; } server { listen 13307; proxy_pass mysql_read; proxy_timeout 600s; # 匹配慢查询长连接 } }
应用侧写操作连 13306、读操作连 13307,主从切换时只改 Nginx 配置,应用零改动。注意 stream 块与 http 块平级,都写在最外层;proxy_timeout 要按数据库长连接的实际情况放宽,默认 10 分钟对夜间批量任务不够。
⚠️ stream 监听的后端最好同时限制来源 IP:allow/deny 只信任应用网段,否则等于把数据库端口暴露给了所有能连到 Nginx 的客户端。
server { listen 13306; allow 10.0.1.0/24; # 应用网段 deny all; proxy_pass mysql_write; }
扩容完成后,用 wrk 从外部打真实页面接口,验证整条链路:
wrk -t8 -c2000 -d60s --latency https://shop.example.com/api/items
关注三个数字的相对关系:
Latency 分布 :P99 是用户体验的真相,平均值会撒谎 upstream_response_time 日志:后端耗时占比,判断是否还需要扩应用 错误率 :0% 是验收线,出现 502 先查 2.3 节的缓冲与超时

stream 是独立模块,仓库版默认不编译进主二进制。装包后若报"unknown directive stream",先确认模块文件在不在:
# Debian/Ubuntu:把 stream 模块的 include 放进主配置顶部 ls /usr/share/nginx/modules/ | grep stream # 主配置最外层加一行(或在 conf.modules.d 里已自带) # load_module /usr/lib/nginx/modules/ngx_stream_module.so; nginx -t && nginx -s reload
第二类常见报错是 stream 与 http 里的 upstream 重名——两个模块的命名空间是合并的,mysql_read 若在 http 里也有定义,启动直接冲突。命名规范上给四层 upstream 统一加前缀(如 st_mysql_read),一类问题就此绝迹。
stream 入口统一后的最大红利是切换成本趋近于零。演练流程:应用零改动,运维只改一行 server 地址后 reload,观察切换窗口内的连接行为:
T0 改配置:10.0.2.21:3306 → 10.0.2.24:3306(新主库) T0+reload 新连接全部落到新主库 T0+5s 存量长连接仍在旧主库上(proxy_timeout 内不掐断) T0+60s 配合应用侧连接池回收,旧连接自然清零
演练揭示的行为要点:reload 不主动掐断 stream 的存量连接,切换不是瞬时的——应用连接池的最大生命周期要设成小于可容忍的切换窗口,否则会有应用长时间连在旧库上读写。这个参数的配合是四层代理方案里最容易被遗漏的一环。
四层方案的监控也常被遗漏:stream 的连接不产生 access_log 里的 HTTP 记录,需要单独打开 stream 日志才能看见谁在连、连了多少。日志格式沿用 Nginx 的 log_format 机制,关键字段是远程地址、目标 upstream 与连接时长。没有这层可见性,数据库入口的异常连接(应用连接池泄漏、扫描器试探)完全不可见——等数据库连接数告警时,问题已经在下游积压了很久。四层入口的可见性建设,应当与代理本身同一天上线。
四层代理还有一个不常见但救过场的用法:协议不明的神秘流量分析。某次安全团队怀疑内网某端口被滥用,把 Nginx stream 挂在该端口做透明中转并全量记录连接对,一天就画出了完整的访问图谱——谁在连、频率多高、单次连接多长。相比在网络设备上抓包分析,这种方式部署快、可随时撤除、且不依赖网络团队配合。它再次印证了四层代理的本质:因为不理解内容,它反而能服务于任何内容。
四层与七层的选择,最终可以压缩成一句判断:需要理解请求内容才做得了的事,交给七层;只需要"把字节搬到正确的机器",四层更快也更通用。第三章的扩容战役到这里全部收官:应用流量七层分发,数据库入口四层接管,整条链路有了统一的入口、统一的健康摘除与统一的观察面。