在互联网应用中,随着用户量的增长和业务复杂度的提升,如何有效地分配流量、提高服务可用性成为了关键。Nginx不仅擅长七层HTTP协议的处理,还支持四层(传输层)的TCP/UDP协议负载均衡,为网络服务提供了强大的扩展能力。本文将详细介绍如何使用Nginx实现四层负载均衡。
什么是四层负载均衡?
四层负载均衡工作于OSI模型的传输层,它根据IP地址和端口号来决定如何转发客户端请求。相比于七层负载均衡,四层负载均衡更直接地基于网络数据包进行操作,因此具有更低的延迟和更高的性能。
实现 Nginx 四层负载均衡
Nginx在1.9.0版本开始支持tcp模式的负载均衡,在1.9.13版本开始支持udp协议的负载,udp主要用于 DNS的域名解析,其配置方式和指令和http代理类似,其基于ngx_stream_proxy_module模块实现tcp 负载,另外基于模块ngx_stream_upstream_module实现后端服务器分组转发、权重分配、状态监测、 调度算法等高级功能。
如果编译安装,需要指定 –with-stream 选项才能支持ngx_stream_proxy_module模块。
官方文档
https://nginx.org/en/docs/stream/ngx_stream_proxy_module.html
http://nginx.org/en/docs/stream/ngx_stream_upstream_module.html
TCP协议负载均衡配置参数
stream { #定义stream相关的服务;Context:main
upstream backend { #定义后端服务器
hash $remote_addr consistent; #定义调度算法,当后端服务器可用时此算法才生效,不可用时,可能调度其它可用的主机
server backend1.example.com:12345 weight=5; #定义具体server
server 127.0.0.1:12345 max_fails=3 fail_timeout=30s;
server unix:/tmp/backend3;
}
upstream dns { #定义后端服务器
server 192.168.1.20:53535; #定义具体server
server dns.example.com:53;
}
server { #定义server
listen 12345; #监听IP:PORT
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;
}
server {
listen 2222;
proxy 192.168.1.20:22; # 只实现四层代理,无负载均衡功能
}
}
负载均衡实例 : Redis
后端服务器安装redis
#安装两台redis服务器
[root@centos8 ~]# yum -y install redis
[root@centos8 ~]# sed -i '/^bind /c bind 0.0.0.0' /etc/redis.conf
[root@centos8 ~]# systemctl enable --now redis
[root@centos8 ~]# ss -tnl | grep 6379
nginx配置
[root@centos8 ~]# vim /apps/nginx/conf/nginx.conf
include /apps/nginx/conf/tcp/tcp.conf; #注意此处的include与http模块平级
[root@centos8 ~]# mkdir /apps/nginx/conf/tcp
[root@centos8 ~]# cat /apps/nginx/conf/tcp/tcp.conf
stream {
upstream redis_server {
#hash $remote_addr consistent;
server 192.168.1.30:6379 max_fails=3 fail_timeout=30s;
server 192.168.1.40:6379 max_fails=3 fail_timeout=30s;
}
server {
listen 192.168.1.20:6379;
proxy_connect_timeout 3s;
proxy_timeout 3s;
proxy_pass redis_server;
}
}
#重启nginx并访问测试
[root@centos8 ~]# systemctl restart nginx
[root@centos8 ~]# ss -tnl | grep 6379
LISTEN 0 128 192.168.1.20:6379 *:*
#测试通过nginx 负载连接redis:
[root@centos8 ~]#redis-cli -h 192.168.1.30 set name cao
OK
[root@centos8 ~]#redis-cli -h 192.168.1.30 get name
(nil)
[root@centos8 ~]#redis-cli -h 192.168.1.30 get name
"cao"
负载均衡实例: MySQL
后端服务器安装mysql
#先修改后端两个服务器的主机名,方便测试
[root@rocky9 ~]# hostnamectl hostname mysql-server1
[root@rocky9 ~]# hostnamectl hostname mysql-server2
#安装MySQL服务
[root@mysql-server1 ~]# yum install -y mysql-server
[root@mysql-server1 ~]# systemctl enable --now mysqld.service
[root@mysql-server1 ~]# mysql -e "alter user cao@'192.168.1.%' identified with mysql_native_password by 'caoge'"
[root@mysql-server1 ~]# mysql
mysql> create user cao@'192.168.1.%' identified by 'caoge';
Query OK, 0 rows affected (0.02 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.01 sec)
mysql> exit
Bye
[root@mysql-server2 ~]# yum install -y mysql-server
[root@mysql-server2 ~]# systemctl enable --now mysqld.service
[root@mysql-server2 ~]# mysql -e "alter user cao@'192.168.1.%' identified with mysql_native_password by 'caoge'"
[root@mysql-server2 ~]# mysql
mysql> create user cao@'192.168.1.%' identified by 'caoge';
Query OK, 0 rows affected (0.03 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.01 sec)
mysql> exit
nginx配置
[root@rocky9 ~]# vim /app/nginx/conf/nginx.conf
include /app/stream/*.conf; #末尾添加这一行
[root@rocky9 ~]# mkdir /app/stream/
[root@rocky9 ~]# cd /app/stream/
[root@rocky9 stream]# vim mysql.conf
stream {
upstream mysql{
server 192.168.1.30:3306;
server 192.168.1.40:3306;
}
server {
listen 3306;
proxy_pass mysql;
}
}
[root@rocky9 stream]# nginx -s reload
#安装mysql客户端
[root@rocky9 app]# yum install -y mysql.x86_64
#测试通过nginx负载连接MySQL
[root@centos7 ~]# mysql -ucao -pcaoge -h192.168.1.30 -e 'show variables like "hostname"'
+---------------+---------------+
| Variable_name | Value |
+---------------+---------------+
| hostname | mysql-server1 |
+---------------+---------------+
[root@centos7 ~]# mysql -ucao -pcaoge -h192.168.1.20 -e 'show variables like "hostname"'
+---------------+---------------+
| Variable_name | Value |
+---------------+---------------+
| hostname | mysql-server2 |
+---------------+---------------+
#在192.168.101.10停止MySQL服务
[root@centos7 ~]# mysql -ucao -pcaoge -h192.168.1.40 -e 'show variables like "hostname"'
+---------------+---------------+
| Variable_name | Value |
+---------------+---------------+
| hostname | mysql-server2 |
+---------------+---------------+
UDP协议负载均衡实例:DNS
stream{
upstream dns_servers {
server 192.168.1.30:53;
server 192.168.1.40:53;
}
server {
listen 53 udp;
proxy_pass dns_servers;
}
}
实现 FastCGI
CGI的由来
最早的Web服务器只能简单地响应浏览器发来的HTTP请求,并将存储在服务器上的HTML文件返回给浏览器,也就是静态html文件,但是后期随着网站功能增多网站开发也越来越复杂,以至于出现动态技术,比如像php(1995年)、java(1995)、python(1991)语言开发的网站,但是nginx/apache服务器并不能直接运行 php、java这样的文件,apache实现的方式是打补丁,但是nginx缺通过与第三方基于协议实现,即通过某种特定协议将客户端请求转发给第三方服务处理,第三方服务器会新建新的进程处理用户的请求,处理完成后返回数据给Nginx并回收进程,最后nginx在返回给客户端,那这个约定就是通用网关接口(common gateway interface,简称CGI),CGI(协议)是web服务器和外部应用程序之间的接口标准,是cgi程序和web服务器之间传递信息的标准化接口。
为什么会有FastCGI?
CGI协议虽然解决了语言解析器和 Web Server 之间通讯的问题,但是它的效率很低,因为 Web Server 每收到一个请求都会创建一个CGI进程,PHP解析器都会解析php.ini文件,初始化环境,请求结束的时候再关闭进程,对于每一个创建的CGI进程都会执行这些操作,所以效率很低,而FastCGI是用来提高CGI性能的,FastCGI每次处理完请求之后不会关闭掉进程,而是保留这个进程,使这个进程可以处理多个请求。这样的话每个请求都不用再重新创建一个进程了,大大提升了处理效率。
什么是PHP-FPM?
PHP-FPM(FastCGI Process Manager:FastCGI进程管理器)是一个实现了Fastcgi的程序,并且提供进程管理的功能。进程包括master进程和worker进程。master进程只有一个,负责监听端口,接受来自web server的请求。worker进程一般会有多个,每个进程中会嵌入一个PHP解析器,进行PHP代码的处理。
fastCGI配置命令
http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html
FastCGI配置指令 Nginx基于模块ngx_http_fastcgi_module实现通过fastcgi协议将指定的客户端请求转发至php-fpm处理,其配置指令如下
fastcgi_pass address;
#转发请求到后端服务器,address为后端的fastcgi server的地址,可用位置:location, if in location
#示例
fastcgi_pass localhost:9000;
fastcgi_pass unix:/tmp/fastcgi.socket;
fastcgi_index name;
#fastcgi默认的主页资源,示例:fastcgi_index index.php;
fastcgi_param parameter value [if_not_empty];
#设置传递给FastCGI服务器的参数值,可以是文本,变量或组合,可用于将Nginx的内置变量赋值给自定义key
fastcgi_param REMOTE_ADDR $remote_addr; #客户端源IP
fastcgi_param REMOTE_PORT $remote_port; #客户端源端口
fastcgi_param SERVER_ADDR $server_addr; #请求的服务器IP地址
fastcgi_param SERVER_PORT $server_port; #请求的服务器端口
fastcgi_param SERVER_NAME $server_name; #请求的server name
Nginx默认配置示例:
location ~ .php$ {
root /scripts;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; #默认脚本路径
#fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; #此行写法不再需要上面的 root 指令
fastcgi_param HTTPS on; #如果是前端代理采用https,当前后端http服务器需要加此项,否则可能会造成页面显示不正常
include fastcgi_params; #此文件默认系统已提供,存放的相对路径为prefix/conf
}
fastcgi 缓存定义指令
注意使用fastcgi缓存, 可能会导致源代码更新失败,生产慎用
fastcgi_cache_path path [levels=levels] [use_temp_path=on|off]
keys_zone=name:size [inactive=time] [max_size=size] [manager_files=number]
[manager_sleep=time] [manager_threshold=time] [loader_files=number]
[loader_sleep=time] [loader_threshold=time] [purger=on|off]
[purger_files=number] [purger_sleep=time] [purger_threshold=time];
#定义fastcgi的缓存;
path #缓存位置为磁盘上的文件系统路径
max_size=size #磁盘path路径中用于缓存数据的缓存空间上限
levels=levels #缓存目录的层级数量,以及每一级的目录数量,levels=ONE:TWO:THREE,示例:leves=1:2:2
keys_zone=name:size #设置缓存名称及k/v映射的内存空间的名称及大小
inactive=time #缓存有效时间,默认10分钟,需要在指定时间满足fastcgi_cache_min_uses 次数被视为活动缓存。
缓存调用指令
fastcgi_cache zone | off;
#调用指定的缓存空间来缓存数据,可用位置:http, server, location
fastcgi_cache_key string;
#定义用作缓存项的key的字符串,示例:fastcgi_cache_key $request_uri;
fastcgi_cache_methods GET | HEAD | POST ...;
#为哪些请求方法使用缓存
fastcgi_cache_min_uses number;
#缓存空间中的缓存项在inactive定义的非活动时间内至少要被访问到此处所指定的次数方可被认作活动项
fastcgi_keep_conn on | off;
#收到后端服务器响应后,fastcgi服务器是否关闭连接,建议启用长连接
fastcgi_cache_valid [code ...] time;
#不同的响应码各自的缓存时长
fastcgi_hide_header field; #隐藏响应头指定信息
fastcgi_pass_header field; #返回响应头指定信息,默认不会将Status、X-Accel-...返回
FastCGI实战案例 : Nginx与php-fpm在同一服务器
php安装可以通过yum或者编译安装,使用yum安装相对比较简单,编译安装更方便自定义参数或选项
php 环境准备
使用base源自带的php版本
ubuntu安装php-fpm
[root@ubuntu2404 ~]#apt update
[root@ubuntu2404 ~]#apt -y install php-fpm
[root@ubuntu2404 ~]#php-fpm8.3 -v
PHP 8.3.6 (fpm-fcgi) (built: Mar 19 2025 10:08:38)
Copyright (c) The PHP Group
Zend Engine v4.3.6, Copyright (c) Zend Technologies
with Zend OPcache v8.3.6, Copyright (c), by Zend Technologies
[root@ubuntu2404 ~]#systemctl status php8.3-fpm.service
● php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager
Loaded: loaded (/usr/lib/systemd/system/php8.3-fpm.service; enabled; preset: enabled)
Active: active (running) since Mon 2025-07-07 10:00:39 CST; 1min 21s ago
Docs: man:php-fpm8.3(8)
Process: 23039 ExecStartPost=/usr/lib/php/php-fpm-socket-helper install /run/php/php-fpm.sock /etc/php/8.3/fpm/pool.d/www.conf 83 (code=exit>
Main PID: 23036 (php-fpm8.3)
Status: "Processes active: 0, idle: 2, Requests: 0, slow: 0, Traffic: 0req/sec"
Tasks: 3 (limit: 2210)
Memory: 7.6M (peak: 8.7M)
CPU: 49ms
CGroup: /system.slice/php8.3-fpm.service
├─23036 "php-fpm: master process (/etc/php/8.3/fpm/php-fpm.conf)"
├─23037 "php-fpm: pool www"
└─23038 "php-fpm: pool www"
Jul 07 10:00:39 ubuntu2404 systemd[1]: Starting php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager...
Jul 07 10:00:39 ubuntu2404 systemd[1]: Started php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager.
#如果nginx使用socket文件和PHP进行通信,需要修改nginx服务和PHP-fpm使用相同的用户,比如:www-data,否则会出现502错误
#如果nginx通过9000端口和PHP进行通信,则无需修改用户身份
[root@ubuntu2404 ~]#ll /run/php/php8.3-fpm.sock
srw-rw---- 1 www-data www-data 0 Jul 7 10:00 /run/php/php8.3-fpm.sock=
[root@ubuntu2404 ~]#ls /run/php/php-fpm.sock -l
lrwxrwxrwx 1 root root 30 Jul 7 10:00 /run/php/php-fpm.sock -> /etc/alternatives/php-fpm.sock
#默认不支持远程连接
[root@ubuntu2404 ~]#ss -ntl|grep 9000
#yum安装默认版本php和相关APP依赖的包
[root@rocky9 app]# yum -y install php-fpm php-mysqlnd php-json
[root@rocky9 ~]# useradd -r -s /sbin/nologin apache
[root@rocky9 ~]# systemctl status php-fpm.service
[root@rocky9 ~]# ps -ef|grep php
root 4186 1 0 16:10 ? 00:00:00 php-fpm: master process (/etc/php-fpm.conf)
apache 4187 4186 0 16:10 ? 00:00:00 php-fpm: pool www
apache 4188 4186 0 16:10 ? 00:00:00 php-fpm: pool www
apache 4189 4186 0 16:10 ? 00:00:00 php-fpm: pool www
apache 4190 4186 0 16:10 ? 00:00:00 php-fpm: pool www
apache 4191 4186 0 16:10 ? 00:00:00 php-fpm: pool www
root 4206 1417 0 16:12 pts/0 00:00:00 grep --color=auto php
php相关配置优化
[root@ubuntu2404 ~]#vim /etc/php/8.3/fpm/php.ini
date.timezone = Asia/Shanghai
post_max_size = 100M
upload_max_filesize = 100M
[root@ubuntu2404 ~]#vim /etc/php/8.3/fpm/pool.d/www.conf
listen = 127.0.0.1:9000
;listen = /run/php/php8.1-fpm.sock
pm.status_path = /pm_status
ping.path = /ping
[root@ubuntu2404 ~]#systemctl restart php8.3-fpm.service
[root@rocky9 ~]# grep "^[a-Z]" /etc/php-fpm.conf
include=/etc/php-fpm.d/*.conf
pid = /run/php-fpm/php-fpm.pid
error_log = /var/log/php-fpm/error.log
daemonize = yes #是否后台启动
[root@rocky9 ~]# grep -Ev '^;.*$|^ *$' /etc/php-fpm.d/www.conf
[www]
user = nginx
group = nginx
listen = /run/php-fpm/www.sock #指定使用UDS,或者使用下面形式,需要注意文件权限
;listen = 127.0.0.1:9000 #监听地址及IP
listen.acl_users = apache,nginx
listen.allowed_clients = 127.0.0.1
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.status_path = /pm_status #修改此行
ping.path = /ping #修改此行
ping.response = ping-pong #修改此行
slowlog = /var/log/php-fpm/www-slow.log #慢日志路径
php_admin_value[error_log] = /var/log/php-fpm/www-error.log #错误日志
php_admin_flag[log_errors] = on
php_value[session.save_handler] = files #phpsession保存方式及路径
php_value[session.save_path] = /var/lib/php/session #当时使用file保存session的文件路径
#文件最后修改下面两行
php_value[session.save_handler] = redis
php_value[session.save_path] = "tcp://redis-server:6379" #当时使用file保存session的文
php_value[soap.wsdl_cache_dir] = /var/lib/php/wsdlcache
php_value[upload_max_filesize] = 20m
php_value[post_max_size] = 20m
php_value[date.timezone] = Asia/Shanghai
#修改配置文件后记得重启php-fpm
[root@rocky9 ~]# systemctl restart php-fpm.service
[root@ubuntu2404 ~]#apt install mysql-server-8.0
[root@ubuntu2404 ~]#MySQL
准备php测试页面
[root@rocky9 ~]# mkdir -p /data/php
[root@rocky9 ~]# vim /data/php/index.php
<?php
phpinfo();
?>
[root@rocky9 ~]#vim /data/php/mysql.php
<?php
$servername = "localhost";
$username = "root";
$password = "123456";
// 创建连接
$conn = mysqli_connect($servername,$username, $password);
// 检测连接
if (!$conn) {
die("php连接MySQL数据库失败: " . mysqli_connect_error());
}
echo "php连接MySQL数据库成功!";
?>
Nginx配置转发
Nginx安装完成之后默认生成了与fastcgi的相关配置文件,一般保存在nginx的安装路径的conf目录当中。比如/apps/nginx/conf/fastcgi.conf、/apps/nginx/conf/fastcgi_params
[root@rocky9 ~]# cd /app/
[root@rocky9 app]# vim pc.conf #在指定文件配置fastcgi
server {
listen 80;
server_name www.caoge.com;
index index.php index.html;
location ~ .php$|pm_status|ping {
root /data/php; #下面的$document_root调用此行的root指令指定的目录
fastcgi_pass unix:/run/php-fpm/www.sock; #指定使用UDS,或者使用下面形式,需要注意文件权限
#fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
#fastcgi_param SCRIPT_FILENAME /data/php$fastcgi_script_name;
#如果SCRIPT_FILENAME是上面的绝对路径则可以省略root /data/php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_param HTTPS $https if_not_empty; #有些应用支持https需要此项,否则会造成页面不正常
}
}
[root@rocky9 app]# nginx -s reload
#常见的错误:
File not found. #路径不对
502 #php-fpm处理超时、服务停止运行等原因导致的无法连接或请求超时
php-fpm的运行状态页面
访问配置文件里面指定的路径,会返回php-fpm的当前运行状态
nginx配置
location ~ ^/(ping|pm_status)$ {
fastcgi_pass unix:/run/php-fpm/www.sock;
#fastcgi_pass 127.0.0.1:9000;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_script_name;
include fastcgi_params;
}
#重启Nginx并测试:
[root@centos7 ~]# curl www.caoge.com/pm_status
pool: www
process manager: dynamic
start time: 04/Dec/2024:16:26:15 +0800
start since: 1591
accepted conn: 9
listen queue: 0
max listen queue: 0
listen queue len: 0
idle processes: 4
active processes: 1
total processes: 5
max active processes: 1
max children reached: 0
slow requests: 0
FastCGI实战案例:Nginx与php不在同一个服务器
nginx会处理静态请求,但是会转发动态请求到后端指定的php-fpm服务器,因此php代码需要放在后端的php-fpm服务器,即静态页面放在Nginx服务器上,而动态页面放在后端php-fpm服务器,通常情况下,一般都是采用在同一个服务器
yum安装较新版本php-fpm
[root@centos8 ~]#dnf -y install php-fpm php-mysqlnd php-json
[root@centos8 ~]#rpm -ql php-fpm
修改php-fpm监听配置
php-fpm默认监听在127.0.0.1的9000端口,也就是无法远程连接,因此要做相应的修改
#修改监听配置
[root@centos8 ~]#vim /etc/php-fpm.d/www.conf
;listen = /run/php-fpm/www.sock #注释此行
listen = 9000 #修改此行,指定监听端口
;listen.allowed_clients = 127.0.0.1 #注释此行
准备php测试页面
#准备php数据目录
[root@centos8 ~]# mkdir -p /data/php
[root@centos8 ~]# vim /data/php/index.php
<?php
phpinfo();
?>
#测试连接数据库
[root@centos8 ~]# vim /data/php/mysql.php
<?php
$servername = "localhost";
$username = "root";
$password = "123456";
// 创建连接
$conn = mysqli_connect($servername,$username, $password);
// 检测连接
if (!$conn) {
die("php连接MySQL数据库失败: " . mysqli_connect_error());
}
echo "php连接MySQL数据库成功!";
?>
#运行测式脚本
[root@centos8 ~]#php /data/php/mysql.php
启动并验证php-fpm
#启动php-fpm
[root@centos8 ~]#systemctl enable --now php-fpm.service
#验证php-fpm进程及端口:
[root@centos8 ~]#ps -ef | grep php-fpm
[root@centos8 ~]#ss -ntl
[root@centos8 ~]# lsof -i :9000
nginx配置转发
[root@centos8 ~]#vi /apps/nginx/conf/conf.d/pc.conf
location ~ .php$ {
root /data/php;
fastcgi_pass 192.168.1.30:9000;
fastcgi_index index.php;
#fastcgi_param SCRIPT_FILENAME /data/php$fastcgi_script_name;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ ^/(ping|pm_status)$ {
fastcgi_pass 192.168.1.30:9000;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_script_name;
include fastcgi_params;
}
#重启nginx
[root@centos8 ~]# systemctl restart nginx
实际应用场景
四层负载均衡适用于需要高性能和低延迟的应用场景,如数据库连接池、实时通信服务等。由于其不解析具体的协议内容,仅根据IP和端口进行路由决策,因此能够提供非常高效的流量分发。
结语
通过本文,我们了解了如何利用Nginx实现四层负载均衡,并探索了一些高级配置选项和实际应用场景。希望这些信息能帮助你在构建高可用、高性能的网络服务时做出更好的技术选型和架构设计。如果你有更多关于Nginx或其他技术话题的问题,欢迎继续关注我的博客更新!
请注意,上述配置仅为演示用途,具体应用时需根据实际情况调整。此外,确保你的Nginx版本支持Stream模块,否则可能需要重新编译安装。