nginxtcp负载均衡
去年双十一,负载我蹲在机房盯着监控屏,均衡后端三台Tomcat的负载CPU曲线像三条疯狗——一台跑满,另外两台闲得发慌。均衡Nginx的负载access.log里,80%的均衡请求全砸在同一台机器上。问题出在默认的负载round-robin策略上,那台配置最差的均衡机器反而因为响应快被分配了更多连接。换掉权重,负载五分钟搞定。均衡这就是负载TCP负载均衡的日常:你永远不知道下一颗巧克力是什么味道。
Nginx做TCP负载均衡,均衡核心是负载stream模块。跟HTTP的均衡upstream长得像,但玩法完全不同。负载HTTP层能看URL、改Header、缓存响应,TCP层就是个纯粹的管道工——它只认IP和端口,把字节流从A搬到B。这意味着你不能在stream块里写proxy_set_header,也没有proxy_cache。第一次配的时候我踩过坑,把HTTP的配置直接复制过来,nginx -t直接报错,提示unknown directive。后来才明白,stream模块是独立编译的,很多发行版默认没带,得自己加--with-stream重新编译。
配置本身不复杂。一个典型的MySQL读写分离场景:
stream { upstream mysql_cluster { least_conn; server 192.168.1.10:3306 weight=3 max_fails=2 fail_timeout=10s; server 192.168.1.11:3306 weight=1; server 192.168.1.12:3306 backup; } server { listen 3306; proxy_pass mysql_cluster; proxy_timeout 30s; proxy_connect_timeout 3s; } } least_conn比round-robin聪明,它把新连接扔给当前活跃连接数最少的后端。但别迷信它——如果后端处理速度差异大,least_conn反而会让慢机器堆积更多连接。我试过用hash $remote_addr consistent做会话保持,结果某台机器重启后,所有哈希槽重新分配,连接像雪崩一样压过来。一致性哈希不是银弹,节点少的时候尤其明显。
健康检查是个坑。Nginx开源版只有被动检查:max_fails和fail_timeout。后端挂了,得等客户端请求失败两次才标记不可用。生产环境忍不了这个延迟。要么上Nginx Plus(花钱),要么用nginx-upsync-module配合Consul做动态 upstream。我选了个土办法:写个cron脚本,每5秒curl一次后端端口,失败就调Nginx的API动态摘除节点。丑,但管用。
还有个细节:proxy_protocol。后端应用想拿真实客户端IP,得在stream的server块里开proxy_protocol on,同时后端也得支持解析。MySQL不支持?那就别指望在数据库层做IP白名单。Redis倒是可以,但得在redis.conf里加proxy-protocol参数。我见过有人把Nginx的stream日志格式写成log_format basic '$remote_addr [$time_local] $protocol $status $bytes_sent $bytes_received $session_time',结果发现$remote_addr永远是Nginx本机IP——因为TCP层没有X-Forwarded-For,得靠proxy_protocol传。
性能上,单台Nginx扛10万并发TCP连接没问题,前提是调好内核参数。net.core.somaxconn、net.ipv4.tcp_tw_reuse、net.ipv4.ip_local_port_range,这几个不调,Nginx再牛也白搭。我见过一个哥们儿用默认的somaxconn=128,压测到3000连接就开始丢包,查了一整天以为是Nginx的锅。
最后说个反直觉的:TCP负载均衡不一定比LVS快。LVS在四层转发上更接近内核态,Nginx毕竟要走用户态。但Nginx胜在配置灵活、日志清晰、能跟HTTP共享同一套运维体系。选哪个?看团队。运维只会Nginx,那就别折腾LVS。技术选型不是比谁跑分高,是比谁半夜出故障时你能最快修好。