概述
其实 Nginx 的功能特别多,这里我只介绍几个常用的功能,具体的大家可以参考官网介绍。
- 反向代理
这是 Nginx 服务器作为 WEB 服务器的主要功能之一,客户端向服务器发送请求时,会首先经过 Nginx 服务器,由服务器将请求分发到相应的 WEB 服务器。正向代理是代理客户端,而反向代理则是代理服务器,Nginx 在提供反向代理服务方面,通过使用正则表达式进行相关配置,采取不同的转发策略,配置相当灵活,而且在配置后端转发请求时,完全不用关心网络环境如何,可以指定任意的IP地址和端口号,或其他类型的连接、请求等。
- 负载均衡
这也是 Nginx 最常用的功能之一,负载均衡,一方面是将单一的重负载分担到多个网络节点上做并行处理,每个节点处理结束后将结果汇总返回给用户,这样可以大幅度提高网络系统的处理能力;另一方面将大量的前端并发请求或数据流量分担到多个后端网络节点分别处理,这样可以有效减少前端用户等待相应的时间。而 Nginx 负载均衡都是属于后一方面,主要是对大量前端访问或流量进行分流,已保证前端用户访问效率,并可以减少后端服务器处理压力。
- Web 缓存
在很多优秀的网站中,Nginx 可以作为前置缓存服务器,它被用于缓存前端请求,从而提高 Web服务器的性能。Nginx 会对用户已经访问过的内容在服务器本地建立副本,这样在一段时间内再次访问该数据,就不需要通过 Nginx 服务器向后端发出请求。减轻网络拥堵,减小数据传输延时,提高用户访问速度。
安装
安装环境
1 yum install gcc-c++
2 yum install -y pcre pcre-devel
3 yum install -y zlib zlib-devel
4 yum install -y openssl openssl-devel
- gcc,因为安装nginx需要先将官网下载的源码进行编译,编译依赖gcc环境,如果没有gcc环境的话,需要安装gcc。
- pcre,prce(Perl Compatible Regular Expressions)是一个Perl库,包括 perl 兼容的正则表达式库。nginx的http模块使用pcre来解析正则表达式,所以需要在linux上安装pcre库。
- zlib,zlib库提供了很多种压缩和解压缩的方式,nginx使用zlib对http包的内容进行gzip,所以需要在linux上安装zlib库。
- openssl,OpenSSL 是一个强大的安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议,并提供丰富的应用程序供测试或其它目的使用。nginx不仅支持http协议,还支持https(即在ssl协议上传输http),所以需要在linux安装openssl库
编译安装
首先将下载的 nginx-1.14.0.tar.gz 文件复制到 Linux 系统中,然后解压:
tar -zxvf nginx-1.14.0.tar.gz
接着进入到解压之后的目录,进行编译安装。
./configure --prefix=/usr/local/nginx # 指定 /usr/local/nginx 为nginx 服务安装的目录。
make
make install
基本命令
# 启动nginx
./usr/local/nginx/sbin/nginx # 在安装目录中的/sbin/文件夹下
# 停止nginx
./usr/local/nginx/sbin/nginx -s stop # 快速停止,先查出nginx进程id再使用kill命令强制杀掉进程。不太友好
./usr/local/nginx/sbin/nginx -s quit # 平缓停止,允许 nginx 服务将当前正在处理的网络请求处理完成,但不在接收新的请求,之后关闭连接,停止工作
./nginx -s quit
./nginx # 重启
./nginx -s reload # 重新加载配置文件
# 检查配置语法是否正确
nginx -t -c /usr/local/nginx/conf/nginx.conf # 检查指定文件
nginx -t # 没有-c默认检查nginx.conf
配置文件
# 全局区
worker_processes 1;
#user nobody; # 运行用户,不知指定默认nobody
#error_log logs/error.log; # 错误日志文件位置
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid; # PID文件位置
# events块
events {
use epoll; # 使用epoll I/O 模型,2.6以上版本的系统内核,建议使用epoll模型以提高性能
worker_connections 1024; # 每个进程处理的事件为1024
}
# http块
http {
include mime.types; # 文件扩展名与文件类型映射表
default_type application/octet-stream; # 默认文件类型
#log_format main '$remote_addr . $remote_user [$time_local] "$request" ' # 日志格式设定
# '$status $body_bytes_sent "$http_referer"'
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log ma1n; # 访问日志位置
sendfile on; # 开启文件传输模式
#tcp_nopush on; # 减少网络报文段的数量
#keepalive_timeout 0; # 连接不超时
keepalive_timeout 65; # 连接超时时间,单位s
#gzip on; # gzip模块设置,设置是否开启gzip压缩
# server块
server {
listen 80; # 监听地址及端口
server_name localhost; # 站点域名 可以有多个 用空格隔开
# charset utf-8 # 网页的默认字符集
# location块
location / { # 根目录的配置
root html; # 网站根目录的位置
#alias # 还可以配置别名
index index.html index.htm; # 默认首页文件名
}
error_page 500 502 503 504 /50x.html; # 内部错误反馈页
location = /50x.html {
root html; # 错误页面配置
}
}
}
upstream {
}
全局块
全局配置,全局生效,主要包括配置运行 Nginx 服务器的用户(组)、允许生成的 worker process 数,进程 PID 存放路径、日志存放路径和类型以及配置文件的引入等
参数worker_processes
值越大支持处理的并发量越多,会受到软硬件影响
worker_processes * worker_connections
结果为实际处理事件的总数
如提高每个进程的连接数还需执行“ulimit -n 65535”命令临时修改本地每个进程可以同时打开的最大文件数,还可以永久修改vim /etc/security/limits.conf
注意:软硬件的事件处理都要设置才能生效,并且保存退出后,要重新连接查看才会生效
在Linux平台上,在进行高并发TCP连接处理时,最高的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制(这是因为系统为每个TCP连接都要创建一个socket句柄,每个socket句柄同时也是一个文件句柄)。
可使用ulimit -a命令查看系统允许当前用户进程打开的文件数限制。
epoll是Linux内核为处理大批句柄而作改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著的减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。(实现异步非阻塞)
events
events 块涉及的指令主要影响 Nginx 服务器与用户的网络连接,常用的设置包括是否开启对多 work process 下的网络连接进行序列化,是否允许同时接收多个网络连接,选取哪种事件驱动模型来处理连接请求,每个 word process 可以同时支持的最大连接数等
上述例子就表示每个 work process 支持的最大连接数为 1024
这部分的配置对 Nginx 的性能影响较大,在实际中应该灵活配置
http块
配置代理,缓存,日志定义等绝大多数功能和第三方模块的配置,http全局块配置的指令包括文件引入、MIME-TYPE 定义、日志自定义、连接超时时间、单链接请求数上限等
和虚拟主机有密切关系,虚拟主机从用户角度看,和一台独立的硬件主机是完全一样的,该技术的产生是为了节省互联网服务器硬件成本
每个 http 块可以包括多个 server 块,而每个 server 块就相当于一个虚拟主机
日志格式设定:
$remote_addr与$http_x_forwarded_for用以记录客户端的ip地址
$remote_user:用来记录客户端用户名称
$time_local: 用来记录访问时间与时区
$request: 用来记录请求的url与http协议
$status: 用来记录请求状态;成功是200
$body_bytes_sent :记录发送给客户端文件主体内容大小
$http_referer:用来记录从哪个页面链接访问过来的
$http_user_agent:记录客户浏览器的相关信息
通常web服务器放在反向代理的后面,这样就不能获取到客户的IP地址了,通过$remote_add拿到的IP地址是反向代理服务器的iP地址。反向代理服务器在转发请求的http头信息中,可以增加x_forwarded_for信息,用以记录原有客户端的IP地址和原来客户端的请求的服务器地址
server块
- 全局server块:本虚拟机主机的监听配置和本虚拟主机的名称或IP配置
- location块:这块的主要作用是基于 Nginx 服务器接收到的请求字符串(例如 server_name/uri-string),对虚拟主机名称(也可以是IP别名)之外的字符串(例如 前面的 /uri-string)进行匹配,对特定的请求进行处理。地址定向、数据缓存和应答控制等功能,还有许多第三方模块的配置也在这里进行
upstream块
配置后端服务器具体地址,负载均衡配置不可或缺的部分
注意:location 匹配的内容来源是来自网页的URI,而不是URL(URL代表整个链接如:www.baidu.com/images/search,而URI则是/images/search。所以nginx的location匹配的是URI)
反向代理
nginx 可以代理 http/https 、ICMP/POP/IMAP 、RTMP,用最多的就是http/https代理服务器
正向代理:客户端的代理,跨境访问VPN的原理
反向代理:服务器的代理,暴露的是代理服务器地址,隐藏了真实服务器IP地址
Listen
listen *:80 | *:8080 # 监听所有80端口和8080端口
# 不指定端口就是所有端口
listen port[default_server] [setfib=number] [backlog=number] [rcvbuf=size] [sndbuf=size] [accept_filter=filter] [deferred] [bind] [ipv6only=on|off] [ssl];
参数解释:
- address:IP地址,如果是 IPV6地址,需要使用中括号[] 括起来,比如[fe80::1]等。
- port:端口号,如果只定义了IP地址,没有定义端口号,那么就使用80端口。
- path:socket文件路径,如 var/run/nginx.sock等。
- default_server:标识符,将此虚拟主机设置为 address:port 的默认主机。(在 nginx-0.8.21 之前使用的是 default 指令)
- setfib=number:Nginx-0.8.44 中使用这个变量监听 socket 关联路由表,目前只对 FreeBSD 起作用,不常用。
- backlog=number:设置监听函数listen()最多允许多少网络连接同时处于挂起状态,在 FreeBSD 中默认为 -1,其他平台默认为511.
- rcvbuf=size:设置监听socket接收缓存区大小。
- sndbuf=size:设置监听socket发送缓存区大小。
- deferred:标识符,将accept()设置为Deferred模式。
- accept_filter=filter:设置监听端口对所有请求进行过滤,被过滤的内容不能被接收和处理,本指令只在 FreeBSD 和 NetBSD 5.0+ 平台下有效。filter 可以设置为 dataready 或 httpready 。
- bind:标识符,使用独立的bind() 处理此address:port,一般情况下,对于端口相同而IP地址不同的多个连接,Nginx 服务器将只使用一个监听指令,并使用 bind() 处理端口相同的所有连接。
- ssl:标识符,设置会话连接使用 SSL模式进行,此标识符和Nginx服务器提供的 HTTPS 服务有关。
server_name
server_name *.123.com www.123.* # 使用通配符
server_name ~^www\d+\.123\.com$; # 使用正则
server_name 192.168.1.1 # 使用IP
location
location [ = | ~ | ~* | ^~] uri { }
- = :用于不含正则表达式的 uri 前,要求请求字符串与 uri 严格匹配,如果匹配成功,就停止继续向下搜索并立即处理该请求。
- ~:用于表示 uri 包含正则表达式,并且区分大小写。
- ~*:用于表示 uri 包含正则表达式,并且不区分大小写。
- ^~:用于不含正则表达式的 uri 前,要求 Nginx 服务器找到标识 uri 和请求字符串匹配度最高的 location 后,立即使用此 location 处理请求,而不再使用 location 块中的正则 uri 和请求字符串做匹配。
注意:如果 uri 包含正则表达式,则必须要有 ~ 或者 ~* 标识。
proxy_pass
该指令用于设置被代理服务器的地址。可以是主机名称、IP地址加端口号的形式
proxy_pass http://www.123.com/uri;
URL 为被代理服务器的地址,可以包含传输协议、主机名称或IP地址加端口号,URI等
index
后面的文件名称可以有多个,中间用空格隔开
index index.html index.jsp;
负载均衡
并发量很大 -> 提高及性能 -> 提升有限,成本高
-> 增加多台机器 -> 集群(负载均衡)
主要配置指令为上一讲的 pass_proxy 指令以及 upstream 指令。负载均衡主要通过专门的硬件设备或者软件算法实现。通过硬件设备实现的负载均衡效果好、效率高、性能稳定,但是成本较高。而通过软件实现的负载均衡主要依赖于均衡算法的选择和程序的健壮性。均衡算法又主要分为两大类:
静态负载均衡算法:主要包括轮询算法、基于比率的加权轮询算法或者基于优先级的加权轮询算法。
动态负载均衡算法:主要包括基于任务量的最少连接优化算法、基于性能的最快响应优先算法、预测算法及动态性能分配算法等。
静态负载均衡算法在一般网络环境下也能表现的比较好,动态负载均衡算法更加适用于复杂的网络环境
轮询访问
upstream OrdinaryPolling {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://OrdinaryPolling;
index index.html index.htm index.jsp;
}
}
效果就是,会交替访问两个服务器,1,2,1,2,…
基于比例加权轮询
upstream OrdinaryPolling {
server 127.0.0.1:8080 weight=5;
server 127.0.0.1:8081 weight=2;
}
# server{} 同上
效果:明显8080访问次数要比8081多,测试次数越多越接近2:5
基于IP路由的负载
最典型的一个例子:用户第一次进入一个系统是需要进行登录身份验证的,首先将请求跳转到Tomcat1服务器进行处理,登录信息是保存在Tomcat1 上的,这时候需要进行别的操作,那么可能会将请求轮询到第二个Tomcat2上,那么由于Tomcat2 没有保存会话信息,会以为该用户没有登录,然后继续登录一次,如果有多个服务器,每次第一次访问都要进行登录,这显然是很影响用户体验的。
这里产生的一个问题也就是集群环境下的 session 共享,如何解决这个问题?
通常由两种方法:
1、第一种方法是选择一个中间件,将登录信息保存在一个中间件上,这个中间件可以为 Redis 这样的数据库。那么第一次登录,我们将session 信息保存在 Redis 中,跳转到第二个服务器时,我们可以先去Redis上查询是否有登录信息,如果有,就能直接进行登录之后的操作了,而不用进行重复登录。
2、第二种方法是根据客户端的IP地址划分,每次都将同一个 IP 地址发送的请求都分发到同一个 Tomcat 服务器,那么也不会存在 session 共享的问题。
而 nginx 的基于 IP 路由负载的机制就是上诉第二种形式
upstream OrdinaryPolling {
ip_hash; # 同一个 IP 地址客户端发送的请求都将分发到同一个 Tomcat 服务器进行处理
server 127.0.0.1:8080 weight=5;
server 127.0.0.1:8081 weight=2;
}
# server{} 同上
基于服务器的响应时间负载均衡
upstream OrdinaryPolling {
server 127.0.0.1:8080 weight=5;
server 127.0.0.1:8081 weight=2;
fair; # 根据服务器处理请求的时间来进行负载,处理请求越快,也就是响应时间越短的优先分配
}
对不同域名实现负载均衡
通过配合location 指令块我们还可以实现对不同域名实现负载均衡
upstream wordbackend {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}
upstream pptbackend {
server 127.0.0.1:8082;
server 127.0.0.1:8083;
}
server {
listen 80;
server_name localhost;
location /word/ {
proxy_pass http://wordbackend;
index index.html index.htm index.jsp;
}
location /ppt/ {
proxy_pass http://pptbackend;
index index.html index.htm index.jsp;
}
}
访问状态统计与控制
访问状态统计
# 查看访问统计配置的相关模块
cat /opt/nginx-1.22.0/auto/options | grep YES #可查看 nginx 已安装的所有模块
/usr/local/nginx/sbin/nginx -V # 查看已安装的 Nginx 是否包含 HTTP_STUB_STATUS 模块
将主配置文件备份,并修改主配置文件
# vim /usr/local/nginx/conf/nginx.conf
server {
listen 80;
server_name www.yang.com;
charset utf-8;
location / {
root html;
index index.html index.htm;
}
location /status {
stub_status on; # 打开状态统计功能
access_log off; # 关闭此位置的日志记录
}
访问结果
Active connections:表示当前的活动连接数
server accepts handled requests:表示已经处理的连接信息,三个数字依次表示已处理的连接数、成功的TCP握手次数、已处理的请求数
case: 使用脚本一键查询并发量
#!/bin/bash
while true
do
#筛选静态状态的第三部分
a=$(curl -Ls 192.168.231.102/status | awk '/Active connections/{print $3}')
if [ $a -gt 2 ];then
echo "警报!当前并发连续过高!当前并发数为:$a"
fi
sleep 10 #睡眠10秒
done
脚本效果,每10s回去并发量,大于2时,发送预警
#查看并发连接数的另一种方法
netstat/ss -natp | grep nginx | grep -c ESTABLISHED
基于授权的访问控制
生册用户密码认证文件
yum install -y httpd-tools
htpasswd -c /usr/local/nginx/passwd.db zhangsan
chown nginx /usr/local/nginx/passwd.db
chmod 400 /usr/local/nginx/passwd.db
修改主配
# vim /usr/local/nginx/conf/nginx.conf
server {
location / {
......
##添加认证配置##
auth_basic "secret"; #设置密码提示框文字信息
auth_basic_user_file /usr/local/nginx/passwd.db;
}
}
重启服务,进行访问测试
基于客户端的访问控制
设置方式类似于黑白名单
设置前的访问,其他主机访问测试:
访问控制规则如下:
deny IP/IP 段:拒绝某个 IP 或 IP 段的客户端访问
allow IP/IP 段:允许某个 IP 或 IP 段的客户端访问
规则从上往下执行,如匹配则停止,不再往下匹配
# vim /usr/local/nginx/conf/nginx.conf
......
server {
location / {
......
##添加控制规则##
allow 192.168.73.105; #允许访问的客户端 IP
deny all; #拒绝其它IP客户端访问
}
}
设置后的访问测试:
Nginx的虚拟主机设置
相比较Apache的虚拟主机设置,Nginx的设置是十分简便的只需要修改主配置中的相关配置就能实现虚拟主机的效果
基于域名的虚拟主机
域名准备和网页准备
$ echo "192.168.73.105 www.test1.com www.test2.com" >> /etc/hosts
$ mkdir -p /var/www/html/test1
$ mkdir -p /var/www/html/test2
$ echo "<h1>this is test1</h1>" > /var/www/html/test1/index.html
$ echo "<h1>this is test2</h1>" > /var/www/html/test2/index.html
主配置文件的修改
# vim /usr/local/nginx/conf/nginx.conf
http {
......
server {
listen 80;
server_name www.test1.com;
charset utf-8;
access_log logs/www.test1.access.log;
location / {
root /var/www/html/test1;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
server {
listen 80;
server_name www.test2.com;
charset utf-8;
access_log logs/www.test2.access.log;
location / {
root /var/www/html/test2;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
..............
}
}
重启服务,访问测试
基于IP 的 Nginx 虚拟主机
设置虚拟主机IP
$ ifconfig ens33:0 192.168.73.200/24
$ ifconfig ens33:0
修改主配置文件
# vim /usr/local/nginx/conf/nginx.conf
......
http {
......
server {
listen 192.168.73.105:80;
server_name www.test1.com;
charset utf-8;
access_log logs/www.test1.access.log;
location / {
root /var/www/html/test1;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
server {
listen 192.168.73.200:80;
server_name www.test2.com;
charset utf-8;
access_log logs/www.test2.access.log;
location / {
root /var/www/html/test2;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
..........
}
重启服务,访问测试
基于端口的 Nginx 虚拟主机
修改主配置文件
# vim /usr/local/nginx/conf/nginx.conf
......
http {
......
server {
listen 192.168.73.105:666;
server_name www.test1.com;
charset utf-8;
access_log logs/www.test1.access.log;
location / {
root /var/www/html/test1;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
server {
listen 192.168.73.105:888;
server_name www.test2.com;
charset utf-8;
access_log logs/www.test2.access.log;
location / {
root /var/www/html/test2;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
..........
}
重启服务,测试访问测试
未解决的问题
1、在使用 nginx docker 配置文件中配置了监听 80 端口将请求转到 443 端口,监听 443 端口负责处理请求并响应
- 为什么没有开放 443 端口直接负责处理 https 请求因为 container 已经运行,并且 dockerfile 也没有只有 docker-compose 映射 nginx80 端口,为了最小化修改就将 80 直接监听 https 请求重定向到 443 端口发现可以正常处理
问题:但是如果 docker 是 bridge 模式,虽然 docker-compose 将 80 端口映射到主机但是发现直接请求宿主机 ip 无法请求
测试:此时 nginx 的 80 是通的,进入到 nginx 容器内部使用 curl -k https://127.0.0.1 发现可以正常访问,在宿主机使用 curl -k https://127.0.0.1 就会提示连接拒绝,在外部访问宿主机使用浏览器无反应,使用 curl 会提示函数错误,解释为 ssl 证书问题,实际观察 nginx 的内部日志可以收到请求但是请求所有内容都是加密过的(在内部访问成功会正确的到是 get 请求 并显示请求的 url 地址),发现这种情况下会直接将 https 请求转到 443 端口但是并没有服务监听这个端口就导致请求失败、拒绝
解决:就将 nginx 网络模式更换为 host 就可以访问,并将其中后段地址改为容器 IP 在 host 模式下不能和自定义的网络模式中的服务相互解析服务名,此时访问 https://ip,就能访问的到
疑惑:此时的请求是被解析成 443 还是 80 ,事实是 80 因为如果是 443 就不成功,https 每次被解析成 80,80 转为 443 也能成功响应 https 的请求